Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/SwiftJavaToolLib/JavaClassTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ extension JavaClassTranslator {

// Static fields go into a separate list.
if field.isStatic {
// Deduplicate by name: getFields() can return the same field from both an
// interface and its super-interface (e.g. serialVersionUID on Key and PublicKey).
guard !staticFields.contains(where: { $0.getName() == field.getName() }) else {
return
}
staticFields.append(field)

// Enum constants will be used to produce a Swift enum projecting the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,43 @@ final class BasicWrapJavaTests: XCTestCase {
)
}

// Test that static fields are not duplicated when both a Java interface and its
// super-interface independently declare the same field name.
//
// Real-world case: java.security.PublicKey extends java.security.Key, and both
// declare serialVersionUID. Class.getFields() returns both Field objects (with
// different declaring classes), which previously caused two @JavaStaticField
// declarations to be emitted in extension JavaClass<PublicKey>.
//
// Note: uses real JDK classes rather than compileJava() — the duplicate only
// manifests with JDK bytecode; freshly compiled interfaces apply stricter
// field-hiding rules that prevent getFields() from returning both fields.
//
// The closing `}` in the expected chunk is load-bearing: if there are two
// serialVersionUID declarations the `}` would be preceded by the second field,
// not the first, so the chunk would not match.
func test_wrapJava_noDuplicateStaticFieldsFromSuperInterface() async throws {
let classpathURL = try await compileJava("class Dummy {}")
try assertWrapJavaOutput(
javaClassNames: [
"java.security.Key",
"java.security.PublicKey",
],
classpath: [classpathURL],
expectedChunks: [
// PublicKey should close its extension block right after one serialVersionUID.
// A duplicate would insert a second field before the `}`, breaking this match.
"""
extension JavaClass<PublicKey> {
@available(*, deprecated)
@JavaStaticField(isFinal: true)
public var serialVersionUID: Int64
}
"""
]
)
}

// Test that Java methods named "init" get @JavaMethod("init") annotation.
// Since "init" is a Swift keyword and gets escaped with backticks in the function name,
// we explicitly specify the Java method name in the annotation.
Expand Down
Loading