From a6780be52edb7903c38b6bec6223611d5b6700e9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 12:10:58 -0700 Subject: [PATCH 01/17] JavaKit: Macro and translator support for mapping Java classes to Swift classes Java classes are currently translated in Swift structs, and we use is/as functions to cast up and down the hierarchy. Start down an alternative path where we translate Java classes into Swift classes, making proper use of inheritance in Swift to reflect inheritance in Java. This step updates the @JavaClass macro implementation to check whether it is being applied to a class (rather than a struct) and adjust its behavior accordingly: * The superclass is extracted from the inheritance clause of the class rather than the "extends" argument to the macro. * The "full Java class name" static member becomes a "class" member. For everything other than JavaObject itself, it is an override. * The `javaHolder` property is only emitted into JavaObject, nowhere else. * The `init(javaHolder:)` initializer becomes required. For everything other than JavaObject, it's implemented as a super.init call. * The `JavaSuperclass` typealias is no longer emitted. All of this is keyed off the subject of the attribute being a "class" rather than a "struct", so we keep the existing struct versions working. Relatedly, extend the translator with an option to map Java classes to Swift classes. This involves a number of changes: * Emitting each as an "open class" rather than "public struct" * Emitting the superclass into the inheritance clause rather than the "extends" argument * Emitting methods as "open" rather than "public" * Only emit methods and fields declared in the class, not inherited ones This option is only currently only enabled in test cases while we stage in this functionality. --- .../Java2SwiftLib/JavaClassTranslator.swift | 21 ++- Sources/Java2SwiftLib/JavaTranslator.swift | 8 ++ Sources/JavaKit/JavaObject+MethodCalls.swift | 18 ++- Sources/JavaKitMacros/GenerationMode.swift | 2 + Sources/JavaKitMacros/JavaClassMacro.swift | 116 +++++++++++---- ...Plugin.swift => JavaKitMacrosPlugin.swift} | 0 Sources/JavaKitMacros/JavaMethodMacro.swift | 14 +- Tests/Java2SwiftTests/Java2SwiftTests.swift | 31 +++- .../JavaClassMacroTests.swift | 136 ++++++++++++++++++ 9 files changed, 304 insertions(+), 42 deletions(-) rename Sources/JavaKitMacros/{SwiftJNIMacrosPlugin.swift => JavaKitMacrosPlugin.swift} (100%) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 1df6d683..3e12c76b 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -161,9 +161,15 @@ struct JavaClassTranslator { } // Gather methods. - for method in javaClass.getMethods() { + let methods = translator.translateAsClass + ? javaClass.getDeclaredMethods() + : javaClass.getMethods() + for method in methods { guard let method else { continue } + // Only look at public methods here. + guard method.isPublic else { continue } + // Skip any methods that are expected to be implemented in Swift. We will // visit them in the second pass, over the *declared* methods, because // we want to see non-public methods as well. @@ -178,6 +184,7 @@ struct JavaClassTranslator { } if translator.swiftNativeImplementations.contains(javaClass.getName()) { + // Gather the native methods we're going to implement. for method in javaClass.getDeclaredMethods() { guard let method else { continue } @@ -209,6 +216,12 @@ extension JavaClassTranslator { return } + // Don't include inherited fields when translating to a class. + if translator.translateAsClass && + !field.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) { + return + } + fields.append(field) } @@ -297,10 +310,11 @@ extension JavaClassTranslator { // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; + let introducer = translator.translateAsClass ? "open class" : "public struct" var classDecl: DeclSyntax = """ @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) - public struct \(raw: swiftInnermostTypeName)\(raw: genericParameterClause) { + \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } """ @@ -483,7 +497,8 @@ extension JavaClassTranslator { let methodAttribute: AttributeSyntax = implementedInSwift ? "" : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; - let accessModifier = implementedInSwift ? "" : "public " + let accessModifier = implementedInSwift ? "" + : "\(translator.defaultAccessSpecifier) " return """ \(methodAttribute)\(raw: accessModifier)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index 5889a193..c207edb3 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -26,6 +26,7 @@ package class JavaTranslator { let swiftModuleName: String let environment: JNIEnvironment + let translateAsClass: Bool let format: BasicFormat /// A mapping from the name of each known Java class to the corresponding @@ -61,10 +62,12 @@ package class JavaTranslator { package init( swiftModuleName: String, environment: JNIEnvironment, + translateAsClass: Bool = false, format: BasicFormat = JavaTranslator.defaultFormat ) { self.swiftModuleName = swiftModuleName self.environment = environment + self.translateAsClass = translateAsClass self.format = format } @@ -247,6 +250,11 @@ extension JavaTranslator { throw TranslationError.untranslatedJavaClass(name) } + + /// The default Swift access specifier for a Java method. + var defaultAccessSpecifier: String { + translateAsClass ? "open" : "public" + } } // MARK: Class translation diff --git a/Sources/JavaKit/JavaObject+MethodCalls.swift b/Sources/JavaKit/JavaObject+MethodCalls.swift index 0a48b8bd..453358ce 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/JavaKit/JavaObject+MethodCalls.swift @@ -237,12 +237,22 @@ extension AnyJavaObject { ) } - /// Call a Java method with the given name and arguments, which must be of the correct - /// type, that produces the given result type. + /// Construct a new Java object with the given name and arguments and return + /// the result Java instance. public static func dynamicJavaNewObject( in environment: JNIEnvironment, arguments: repeat each Param ) throws -> Self { + let this = try dynamicJavaNewObjectInstance(in: environment, arguments: repeat each arguments) + return Self(javaThis: this, environment: environment) + } + + /// Construct a new Java object with the given name and arguments and return + /// the result Java instance. + public static func dynamicJavaNewObjectInstance( + in environment: JNIEnvironment, + arguments: repeat each Param + ) throws -> jobject { let thisClass = try Self.getJNIClass(in: environment) // Compute the method signature so we can find the right method, then look up the @@ -257,11 +267,9 @@ extension AnyJavaObject { // Retrieve the constructor, then map the arguments and call it. let jniArgs = getJValues(repeat each arguments, in: environment) - let this = try environment.translatingJNIExceptions { + return try environment.translatingJNIExceptions { environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs) }! - - return Self(javaThis: this, environment: environment) } /// Retrieve the JNI field ID for a field with the given name and type. diff --git a/Sources/JavaKitMacros/GenerationMode.swift b/Sources/JavaKitMacros/GenerationMode.swift index d000d209..2bf9fc4f 100644 --- a/Sources/JavaKitMacros/GenerationMode.swift +++ b/Sources/JavaKitMacros/GenerationMode.swift @@ -54,6 +54,8 @@ enum GenerationMode { let attributes: AttributeListSyntax if let structSyntax = lexicalContext.as(StructDeclSyntax.self) { attributes = structSyntax.attributes + } else if let classSyntax = lexicalContext.as(ClassDeclSyntax.self) { + attributes = classSyntax.attributes } else if let extSyntax = lexicalContext.as(ExtensionDeclSyntax.self) { attributes = extSyntax.attributes } else { diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/JavaKitMacros/JavaClassMacro.swift index 5a6e25cb..f88d12e4 100644 --- a/Sources/JavaKitMacros/JavaClassMacro.swift +++ b/Sources/JavaKitMacros/JavaClassMacro.swift @@ -26,6 +26,11 @@ extension JavaClassMacro: MemberMacro { conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [DeclSyntax] { + guard let namedDecl = declaration.asProtocol(NamedDeclSyntax.self) else { + throw MacroErrors.javaClassNotOnType + } + let swiftName = namedDecl.name.text + // Dig out the Java class name. guard case .argumentList(let arguments) = node.arguments, let wrapperTypeNameExpr = arguments.first?.expression, @@ -36,59 +41,99 @@ extension JavaClassMacro: MemberMacro { throw MacroErrors.classNameNotStringLiteral } - // Dig out the "superclass" clause, if there is one. - let superclass: String - if let superclassArg = arguments.dropFirst().first, - let superclassArgLabel = superclassArg.label, - superclassArgLabel.text == "extends", - let superclassMemberAccess = superclassArg.expression.as(MemberAccessExprSyntax.self), - superclassMemberAccess.declName.trimmedDescription == "self", - let superclassMemberBase = superclassMemberAccess.base - { - superclass = superclassMemberBase.trimmedDescription + // Determine whether we're exposing the Java class as a Swift class, which + // changes how we generate some of the members. + let isSwiftClass: Bool + let isJavaLangObject: Bool + let specifiedSuperclass: String? + if let classDecl = declaration.as(ClassDeclSyntax.self) { + isSwiftClass = true + isJavaLangObject = classDecl.isJavaLangObject + + // Retrieve the superclass, if there is one. + specifiedSuperclass = classDecl.inheritanceClause?.inheritedTypes.first?.trimmedDescription } else { - superclass = "JavaObject" + isSwiftClass = false + isJavaLangObject = false + + // Dig out the "extends" argument from the attribute. + if let superclassArg = arguments.dropFirst().first, + let superclassArgLabel = superclassArg.label, + superclassArgLabel.text == "extends", + let superclassMemberAccess = superclassArg.expression.as(MemberAccessExprSyntax.self), + superclassMemberAccess.declName.trimmedDescription == "self", + let superclassMemberBase = superclassMemberAccess.base + { + specifiedSuperclass = superclassMemberBase.trimmedDescription + } else { + specifiedSuperclass = nil + } } + let superclass = specifiedSuperclass ?? "JavaObject" + // Check that the class name is fully-qualified, as it should be. let className = classNameSegment.content.text if className.firstIndex(of: ".") == nil { throw MacroErrors.classNameNotFullyQualified(className) } - let fullJavaClassNameMember: DeclSyntax = """ + var members: [DeclSyntax] = [] + + // Determine the modifiers to use for the fullJavaClassName member. + let fullJavaClassNameMemberModifiers: String + switch (isSwiftClass, isJavaLangObject) { + case (false, _): + fullJavaClassNameMemberModifiers = "static" + case (true, false): + fullJavaClassNameMemberModifiers = "override class" + case (true, true): + fullJavaClassNameMemberModifiers = "class" + } + + members.append(""" /// The full Java class name for this Swift type. - public static var fullJavaClassName: String { \(literal: className) } + public \(raw: fullJavaClassNameMemberModifiers) var fullJavaClassName: String { \(literal: className) } """ + ) - let superclassTypealias: DeclSyntax = """ - public typealias JavaSuperclass = \(raw: superclass) - """ + // struct wrappers need a JavaSuperclass type. + if !isSwiftClass { + members.append(""" + public typealias JavaSuperclass = \(raw: superclass) + """ + ) + } - let javaHolderMember: DeclSyntax = """ - public var javaHolder: JavaObjectHolder - """ + // If this is for a struct or is the root java.lang.Object class, we need + // a javaHolder instance property. + if !isSwiftClass || isJavaLangObject { + members.append(""" + public var javaHolder: JavaObjectHolder + """ + ) + } - let initMember: DeclSyntax = """ - public init(javaHolder: JavaObjectHolder) { - self.javaHolder = javaHolder + let requiredModifierOpt = isSwiftClass ? "required " : "" + let initBody: CodeBlockItemSyntax = isSwiftClass && !isJavaLangObject + ? "super.init(javaHolder: javaHolder)" + : "self.javaHolder = javaHolder" + members.append(""" + public \(raw: requiredModifierOpt)init(javaHolder: JavaObjectHolder) { + \(initBody) } """ + ) - let nonOptionalAs: DeclSyntax = """ - /// Casting to ``\(raw: superclass)`` will never be nil because ``\(raw: className.split(separator: ".").last!)`` extends it. + members.append(""" + /// Casting to ``\(raw: superclass)`` will never be nil because ``\(raw: swiftName)`` extends it. public func `as`(_: \(raw: superclass).Type) -> \(raw: superclass) { return \(raw: superclass)(javaHolder: javaHolder) } """ + ) - return [ - fullJavaClassNameMember, - superclassTypealias, - javaHolderMember, - initMember, - nonOptionalAs, - ] + return members } } @@ -112,3 +157,12 @@ extension JavaClassMacro: ExtensionMacro { return [AnyJavaObjectConformance.as(ExtensionDeclSyntax.self)!] } } + +extension ClassDeclSyntax { + /// Whether this class describes java.lang.Object + var isJavaLangObject: Bool { + // FIXME: This is somewhat of a hack; we could look for + // @JavaClass("java.lang.Object") instead. + return name.text == "JavaObject" + } +} diff --git a/Sources/JavaKitMacros/SwiftJNIMacrosPlugin.swift b/Sources/JavaKitMacros/JavaKitMacrosPlugin.swift similarity index 100% rename from Sources/JavaKitMacros/SwiftJNIMacrosPlugin.swift rename to Sources/JavaKitMacros/JavaKitMacrosPlugin.swift diff --git a/Sources/JavaKitMacros/JavaMethodMacro.swift b/Sources/JavaKitMacros/JavaMethodMacro.swift index 0a4be1f0..db8a3b36 100644 --- a/Sources/JavaKitMacros/JavaMethodMacro.swift +++ b/Sources/JavaKitMacros/JavaMethodMacro.swift @@ -108,6 +108,17 @@ extension JavaMethodMacro: BodyMacro { initDecl.signature.effectSpecifiers?.throwsClause != nil ? "try" : "try!" + let objectCreation: [CodeBlockItemSyntax] + if context.lexicalContext.first?.is(ClassDeclSyntax.self) ?? false { + objectCreation = [ + "let javaThis = \(raw: tryKeyword) Self.dynamicJavaNewObjectInstance(in: _environment\(raw: arguments))\n", + "self.init(javaThis: javaThis, environment: _environment)\n", + ] + } else { + objectCreation = [ + "self = \(raw: tryKeyword) Self.dynamicJavaNewObject(in: _environment\(raw: arguments))\n" + ] + } return [ """ let _environment = if let environment { @@ -115,9 +126,8 @@ extension JavaMethodMacro: BodyMacro { } else { \(raw: tryKeyword) JavaVirtualMachine.shared().environment() } - self = \(raw: tryKeyword) Self.dynamicJavaNewObject(in: _environment\(raw: arguments)) """ - ] + ] + objectCreation } } diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 80c86fc0..2d358104 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -266,6 +266,33 @@ class Java2SwiftTests: XCTestCase { ] ) } + + func testJavaLangObjectMappingAsClass() throws { + try assertTranslatedClass( + JavaObject.self, + swiftTypeName: "JavaObject", + asClass: true, + expectedChunks: [ + "import JavaKit", + """ + @JavaClass("java.lang.Object") + open class JavaObject { + """, + """ + @JavaMethod + public init(environment: JNIEnvironment? = nil) + """, + """ + @JavaMethod + open func toString() -> String + """, + """ + @JavaMethod + open func wait() throws + """ + ] + ) + } } @JavaClass("java.util.ArrayList") @@ -295,6 +322,7 @@ public struct MyJavaIntFunction { func assertTranslatedClass( _ javaType: JavaClassType.Type, swiftTypeName: String, + asClass: Bool = false, translatedClasses: [ String: (swiftType: String, swiftModule: String?) ] = [:], @@ -306,7 +334,8 @@ func assertTranslatedClass( let environment = try jvm.environment() let translator = JavaTranslator( swiftModuleName: "SwiftModule", - environment: environment + environment: environment, + translateAsClass: asClass ) translator.translatedClasses = translatedClasses diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift index e83356c0..5a85c729 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift @@ -115,5 +115,141 @@ class JavaKitMacroTests: XCTestCase { macros: Self.javaKitMacros ) } + + func testJavaClassAsClass() throws { + assertMacroExpansion(""" + @JavaClass("org.swift.example.HelloWorld") + public class HelloWorld: OtherJavaType { + @JavaMethod + public init(environment: JNIEnvironment? = nil) + + @JavaMethod + public init(_ value: Int32, environment: JNIEnvironment? = nil) + + @JavaMethod + public func isBigEnough(_: Int32) -> Bool + + @JavaField + public var myField: Int64 + + @JavaField + public var objectField: JavaObject! + + @JavaField(isFinal: true) + public var myFinalField: Int64 + } + """, + expandedSource: """ + + public class HelloWorld: OtherJavaType { + public init(environment: JNIEnvironment? = nil) { + let _environment = if let environment { + environment + } else { + try! JavaVirtualMachine.shared().environment() + } + let javaThis = try! Self.dynamicJavaNewObjectInstance(in: _environment) + self.init(javaThis: javaThis, environment: _environment) + } + public init(_ value: Int32, environment: JNIEnvironment? = nil) { + let _environment = if let environment { + environment + } else { + try! JavaVirtualMachine.shared().environment() + } + let javaThis = try! Self.dynamicJavaNewObjectInstance(in: _environment, arguments: value.self) + self.init(javaThis: javaThis, environment: _environment) + } + public func isBigEnough(_: Int32) -> Bool { + return try! dynamicJavaMethodCall(methodName: "isBigEnough", resultType: Bool.self) + } + public var myField: Int64 { + get { + self[javaFieldName: "myField", fieldType: Int64.self] + } + nonmutating set { + self[javaFieldName: "myField", fieldType: Int64.self] = newValue + } + } + public var objectField: JavaObject! { + get { + self[javaFieldName: "objectField", fieldType: JavaObject?.self] + } + nonmutating set { + self[javaFieldName: "objectField", fieldType: JavaObject?.self] = newValue + } + } + public var myFinalField: Int64 { + get { + self[javaFieldName: "myFinalField", fieldType: Int64.self] + } + } + + /// The full Java class name for this Swift type. + public override class var fullJavaClassName: String { + "org.swift.example.HelloWorld" + } + + public required init(javaHolder: JavaObjectHolder) { + super.init(javaHolder: javaHolder) + } + + /// Casting to ``OtherJavaType`` will never be nil because ``HelloWorld`` extends it. + public func `as`(_: OtherJavaType.Type) -> OtherJavaType { + return OtherJavaType(javaHolder: javaHolder) + } + } + """, + macros: Self.javaKitMacros + ) + } + + func testJavaObjectAsClass() throws { + assertMacroExpansion(""" + @JavaClass("java.lang.Object") + public class JavaObject { + @JavaMethod + public init(environment: JNIEnvironment? = nil) + + @JavaMethod + public func isBigEnough(_: Int32) -> Bool + } + """, + expandedSource: """ + + public class JavaObject { + public init(environment: JNIEnvironment? = nil) { + let _environment = if let environment { + environment + } else { + try! JavaVirtualMachine.shared().environment() + } + let javaThis = try! Self.dynamicJavaNewObjectInstance(in: _environment) + self.init(javaThis: javaThis, environment: _environment) + } + public func isBigEnough(_: Int32) -> Bool { + return try! dynamicJavaMethodCall(methodName: "isBigEnough", resultType: Bool.self) + } + + /// The full Java class name for this Swift type. + public class var fullJavaClassName: String { + "java.lang.Object" + } + + public var javaHolder: JavaObjectHolder + + public required init(javaHolder: JavaObjectHolder) { + self.javaHolder = javaHolder + } + + /// Casting to ``JavaObject`` will never be nil because ``JavaObject`` extends it. + public func `as`(_: JavaObject.Type) -> JavaObject { + return JavaObject(javaHolder: javaHolder) + } + } + """, + macros: Self.javaKitMacros + ) + } } From 6440a3c29d302e9a9f0dffa432e27f62b8854843 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 15:55:42 -0700 Subject: [PATCH 02/17] Java2Swift: Mark instance methods as "override" when needed --- .../Java2SwiftLib/JavaClassTranslator.swift | 40 +++++++++++++++++-- .../JavaClass+Reflection.swift | 8 +++- Tests/Java2SwiftTests/Java2SwiftTests.swift | 34 ++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 3e12c76b..332c2ed4 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -296,8 +296,18 @@ extension JavaClassTranslator { // Collect all of the members of this type. let members = properties + enumDecls + initializers + instanceMethods - // Compute the "extends" clause for the superclass. - let extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + // Compute the "extends" clause for the superclass (of the struct + // formulation) or the inheritance clause (for the class + // formulation). + let extends: String + let inheritanceClause: String + if translator.translateAsClass { + extends = "" + inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? "" + } else { + extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + inheritanceClause = "" + } // Compute the string to capture all of the interfaces. let interfacesStr: String @@ -314,7 +324,7 @@ extension JavaClassTranslator { var classDecl: DeclSyntax = """ @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) - \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause) { + \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } """ @@ -499,8 +509,12 @@ extension JavaClassTranslator { : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; let accessModifier = implementedInSwift ? "" : "\(translator.defaultAccessSpecifier) " + let overrideOpt = (translator.translateAsClass && + !javaMethod.isStatic && isOverride(javaMethod)) + ? "override " + : "" return """ - \(methodAttribute)\(raw: accessModifier)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } @@ -626,3 +640,21 @@ struct MethodCollector { methods.append(method) } } + +// MARK: Utility functions +extension JavaClassTranslator { + /// Determine whether this method is an override of another Java + /// method. + func isOverride(_ method: Method) -> Bool { + guard let javaSuperclass = javaClass.getSuperclass() else { + return false + } + + do { + let overriddenMethod = try javaSuperclass.getDeclaredMethod(method.getName(), method.getParameterTypes()) + return overriddenMethod != nil + } catch { + return false + } + } +} diff --git a/Sources/JavaKitReflection/JavaClass+Reflection.swift b/Sources/JavaKitReflection/JavaClass+Reflection.swift index 569b72e8..e5505144 100644 --- a/Sources/JavaKitReflection/JavaClass+Reflection.swift +++ b/Sources/JavaKitReflection/JavaClass+Reflection.swift @@ -22,7 +22,13 @@ extension JavaClass { @JavaMethod public func getMethods() -> [Method?] - + + @JavaMethod + public func getDeclaredMethod( + _ name: String, + _ parameterTypes: [JavaClass?] + ) throws -> Method? + @JavaMethod public func getFields() -> [Field?] diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 2d358104..a58ec289 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -293,6 +293,40 @@ class Java2SwiftTests: XCTestCase { ] ) } + + func testJavaLangStringMappingAsClass() throws { + try assertTranslatedClass( + JavaString.self, + swiftTypeName: "JavaString", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + ], + expectedChunks: [ + "import JavaKit", + """ + @JavaClass("java.lang.String") + open class JavaString: JavaObject { + """, + """ + @JavaMethod + public init(environment: JNIEnvironment? = nil) + """, + """ + @JavaMethod + open override func toString() -> String + """, + """ + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool + """, + """ + @JavaMethod + open func intern() -> String + """ + ] + ) + } } @JavaClass("java.util.ArrayList") From 83b0e1a1a789c3bf760f19e6b93f7f15988812bd Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 16:19:09 -0700 Subject: [PATCH 03/17] Java2Swift: Always use the nearest imported superclass for the superclass When we import a class from Java into Swift, we check whether its superclass was also imported before generating a reference to that superclass. If it isn't there, we fell back to JavaObject. That's too conservative, because there might be a superclass in between that we could use. Instead, walk up the superclass chain until we find the most-specific superclass that *is* mapped into Swift, and use that as the generated superclass. Additionally, use this as the basis for determining when we need the "override" keyword when in the class-generating mode. --- .../Java2SwiftLib/JavaClassTranslator.swift | 28 ++++++-- Tests/Java2SwiftTests/Java2SwiftTests.swift | 71 +++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 332c2ed4..a86fa1f2 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -37,6 +37,10 @@ struct JavaClassTranslator { /// class. let swiftTypeName: String + /// The effective Java superclass object, which is the nearest + /// superclass that has been mapped into Swift. + let effectiveJavaSuperclass: JavaClass? + /// The Swift name of the superclass. let swiftSuperclass: String? @@ -114,14 +118,24 @@ struct JavaClassTranslator { self.nestedClasses = translator.nestedClasses[fullName] ?? [] // Superclass. - if !javaClass.isInterface(), let javaSuperclass = javaClass.getSuperclass() { - do { - self.swiftSuperclass = try translator.getSwiftTypeName(javaSuperclass, preferValueTypes: false).swiftName - } catch { - translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") - self.swiftSuperclass = nil + if !javaClass.isInterface() { + var javaSuperclass = javaClass.getSuperclass() + var swiftSuperclass: String? = nil + while let javaSuperclassNonOpt = javaSuperclass { + do { + swiftSuperclass = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + break + } catch { + translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") + } + + javaSuperclass = javaSuperclassNonOpt.getSuperclass() } + + self.effectiveJavaSuperclass = javaSuperclass + self.swiftSuperclass = swiftSuperclass } else { + self.effectiveJavaSuperclass = nil self.swiftSuperclass = nil } @@ -646,7 +660,7 @@ extension JavaClassTranslator { /// Determine whether this method is an override of another Java /// method. func isOverride(_ method: Method) -> Bool { - guard let javaSuperclass = javaClass.getSuperclass() else { + guard let javaSuperclass = effectiveJavaSuperclass else { return false } diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index a58ec289..a8007fe7 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -327,8 +327,79 @@ class Java2SwiftTests: XCTestCase { ] ) } + + func testURLLoaderSkipMappingAsClass() throws { + // URLClassLoader actually inherits from SecureClassLoader. However, + // that type wasn't mapped into Swift, so we find the nearest + // superclass that was mapped into Swift. + try assertTranslatedClass( + URLClassLoader.self, + swiftTypeName: "URLClassLoader", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.ClassLoader" : ("ClassLoader", "JavaKit"), + "java.net.URL" : ("URL", "JavaKitNetwork"), + ], + expectedChunks: [ + "import JavaKit", + """ + @JavaClass("java.net.URLClassLoader") + open class URLClassLoader: ClassLoader { + """, + """ + @JavaMethod + open func close() throws + """, + """ + @JavaMethod + open override func findResource(_ arg0: String) -> URL! + """, + ] + ) + } + + func testURLLoaderSkipTwiceMappingAsClass() throws { + // URLClassLoader actually inherits from SecureClassLoader. However, + // that type wasn't mapped into Swift here, nor is ClassLoader, + // so we fall back to JavaObject. + try assertTranslatedClass( + URLClassLoader.self, + swiftTypeName: "URLClassLoader", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.net.URL" : ("URL", "JavaKitNetwork"), + ], + expectedChunks: [ + "import JavaKit", + """ + @JavaClass("java.net.URLClassLoader") + open class URLClassLoader: JavaObject { + """, + """ + @JavaMethod + open func close() throws + """, + """ + @JavaMethod + open func findResource(_ arg0: String) -> URL! + """, + ] + ) + } } +@JavaClass("java.lang.ClassLoader") +public struct ClassLoader { } + +@JavaClass("java.security.SecureClassLoader") +public struct SecureClassLoader { } + +@JavaClass("java.net.URLClassLoader") +public struct URLClassLoader { } + + @JavaClass("java.util.ArrayList") public struct MyArrayList { } From 819dd275c0d80ebc51fe8a8e12fcae24796a5a85 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 16:25:30 -0700 Subject: [PATCH 04/17] Java2Swift: Regenerate generated mappings No functionality changes here; we're just more consistent about putting JavaObject in as the extended type. --- Sources/JavaKit/generated/JavaCharacter.swift | 4 ++-- .../JavaKitCollection/generated/ArrayDeque.swift | 2 +- .../JavaKitCollection/generated/ArrayList.swift | 2 +- Sources/JavaKitCollection/generated/HashMap.swift | 2 +- Sources/JavaKitCollection/generated/HashSet.swift | 2 +- .../generated/PriorityQueue.swift | 2 +- Sources/JavaKitCollection/generated/Stack.swift | 2 +- Sources/JavaKitCollection/generated/TreeMap.swift | 2 +- Sources/JavaKitCollection/generated/TreeSet.swift | 2 +- Sources/JavaKitJar/generated/JarEntry.swift | 2 +- Sources/JavaKitJar/generated/JarFile.swift | 2 +- Sources/JavaKitJar/generated/JarInputStream.swift | 14 +++++++------- Sources/JavaKitJar/generated/JarOutputStream.swift | 8 ++++---- .../JavaKitNetwork/generated/URLClassLoader.swift | 2 +- 14 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Sources/JavaKit/generated/JavaCharacter.swift b/Sources/JavaKit/generated/JavaCharacter.swift index 1ebb7625..47ed2744 100644 --- a/Sources/JavaKit/generated/JavaCharacter.swift +++ b/Sources/JavaKit/generated/JavaCharacter.swift @@ -1102,7 +1102,7 @@ extension JavaClass { public func of(_ arg0: UInt16) -> JavaCharacter.UnicodeBlock! } extension JavaCharacter { - @JavaClass("java.lang.Character$UnicodeScript") + @JavaClass("java.lang.Character$UnicodeScript", extends: JavaObject.self) public struct UnicodeScript { public enum UnicodeScriptCases: Equatable { case COMMON @@ -2639,7 +2639,7 @@ extension JavaCharacter { } } extension JavaCharacter.UnicodeScript { - @JavaClass("java.lang.Enum$EnumDesc") + @JavaClass("java.lang.Enum$EnumDesc", extends: JavaObject.self) public struct EnumDesc { @JavaMethod public func toString() -> String diff --git a/Sources/JavaKitCollection/generated/ArrayDeque.swift b/Sources/JavaKitCollection/generated/ArrayDeque.swift index 9c7af8bf..d0a1134a 100644 --- a/Sources/JavaKitCollection/generated/ArrayDeque.swift +++ b/Sources/JavaKitCollection/generated/ArrayDeque.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.ArrayDeque") +@JavaClass("java.util.ArrayDeque", extends: JavaObject.self) public struct ArrayDeque { @JavaMethod public init(_ arg0: Int32, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/ArrayList.swift b/Sources/JavaKitCollection/generated/ArrayList.swift index 0ff6a923..7074b693 100644 --- a/Sources/JavaKitCollection/generated/ArrayList.swift +++ b/Sources/JavaKitCollection/generated/ArrayList.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.ArrayList", implements: List.self, RandomAccess.self) +@JavaClass("java.util.ArrayList", extends: JavaObject.self, implements: List.self, RandomAccess.self) public struct ArrayList { @JavaMethod public init(_ arg0: JavaCollection?, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/HashMap.swift b/Sources/JavaKitCollection/generated/HashMap.swift index 287593f5..9f800c63 100644 --- a/Sources/JavaKitCollection/generated/HashMap.swift +++ b/Sources/JavaKitCollection/generated/HashMap.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.HashMap") +@JavaClass("java.util.HashMap", extends: JavaObject.self) public struct HashMap { @JavaMethod public init(_ arg0: Int32, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/HashSet.swift b/Sources/JavaKitCollection/generated/HashSet.swift index 9ec5b088..ab2da187 100644 --- a/Sources/JavaKitCollection/generated/HashSet.swift +++ b/Sources/JavaKitCollection/generated/HashSet.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.HashSet", implements: JavaSet.self) +@JavaClass("java.util.HashSet", extends: JavaObject.self, implements: JavaSet.self) public struct HashSet { @JavaMethod public init(_ arg0: Int32, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/PriorityQueue.swift b/Sources/JavaKitCollection/generated/PriorityQueue.swift index c3df172d..5704db8a 100644 --- a/Sources/JavaKitCollection/generated/PriorityQueue.swift +++ b/Sources/JavaKitCollection/generated/PriorityQueue.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.PriorityQueue") +@JavaClass("java.util.PriorityQueue", extends: JavaObject.self) public struct PriorityQueue { @JavaMethod public init(_ arg0: PriorityQueue?, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/Stack.swift b/Sources/JavaKitCollection/generated/Stack.swift index a9d0aa5a..e79e7e07 100644 --- a/Sources/JavaKitCollection/generated/Stack.swift +++ b/Sources/JavaKitCollection/generated/Stack.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.Stack") +@JavaClass("java.util.Stack", extends: JavaObject.self) public struct Stack { @JavaMethod public init(environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/TreeMap.swift b/Sources/JavaKitCollection/generated/TreeMap.swift index 706d5339..3d7d858a 100644 --- a/Sources/JavaKitCollection/generated/TreeMap.swift +++ b/Sources/JavaKitCollection/generated/TreeMap.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.TreeMap") +@JavaClass("java.util.TreeMap", extends: JavaObject.self) public struct TreeMap { @JavaMethod public init(environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitCollection/generated/TreeSet.swift b/Sources/JavaKitCollection/generated/TreeSet.swift index 985d6837..e78d6ada 100644 --- a/Sources/JavaKitCollection/generated/TreeSet.swift +++ b/Sources/JavaKitCollection/generated/TreeSet.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.TreeSet") +@JavaClass("java.util.TreeSet", extends: JavaObject.self) public struct TreeSet { @JavaMethod public init(_ arg0: JavaCollection?, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitJar/generated/JarEntry.swift b/Sources/JavaKitJar/generated/JarEntry.swift index dd0e30ab..40bbef54 100644 --- a/Sources/JavaKitJar/generated/JarEntry.swift +++ b/Sources/JavaKitJar/generated/JarEntry.swift @@ -2,7 +2,7 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.jar.JarEntry") +@JavaClass("java.util.jar.JarEntry", extends: JavaObject.self) public struct JarEntry { @JavaMethod public init(_ arg0: JarEntry?, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitJar/generated/JarFile.swift b/Sources/JavaKitJar/generated/JarFile.swift index 9da57d98..4c215e05 100644 --- a/Sources/JavaKitJar/generated/JarFile.swift +++ b/Sources/JavaKitJar/generated/JarFile.swift @@ -3,7 +3,7 @@ import JavaKit import JavaKitCollection import JavaRuntime -@JavaClass("java.util.jar.JarFile") +@JavaClass("java.util.jar.JarFile", extends: JavaObject.self) public struct JarFile { @JavaMethod public init(_ arg0: String, _ arg1: Bool, environment: JNIEnvironment? = nil) throws diff --git a/Sources/JavaKitJar/generated/JarInputStream.swift b/Sources/JavaKitJar/generated/JarInputStream.swift index de063bb9..c8a25439 100644 --- a/Sources/JavaKitJar/generated/JarInputStream.swift +++ b/Sources/JavaKitJar/generated/JarInputStream.swift @@ -2,8 +2,11 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.jar.JarInputStream") +@JavaClass("java.util.jar.JarInputStream", extends: JavaObject.self) public struct JarInputStream { + @JavaMethod + public func getNextJarEntry() throws -> JarEntry! + @JavaMethod public func read(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32 @@ -11,7 +14,7 @@ public struct JarInputStream { public func getManifest() -> Manifest! @JavaMethod - public func getNextJarEntry() throws -> JarEntry! + public func closeEntry() throws @JavaMethod public func read() throws -> Int32 @@ -23,10 +26,10 @@ public struct JarInputStream { public func readAllBytes() throws -> [Int8] @JavaMethod - public func readNBytes(_ arg0: Int32) throws -> [Int8] + public func readNBytes(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32 @JavaMethod - public func readNBytes(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32 + public func readNBytes(_ arg0: Int32) throws -> [Int8] @JavaMethod public func skip(_ arg0: Int64) throws -> Int64 @@ -37,9 +40,6 @@ public struct JarInputStream { @JavaMethod public func skipNBytes(_ arg0: Int64) throws - @JavaMethod - public func closeEntry() throws - @JavaMethod public func reset() throws diff --git a/Sources/JavaKitJar/generated/JarOutputStream.swift b/Sources/JavaKitJar/generated/JarOutputStream.swift index 542a784f..b10dc4b5 100644 --- a/Sources/JavaKitJar/generated/JarOutputStream.swift +++ b/Sources/JavaKitJar/generated/JarOutputStream.swift @@ -2,8 +2,11 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.jar.JarOutputStream") +@JavaClass("java.util.jar.JarOutputStream", extends: JavaObject.self) public struct JarOutputStream { + @JavaMethod + public func closeEntry() throws + @JavaMethod public func write(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws @@ -22,9 +25,6 @@ public struct JarOutputStream { @JavaMethod public func setLevel(_ arg0: Int32) - @JavaMethod - public func closeEntry() throws - @JavaMethod public func flush() throws diff --git a/Sources/JavaKitNetwork/generated/URLClassLoader.swift b/Sources/JavaKitNetwork/generated/URLClassLoader.swift index 64b26dda..b8e1f54f 100644 --- a/Sources/JavaKitNetwork/generated/URLClassLoader.swift +++ b/Sources/JavaKitNetwork/generated/URLClassLoader.swift @@ -3,7 +3,7 @@ import JavaKit import JavaKitCollection import JavaRuntime -@JavaClass("java.net.URLClassLoader") +@JavaClass("java.net.URLClassLoader", extends: JavaObject.self) public struct URLClassLoader { @JavaMethod public init(_ arg0: [URL?], environment: JNIEnvironment? = nil) From 2a7d841b92003603d2694ea4e6a55da3ab459f70 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 16:46:04 -0700 Subject: [PATCH 05/17] Java2Swift: Drop the "as(superclass)" function when generating classes We don't need this function when generating classes, because we'll already get the subtype conversion for free. --- Sources/JavaKitMacros/JavaClassMacro.swift | 16 +++++++++------- .../JavaKitMacroTests/JavaClassMacroTests.swift | 10 ---------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/JavaKitMacros/JavaClassMacro.swift index f88d12e4..eb9417aa 100644 --- a/Sources/JavaKitMacros/JavaClassMacro.swift +++ b/Sources/JavaKitMacros/JavaClassMacro.swift @@ -125,13 +125,15 @@ extension JavaClassMacro: MemberMacro { """ ) - members.append(""" - /// Casting to ``\(raw: superclass)`` will never be nil because ``\(raw: swiftName)`` extends it. - public func `as`(_: \(raw: superclass).Type) -> \(raw: superclass) { - return \(raw: superclass)(javaHolder: javaHolder) - } - """ - ) + if !isSwiftClass { + members.append(""" + /// Casting to ``\(raw: superclass)`` will never be nil because ``\(raw: swiftName)`` extends it. + public func `as`(_: \(raw: superclass).Type) -> \(raw: superclass) { + return \(raw: superclass)(javaHolder: javaHolder) + } + """ + ) + } return members } diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift index 5a85c729..0d6ac9be 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift @@ -193,11 +193,6 @@ class JavaKitMacroTests: XCTestCase { public required init(javaHolder: JavaObjectHolder) { super.init(javaHolder: javaHolder) } - - /// Casting to ``OtherJavaType`` will never be nil because ``HelloWorld`` extends it. - public func `as`(_: OtherJavaType.Type) -> OtherJavaType { - return OtherJavaType(javaHolder: javaHolder) - } } """, macros: Self.javaKitMacros @@ -241,11 +236,6 @@ class JavaKitMacroTests: XCTestCase { public required init(javaHolder: JavaObjectHolder) { self.javaHolder = javaHolder } - - /// Casting to ``JavaObject`` will never be nil because ``JavaObject`` extends it. - public func `as`(_: JavaObject.Type) -> JavaObject { - return JavaObject(javaHolder: javaHolder) - } } """, macros: Self.javaKitMacros From 4d833cf25f4132ac2f78eb43d6380d7af1dfe470 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 16:53:54 -0700 Subject: [PATCH 06/17] Java2Swift: all initializers in "class mode" are convenience inits --- Sources/Java2SwiftLib/JavaClassTranslator.swift | 3 ++- Tests/Java2SwiftTests/Java2SwiftTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index a86fa1f2..b05f67e6 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -485,9 +485,10 @@ extension JavaClassTranslator { let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" + let convenienceModifier = translator.translateAsClass ? "convenience " : "" return """ @JavaMethod - \(raw: accessModifier)init(\(raw: parametersStr))\(raw: throwsStr) + \(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index a8007fe7..be93610c 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -280,7 +280,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public init(environment: JNIEnvironment? = nil) + public convenience init(environment: JNIEnvironment? = nil) """, """ @JavaMethod @@ -310,7 +310,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public init(environment: JNIEnvironment? = nil) + public convenience init(environment: JNIEnvironment? = nil) """, """ @JavaMethod From 1baa0957b612cb049090c67fc8c954d1e30adb69 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 21:48:42 -0700 Subject: [PATCH 07/17] Java2Swift: Fix enum initializer for class-based generation. --- .../Java2SwiftLib/JavaClassTranslator.swift | 7 +++-- Tests/Java2SwiftTests/Java2SwiftTests.swift | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index b05f67e6..3e26bd9f 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -576,8 +576,9 @@ extension JavaClassTranslator { } """ + let convenienceModifier = translator.translateAsClass ? "convenience " : "" let initSyntax: DeclSyntax = """ - public init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) { + public \(raw: convenienceModifier)init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) { let _environment = if let environment { environment } else { @@ -589,7 +590,9 @@ extension JavaClassTranslator { return """ case .\($0.getName()): if let \($0.getName()) = classObj.\($0.getName()) { - self = \($0.getName()) + \(translator.translateAsClass + ? "self.init(javaHolder: \($0.getName()).javaHolder)" + : "self = \($0.getName())") } else { fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") } diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index be93610c..abed40a3 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -328,6 +328,37 @@ class Java2SwiftTests: XCTestCase { ) } + func testEnumAsClass() throws { + try assertTranslatedClass( + JavaMonth.self, + swiftTypeName: "Month", + asClass: true, + expectedChunks: [ + "import JavaKit", + "enum MonthCases: Equatable", + "case APRIL", + "public var enumValue: MonthCases!", + """ + } else if self.equals(classObj.APRIL?.as(JavaObject.self)) { + return MonthCases.APRIL + } + """, + "public convenience init(_ enumValue: MonthCases, environment: JNIEnvironment? = nil) {", + """ + case .APRIL: + if let APRIL = classObj.APRIL { + self.init(javaHolder: APRIL.javaHolder) + } else { + fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + } + """, + """ + @JavaStaticField(isFinal: true) + public var APRIL: Month! + """ + ]) + } + func testURLLoaderSkipMappingAsClass() throws { // URLClassLoader actually inherits from SecureClassLoader. However, // that type wasn't mapped into Swift, so we find the nearest From 1715af834712a83eaa7488a0b4e44e890dbe88cb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:08:23 -0700 Subject: [PATCH 08/17] Java2Swift: Don't use Self in an invariant position in enum convenience init --- Sources/Java2SwiftLib/JavaClassTranslator.swift | 2 +- Tests/Java2SwiftTests/Java2SwiftTests.swift | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 3e26bd9f..78851376 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -584,7 +584,7 @@ extension JavaClassTranslator { } else { try! JavaVirtualMachine.shared().environment() } - let classObj = try! JavaClass(environment: _environment) + let classObj = try! JavaClass<\(raw: swiftInnermostTypeName)>(environment: _environment) switch enumValue { \(raw: enumConstants.map { return """ diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index abed40a3..0222cf86 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -344,6 +344,9 @@ class Java2SwiftTests: XCTestCase { } """, "public convenience init(_ enumValue: MonthCases, environment: JNIEnvironment? = nil) {", + """ + let classObj = try! JavaClass(environment: _environment) + """, """ case .APRIL: if let APRIL = classObj.APRIL { From b55cd4d8f672761f6db3b2678f81a20065e3fa72 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:11:32 -0700 Subject: [PATCH 09/17] Java2Swift: fullJavaClassName needs to be "open class" in class-generation mode --- Sources/JavaKitMacros/JavaClassMacro.swift | 3 ++- Tests/JavaKitMacroTests/JavaClassMacroTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/JavaKitMacros/JavaClassMacro.swift index eb9417aa..9a2dd4a5 100644 --- a/Sources/JavaKitMacros/JavaClassMacro.swift +++ b/Sources/JavaKitMacros/JavaClassMacro.swift @@ -91,9 +91,10 @@ extension JavaClassMacro: MemberMacro { fullJavaClassNameMemberModifiers = "class" } + let classNameAccessSpecifier = isSwiftClass ? "open" : "public" members.append(""" /// The full Java class name for this Swift type. - public \(raw: fullJavaClassNameMemberModifiers) var fullJavaClassName: String { \(literal: className) } + \(raw: classNameAccessSpecifier) \(raw: fullJavaClassNameMemberModifiers) var fullJavaClassName: String { \(literal: className) } """ ) diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift index 0d6ac9be..9a16d773 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift @@ -186,7 +186,7 @@ class JavaKitMacroTests: XCTestCase { } /// The full Java class name for this Swift type. - public override class var fullJavaClassName: String { + open override class var fullJavaClassName: String { "org.swift.example.HelloWorld" } @@ -227,7 +227,7 @@ class JavaKitMacroTests: XCTestCase { } /// The full Java class name for this Swift type. - public class var fullJavaClassName: String { + open class var fullJavaClassName: String { "java.lang.Object" } From 45d195cba6ef5b52e646a636434245cd656c9d61 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:22:29 -0700 Subject: [PATCH 10/17] Java2Swift: Correctly determine if a Swift method will be an override. When looking for a declared method with a given signature, we need to check all superclasses that have been mapped into Swift, not just the closest one. --- .../Java2SwiftLib/JavaClassTranslator.swift | 31 ++++++++++++++----- Tests/Java2SwiftTests/Java2SwiftTests.swift | 26 ++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 78851376..f6a1b6eb 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -664,15 +664,30 @@ extension JavaClassTranslator { /// Determine whether this method is an override of another Java /// method. func isOverride(_ method: Method) -> Bool { - guard let javaSuperclass = effectiveJavaSuperclass else { - return false - } + var currentSuperclass = effectiveJavaSuperclass + while let currentSuperclassNonOpt = currentSuperclass { + // Set the loop up for the next run. + defer { + currentSuperclass = currentSuperclassNonOpt.getSuperclass() + } + + do { + // If this class didn't get translated into Swift, skip it. + if translator.translatedClasses[currentSuperclassNonOpt.getName()] == nil { + continue + } - do { - let overriddenMethod = try javaSuperclass.getDeclaredMethod(method.getName(), method.getParameterTypes()) - return overriddenMethod != nil - } catch { - return false + // If this superclass declares a method with the same parameter types, + // we have an override. + let overriddenMethod = try currentSuperclassNonOpt + .getDeclaredMethod(method.getName(), method.getParameterTypes()) + if overriddenMethod != nil { + return true + } + } catch { + } } + + return false } } diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 0222cf86..6b4f9e7d 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -422,6 +422,32 @@ class Java2SwiftTests: XCTestCase { ] ) } + + func testOverrideSkipImmediateSuperclass() throws { + // JavaByte overrides equals() from JavaObject, which it indirectly + // inherits through JavaNumber + try assertTranslatedClass( + JavaByte.self, + swiftTypeName: "JavaByte", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Number" : ("JavaNumber", "JavaKit"), + "java.lang.Byte" : ("JavaByte", "JavaKit"), + ], + expectedChunks: [ + "import JavaKit", + """ + @JavaClass("java.lang.Byte") + open class JavaByte: JavaNumber { + """, + """ + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool + """, + ] + ) + } } @JavaClass("java.lang.ClassLoader") From 9f5fbbf1e86a7700dbcf88c45fd7e82ecd89174b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:26:55 -0700 Subject: [PATCH 11/17] Java2Swift: Java static methods need to use "public", not "open" --- Sources/Java2SwiftLib/JavaClassTranslator.swift | 3 ++- Sources/Java2SwiftLib/JavaTranslator.swift | 5 ----- Tests/Java2SwiftTests/Java2SwiftTests.swift | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index f6a1b6eb..30642678 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -523,7 +523,8 @@ extension JavaClassTranslator { ? "" : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; let accessModifier = implementedInSwift ? "" - : "\(translator.defaultAccessSpecifier) " + : (javaMethod.isStatic || !translator.translateAsClass) ? "public " + : "open " let overrideOpt = (translator.translateAsClass && !javaMethod.isStatic && isOverride(javaMethod)) ? "override " diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index c207edb3..56535514 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -250,11 +250,6 @@ extension JavaTranslator { throw TranslationError.untranslatedJavaClass(name) } - - /// The default Swift access specifier for a Java method. - var defaultAccessSpecifier: String { - translateAsClass ? "open" : "public" - } } // MARK: Class translation diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 6b4f9e7d..224c29ac 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -323,7 +323,11 @@ class Java2SwiftTests: XCTestCase { """ @JavaMethod open func intern() -> String + """, """ + @JavaStaticMethod + public func valueOf(_ arg0: Int64) -> String + """, ] ) } From ea6cfaaf6201a3c1dddf2d3fafb3efe304442aed Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:30:30 -0700 Subject: [PATCH 12/17] Java2Swift: Include protected methods in generated classes Swift doesn't have the notion of "protected", so treat these as "open" (or "public") as approriate. --- Sources/Java2SwiftLib/JavaClassTranslator.swift | 4 ++-- Sources/JavaKitReflection/Method+Utilities.swift | 5 +++++ Tests/Java2SwiftTests/Java2SwiftTests.swift | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 30642678..0319b8f9 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -181,8 +181,8 @@ struct JavaClassTranslator { for method in methods { guard let method else { continue } - // Only look at public methods here. - guard method.isPublic else { continue } + // Only look at public and protected methods here. + guard method.isPublic || method.isProtected else { continue } // Skip any methods that are expected to be implemented in Swift. We will // visit them in the second pass, over the *declared* methods, because diff --git a/Sources/JavaKitReflection/Method+Utilities.swift b/Sources/JavaKitReflection/Method+Utilities.swift index 1b632337..6c8e4a66 100644 --- a/Sources/JavaKitReflection/Method+Utilities.swift +++ b/Sources/JavaKitReflection/Method+Utilities.swift @@ -18,6 +18,11 @@ extension Method { return (getModifiers() & 1) != 0 } + /// Whether this is a 'protected' method. + public var isProtected: Bool { + return (getModifiers() & 4) != 0 + } + /// Whether this is a 'static' method. public var isStatic: Bool { return (getModifiers() & 0x08) != 0 diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 224c29ac..3413ee8a 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -289,7 +289,11 @@ class Java2SwiftTests: XCTestCase { """ @JavaMethod open func wait() throws + """, """ + @JavaMethod + open func clone() throws -> JavaObject! + """, ] ) } From c3337b664d26f21036a518ab4da56b7be610b09c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:40:19 -0700 Subject: [PATCH 13/17] Import java.util.zip.ZipEntry and regenerate JavaKit sources --- Sources/JavaKit/generated/JavaCharacter.swift | 2 +- .../JavaKitCollection/generated/HashMap.swift | 86 +++++++ .../JavaKitCollection/generated/TreeMap.swift | 86 ------- Sources/JavaKitJar/Java2Swift.config | 3 +- Sources/JavaKitJar/generated/JarEntry.swift | 5 +- Sources/JavaKitJar/generated/JarFile.swift | 3 + .../JavaKitJar/generated/JarInputStream.swift | 3 + .../generated/JarOutputStream.swift | 3 + Sources/JavaKitJar/generated/ZipEntry.swift | 217 ++++++++++++++++++ 9 files changed, 319 insertions(+), 89 deletions(-) create mode 100644 Sources/JavaKitJar/generated/ZipEntry.swift diff --git a/Sources/JavaKit/generated/JavaCharacter.swift b/Sources/JavaKit/generated/JavaCharacter.swift index 47ed2744..82b92a23 100644 --- a/Sources/JavaKit/generated/JavaCharacter.swift +++ b/Sources/JavaKit/generated/JavaCharacter.swift @@ -1612,7 +1612,7 @@ extension JavaCharacter { } else { try! JavaVirtualMachine.shared().environment() } - let classObj = try! JavaClass(environment: _environment) + let classObj = try! JavaClass(environment: _environment) switch enumValue { case .COMMON: if let COMMON = classObj.COMMON { diff --git a/Sources/JavaKitCollection/generated/HashMap.swift b/Sources/JavaKitCollection/generated/HashMap.swift index 9f800c63..55cea901 100644 --- a/Sources/JavaKitCollection/generated/HashMap.swift +++ b/Sources/JavaKitCollection/generated/HashMap.swift @@ -88,6 +88,92 @@ public struct HashMap { @JavaMethod public func wait() throws } +extension HashMap { + @JavaClass("java.util.AbstractMap$SimpleEntry", extends: JavaObject.self) + public struct SimpleEntry { + @JavaMethod + public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func hashCode() -> Int32 + + @JavaMethod + public func getValue() -> JavaObject! + + @JavaMethod + public func getKey() -> JavaObject! + + @JavaMethod + public func setValue(_ arg0: JavaObject?) -> JavaObject! + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func notify() + + @JavaMethod + public func notifyAll() + + @JavaMethod + public func wait(_ arg0: Int64) throws + + @JavaMethod + public func wait(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + public func wait() throws + } +} +extension HashMap { + @JavaClass("java.util.AbstractMap$SimpleImmutableEntry", extends: JavaObject.self) + public struct SimpleImmutableEntry { + @JavaMethod + public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func hashCode() -> Int32 + + @JavaMethod + public func getValue() -> JavaObject! + + @JavaMethod + public func getKey() -> JavaObject! + + @JavaMethod + public func setValue(_ arg0: JavaObject?) -> JavaObject! + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func notify() + + @JavaMethod + public func notifyAll() + + @JavaMethod + public func wait(_ arg0: Int64) throws + + @JavaMethod + public func wait(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + public func wait() throws + } +} extension JavaClass { @JavaStaticMethod public func newHashMap(_ arg0: Int32) -> HashMap! where ObjectType == HashMap diff --git a/Sources/JavaKitCollection/generated/TreeMap.swift b/Sources/JavaKitCollection/generated/TreeMap.swift index 3d7d858a..f6943b48 100644 --- a/Sources/JavaKitCollection/generated/TreeMap.swift +++ b/Sources/JavaKitCollection/generated/TreeMap.swift @@ -106,89 +106,3 @@ public struct TreeMap { @JavaMethod public func getOrDefault(_ arg0: JavaObject?, _ arg1: JavaObject?) -> JavaObject! } -extension TreeMap { - @JavaClass("java.util.AbstractMap$SimpleEntry", extends: JavaObject.self) - public struct SimpleEntry { - @JavaMethod - public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) - - @JavaMethod - public func equals(_ arg0: JavaObject?) -> Bool - - @JavaMethod - public func toString() -> String - - @JavaMethod - public func hashCode() -> Int32 - - @JavaMethod - public func getValue() -> JavaObject! - - @JavaMethod - public func getKey() -> JavaObject! - - @JavaMethod - public func setValue(_ arg0: JavaObject?) -> JavaObject! - - @JavaMethod - public func getClass() -> JavaClass! - - @JavaMethod - public func notify() - - @JavaMethod - public func notifyAll() - - @JavaMethod - public func wait(_ arg0: Int64) throws - - @JavaMethod - public func wait(_ arg0: Int64, _ arg1: Int32) throws - - @JavaMethod - public func wait() throws - } -} -extension TreeMap { - @JavaClass("java.util.AbstractMap$SimpleImmutableEntry", extends: JavaObject.self) - public struct SimpleImmutableEntry { - @JavaMethod - public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) - - @JavaMethod - public func equals(_ arg0: JavaObject?) -> Bool - - @JavaMethod - public func toString() -> String - - @JavaMethod - public func hashCode() -> Int32 - - @JavaMethod - public func getValue() -> JavaObject! - - @JavaMethod - public func getKey() -> JavaObject! - - @JavaMethod - public func setValue(_ arg0: JavaObject?) -> JavaObject! - - @JavaMethod - public func getClass() -> JavaClass! - - @JavaMethod - public func notify() - - @JavaMethod - public func notifyAll() - - @JavaMethod - public func wait(_ arg0: Int64) throws - - @JavaMethod - public func wait(_ arg0: Int64, _ arg1: Int32) throws - - @JavaMethod - public func wait() throws - } -} diff --git a/Sources/JavaKitJar/Java2Swift.config b/Sources/JavaKitJar/Java2Swift.config index 011b169f..caf7a6f9 100644 --- a/Sources/JavaKitJar/Java2Swift.config +++ b/Sources/JavaKitJar/Java2Swift.config @@ -5,6 +5,7 @@ "java.util.jar.JarFile" : "JarFile", "java.util.jar.JarInputStream" : "JarInputStream", "java.util.jar.JarOutputStream" : "JarOutputStream", - "java.util.jar.Manifest" : "Manifest" + "java.util.jar.Manifest" : "Manifest", + "java.util.zip.ZipEntry" : "ZipEntry" } } diff --git a/Sources/JavaKitJar/generated/JarEntry.swift b/Sources/JavaKitJar/generated/JarEntry.swift index 40bbef54..85f5afe7 100644 --- a/Sources/JavaKitJar/generated/JarEntry.swift +++ b/Sources/JavaKitJar/generated/JarEntry.swift @@ -2,11 +2,14 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.jar.JarEntry", extends: JavaObject.self) +@JavaClass("java.util.jar.JarEntry", extends: ZipEntry.self) public struct JarEntry { @JavaMethod public init(_ arg0: JarEntry?, environment: JNIEnvironment? = nil) + @JavaMethod + public init(_ arg0: ZipEntry?, environment: JNIEnvironment? = nil) + @JavaMethod public init(_ arg0: String, environment: JNIEnvironment? = nil) diff --git a/Sources/JavaKitJar/generated/JarFile.swift b/Sources/JavaKitJar/generated/JarFile.swift index 4c215e05..a76d645e 100644 --- a/Sources/JavaKitJar/generated/JarFile.swift +++ b/Sources/JavaKitJar/generated/JarFile.swift @@ -17,6 +17,9 @@ public struct JarFile { @JavaMethod public func getManifest() throws -> Manifest! + @JavaMethod + public func getEntry(_ arg0: String) -> ZipEntry! + @JavaMethod public func getJarEntry(_ arg0: String) -> JarEntry! diff --git a/Sources/JavaKitJar/generated/JarInputStream.swift b/Sources/JavaKitJar/generated/JarInputStream.swift index c8a25439..ac6ade3b 100644 --- a/Sources/JavaKitJar/generated/JarInputStream.swift +++ b/Sources/JavaKitJar/generated/JarInputStream.swift @@ -4,6 +4,9 @@ import JavaRuntime @JavaClass("java.util.jar.JarInputStream", extends: JavaObject.self) public struct JarInputStream { + @JavaMethod + public func getNextEntry() throws -> ZipEntry! + @JavaMethod public func getNextJarEntry() throws -> JarEntry! diff --git a/Sources/JavaKitJar/generated/JarOutputStream.swift b/Sources/JavaKitJar/generated/JarOutputStream.swift index b10dc4b5..04b544c8 100644 --- a/Sources/JavaKitJar/generated/JarOutputStream.swift +++ b/Sources/JavaKitJar/generated/JarOutputStream.swift @@ -4,6 +4,9 @@ import JavaRuntime @JavaClass("java.util.jar.JarOutputStream", extends: JavaObject.self) public struct JarOutputStream { + @JavaMethod + public func putNextEntry(_ arg0: ZipEntry?) throws + @JavaMethod public func closeEntry() throws diff --git a/Sources/JavaKitJar/generated/ZipEntry.swift b/Sources/JavaKitJar/generated/ZipEntry.swift new file mode 100644 index 00000000..2f2e16a5 --- /dev/null +++ b/Sources/JavaKitJar/generated/ZipEntry.swift @@ -0,0 +1,217 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import JavaKit +import JavaRuntime + +@JavaClass("java.util.zip.ZipEntry", extends: JavaObject.self) +public struct ZipEntry { + @JavaMethod + public init(_ arg0: String, environment: JNIEnvironment? = nil) + + @JavaMethod + public init(_ arg0: ZipEntry?, environment: JNIEnvironment? = nil) + + @JavaMethod + public func getName() -> String + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func hashCode() -> Int32 + + @JavaMethod + public func clone() -> JavaObject! + + @JavaMethod + public func getMethod() -> Int32 + + @JavaMethod + public func getSize() -> Int64 + + @JavaMethod + public func isDirectory() -> Bool + + @JavaMethod + public func getTime() -> Int64 + + @JavaMethod + public func setTime(_ arg0: Int64) + + @JavaMethod + public func setSize(_ arg0: Int64) + + @JavaMethod + public func getCompressedSize() -> Int64 + + @JavaMethod + public func setCompressedSize(_ arg0: Int64) + + @JavaMethod + public func setCrc(_ arg0: Int64) + + @JavaMethod + public func getCrc() -> Int64 + + @JavaMethod + public func setMethod(_ arg0: Int32) + + @JavaMethod + public func setExtra(_ arg0: [Int8]) + + @JavaMethod + public func getExtra() -> [Int8] + + @JavaMethod + public func setComment(_ arg0: String) + + @JavaMethod + public func getComment() -> String + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func notify() + + @JavaMethod + public func notifyAll() + + @JavaMethod + public func wait(_ arg0: Int64) throws + + @JavaMethod + public func wait(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + public func wait() throws +} +extension JavaClass { + @JavaStaticField(isFinal: true) + public var STORED: Int32 + + @JavaStaticField(isFinal: true) + public var DEFLATED: Int32 + + @JavaStaticField(isFinal: true) + public var LOCSIG: Int64 + + @JavaStaticField(isFinal: true) + public var EXTSIG: Int64 + + @JavaStaticField(isFinal: true) + public var CENSIG: Int64 + + @JavaStaticField(isFinal: true) + public var ENDSIG: Int64 + + @JavaStaticField(isFinal: true) + public var LOCHDR: Int32 + + @JavaStaticField(isFinal: true) + public var EXTHDR: Int32 + + @JavaStaticField(isFinal: true) + public var CENHDR: Int32 + + @JavaStaticField(isFinal: true) + public var ENDHDR: Int32 + + @JavaStaticField(isFinal: true) + public var LOCVER: Int32 + + @JavaStaticField(isFinal: true) + public var LOCFLG: Int32 + + @JavaStaticField(isFinal: true) + public var LOCHOW: Int32 + + @JavaStaticField(isFinal: true) + public var LOCTIM: Int32 + + @JavaStaticField(isFinal: true) + public var LOCCRC: Int32 + + @JavaStaticField(isFinal: true) + public var LOCSIZ: Int32 + + @JavaStaticField(isFinal: true) + public var LOCLEN: Int32 + + @JavaStaticField(isFinal: true) + public var LOCNAM: Int32 + + @JavaStaticField(isFinal: true) + public var LOCEXT: Int32 + + @JavaStaticField(isFinal: true) + public var EXTCRC: Int32 + + @JavaStaticField(isFinal: true) + public var EXTSIZ: Int32 + + @JavaStaticField(isFinal: true) + public var EXTLEN: Int32 + + @JavaStaticField(isFinal: true) + public var CENVEM: Int32 + + @JavaStaticField(isFinal: true) + public var CENVER: Int32 + + @JavaStaticField(isFinal: true) + public var CENFLG: Int32 + + @JavaStaticField(isFinal: true) + public var CENHOW: Int32 + + @JavaStaticField(isFinal: true) + public var CENTIM: Int32 + + @JavaStaticField(isFinal: true) + public var CENCRC: Int32 + + @JavaStaticField(isFinal: true) + public var CENSIZ: Int32 + + @JavaStaticField(isFinal: true) + public var CENLEN: Int32 + + @JavaStaticField(isFinal: true) + public var CENNAM: Int32 + + @JavaStaticField(isFinal: true) + public var CENEXT: Int32 + + @JavaStaticField(isFinal: true) + public var CENCOM: Int32 + + @JavaStaticField(isFinal: true) + public var CENDSK: Int32 + + @JavaStaticField(isFinal: true) + public var CENATT: Int32 + + @JavaStaticField(isFinal: true) + public var CENATX: Int32 + + @JavaStaticField(isFinal: true) + public var CENOFF: Int32 + + @JavaStaticField(isFinal: true) + public var ENDSUB: Int32 + + @JavaStaticField(isFinal: true) + public var ENDTOT: Int32 + + @JavaStaticField(isFinal: true) + public var ENDSIZ: Int32 + + @JavaStaticField(isFinal: true) + public var ENDOFF: Int32 + + @JavaStaticField(isFinal: true) + public var ENDCOM: Int32 +} From 4b045b2d84bc69a378e096749870a714d75df5f0 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 22:47:44 -0700 Subject: [PATCH 14/17] Java2Swift: Don't translate Java interfaces into Swift classes Keep interfaces as structs for now. Their future is yet unwritten. --- .../Java2SwiftLib/JavaClassTranslator.swift | 25 +++++++++++-------- Sources/Java2SwiftLib/JavaTranslator.swift | 3 +++ Tests/Java2SwiftTests/Java2SwiftTests.swift | 24 ++++++++++++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 0319b8f9..13e1a8af 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -26,6 +26,11 @@ struct JavaClassTranslator { /// The Java class (or interface) being translated. let javaClass: JavaClass + /// Whether to translate this Java class into a Swift class. + /// + /// This will be false for Java interfaces. + let translateAsClass: Bool + /// The type parameters to the Java class or interface. let javaTypeParameters: [TypeVariable>] @@ -107,6 +112,7 @@ struct JavaClassTranslator { let fullName = javaClass.getName() self.javaClass = javaClass self.translator = translator + self.translateAsClass = translator.translateAsClass && !javaClass.isInterface() self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName( fullName, preferValueTypes: false, @@ -175,7 +181,7 @@ struct JavaClassTranslator { } // Gather methods. - let methods = translator.translateAsClass + let methods = translateAsClass ? javaClass.getDeclaredMethods() : javaClass.getMethods() for method in methods { @@ -231,7 +237,7 @@ extension JavaClassTranslator { } // Don't include inherited fields when translating to a class. - if translator.translateAsClass && + if translateAsClass && !field.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) { return } @@ -315,7 +321,7 @@ extension JavaClassTranslator { // formulation). let extends: String let inheritanceClause: String - if translator.translateAsClass { + if translateAsClass { extends = "" inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? "" } else { @@ -334,7 +340,7 @@ extension JavaClassTranslator { // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; - let introducer = translator.translateAsClass ? "open class" : "public struct" + let introducer = translateAsClass ? "open class" : "public struct" var classDecl: DeclSyntax = """ @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) @@ -485,7 +491,7 @@ extension JavaClassTranslator { let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" - let convenienceModifier = translator.translateAsClass ? "convenience " : "" + let convenienceModifier = translateAsClass ? "convenience " : "" return """ @JavaMethod \(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) @@ -523,10 +529,9 @@ extension JavaClassTranslator { ? "" : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; let accessModifier = implementedInSwift ? "" - : (javaMethod.isStatic || !translator.translateAsClass) ? "public " + : (javaMethod.isStatic || !translateAsClass) ? "public " : "open " - let overrideOpt = (translator.translateAsClass && - !javaMethod.isStatic && isOverride(javaMethod)) + let overrideOpt = (translateAsClass && !javaMethod.isStatic && isOverride(javaMethod)) ? "override " : "" return """ @@ -577,7 +582,7 @@ extension JavaClassTranslator { } """ - let convenienceModifier = translator.translateAsClass ? "convenience " : "" + let convenienceModifier = translateAsClass ? "convenience " : "" let initSyntax: DeclSyntax = """ public \(raw: convenienceModifier)init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) { let _environment = if let environment { @@ -591,7 +596,7 @@ extension JavaClassTranslator { return """ case .\($0.getName()): if let \($0.getName()) = classObj.\($0.getName()) { - \(translator.translateAsClass + \(translateAsClass ? "self.init(javaHolder: \($0.getName()).javaHolder)" : "self = \($0.getName())") } else { diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index 56535514..69edac6c 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -26,7 +26,10 @@ package class JavaTranslator { let swiftModuleName: String let environment: JNIEnvironment + + /// Whether to translate Java classes into classes (rather than structs). let translateAsClass: Bool + let format: BasicFormat /// A mapping from the name of each known Java class to the corresponding diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 3413ee8a..74127a0c 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -456,6 +456,30 @@ class Java2SwiftTests: XCTestCase { ] ) } + + func testJavaInterfaceAsClassNOT() throws { + try assertTranslatedClass( + MyJavaIntFunction.self, + swiftTypeName: "MyJavaIntFunction", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + ], + expectedChunks: [ + "import JavaKit", + """ + @JavaInterface("java.util.function.IntFunction") + public struct MyJavaIntFunction { + """, + """ + @JavaMethod + public func apply(_ arg0: Int32) -> JavaObject! + """, + ] + ) + + } } @JavaClass("java.lang.ClassLoader") From 374c847262ce4818b151a1c9c0fe2ca0e1df59e6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 23:12:02 -0700 Subject: [PATCH 15/17] JavaField macro: within a class, don't generate "nonmutating" modifier --- Sources/JavaKitMacros/JavaFieldMacro.swift | 7 ++++++- Tests/JavaKitMacroTests/JavaClassMacroTests.swift | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/JavaKitMacros/JavaFieldMacro.swift b/Sources/JavaKitMacros/JavaFieldMacro.swift index 02280570..40bf7113 100644 --- a/Sources/JavaKitMacros/JavaFieldMacro.swift +++ b/Sources/JavaKitMacros/JavaFieldMacro.swift @@ -64,9 +64,14 @@ extension JavaFieldMacro: AccessorMacro { getter ] + let nonmutatingModifier = + (context.lexicalContext.first?.is(ClassDeclSyntax.self) ?? false) + ? "" + : "nonmutating " + if createSetter { let setter: AccessorDeclSyntax = """ - nonmutating set { self[javaFieldName: \(literal: fieldName), fieldType: \(fieldType).self] = newValue } + \(raw: nonmutatingModifier)set { self[javaFieldName: \(literal: fieldName), fieldType: \(fieldType).self] = newValue } """ accessors.append(setter) } diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift index 9a16d773..e639800b 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift @@ -167,7 +167,7 @@ class JavaKitMacroTests: XCTestCase { get { self[javaFieldName: "myField", fieldType: Int64.self] } - nonmutating set { + set { self[javaFieldName: "myField", fieldType: Int64.self] = newValue } } @@ -175,7 +175,7 @@ class JavaKitMacroTests: XCTestCase { get { self[javaFieldName: "objectField", fieldType: JavaObject?.self] } - nonmutating set { + set { self[javaFieldName: "objectField", fieldType: JavaObject?.self] = newValue } } From 7346359ea124e816ddc40456718f4f3e46d383c1 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 30 Oct 2024 23:36:04 -0700 Subject: [PATCH 16/17] Java2Swift: Account for differences in covariant overrides between Java and Swift Java allows more subtyping relationships for the result types in a covariant method override than Swift does, such as covariant arrays and wildcards. Take the Swift semantics into account when determining whether to apply the `override` keyword. --- Sources/Java2Swift/JavaToSwift.swift | 3 +- .../Java2SwiftLib/JavaClassTranslator.swift | 141 +++++++++++++++++- .../JavaKitCollection/generated/HashMap.swift | 86 ----------- .../JavaKitCollection/generated/TreeMap.swift | 86 +++++++++++ Tests/Java2SwiftTests/Java2SwiftTests.swift | 83 +++++++++++ 5 files changed, 309 insertions(+), 90 deletions(-) diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/Java2Swift/JavaToSwift.swift index 15e346ae..112c015b 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/Java2Swift/JavaToSwift.swift @@ -183,7 +183,8 @@ struct JavaToSwift: ParsableCommand { ) throws { let translator = JavaTranslator( swiftModuleName: moduleName, - environment: environment + environment: environment, + translateAsClass: false ) // Keep track of all of the Java classes that will have diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 13e1a8af..061c8744 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -685,9 +685,21 @@ extension JavaClassTranslator { // If this superclass declares a method with the same parameter types, // we have an override. - let overriddenMethod = try currentSuperclassNonOpt - .getDeclaredMethod(method.getName(), method.getParameterTypes()) - if overriddenMethod != nil { + guard let overriddenMethod = try currentSuperclassNonOpt + .getDeclaredMethod(method.getName(), method.getParameterTypes()) else { + continue + } + + // Ignore non-public, non-protected methods because they would not + // have been render into the Swift superclass. + if !overriddenMethod.isPublic && !overriddenMethod.isProtected { + continue + } + + // We know that Java considers this method an override. However, it is + // possible that Swift will not consider it an override, because Java + // has subtyping relations that Swift does not. + if method.getGenericReturnType().isEqualToOrSubtypeOf(overriddenMethod.getGenericReturnType()) { return true } } catch { @@ -697,3 +709,126 @@ extension JavaClassTranslator { return false } } + +extension [Type?] { + /// Determine whether the types in the array match the other array. + func allTypesEqual(_ other: [Type?]) -> Bool { + if self.count != other.count { + return false + } + + for (selfType, otherType) in zip(self, other) { + if !selfType!.isEqualTo(otherType!) { + return false + } + } + + return true + } +} + +extension Type { + /// Adjust the given type to use its bounds, mirroring what we do in + /// mapping Java types into Swift. + func adjustToJavaBounds(adjusted: inout Bool) -> Type { + if let typeVariable = self.as(TypeVariable.self), + typeVariable.getBounds().count == 1, + let bound = typeVariable.getBounds()[0] { + adjusted = true + return bound + } + + if let wildcardType = self.as(WildcardType.self), + wildcardType.getUpperBounds().count == 1, + let bound = wildcardType.getUpperBounds()[0] { + adjusted = true + return bound + } + + return self + } + + /// Determine whether this type is equivalent to or a subtype of the other + /// type. + func isEqualTo(_ other: Type) -> Bool { + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualTo(adjustedOther) + } + + // If both are classes, check for equivalence. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + return selfClass.equals(otherClass.as(JavaObject.self)) + } + + // If both are arrays, check that their component types are equivalent. + if let selfArray = self.as(GenericArrayType.self), + let otherArray = other.as(GenericArrayType.self) { + return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) + } + + // If both are parameterized types, check their raw type and type + // arguments for equivalence. + if let selfParameterizedType = self.as(ParameterizedType.self), + let otherParameterizedType = other.as(ParameterizedType.self) { + if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { + return false + } + + return selfParameterizedType.getActualTypeArguments() + .allTypesEqual(otherParameterizedType.getActualTypeArguments()) + } + + // If both are type variables, compare their bounds. + // FIXME: This is a hack. + if let selfTypeVariable = self.as(TypeVariable.self), + let otherTypeVariable = other.as(TypeVariable.self) { + return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) + } + + // If both are wildcards, compare their upper and lower bounds. + if let selfWildcard = self.as(WildcardType.self), + let otherWildcard = other.as(WildcardType.self) { + return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) + && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) + } + + return false + } + + /// Determine whether this type is equivalent to or a subtype of the + /// other type. + func isEqualToOrSubtypeOf(_ other: Type) -> Bool { + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) + } + + if isEqualTo(other) { + return true + } + + // If both are classes, check for subclassing. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + return selfClass.isSubclass(of: otherClass) + } + + // Anything object-like is a subclass of java.lang.Object + if let otherClass = other.as(JavaClass.self), + otherClass.getName() == "java.lang.Object" { + if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || + self.is(WildcardType.self) || self.is(TypeVariable.self) { + return true + } + } + return false + } +} diff --git a/Sources/JavaKitCollection/generated/HashMap.swift b/Sources/JavaKitCollection/generated/HashMap.swift index 55cea901..9f800c63 100644 --- a/Sources/JavaKitCollection/generated/HashMap.swift +++ b/Sources/JavaKitCollection/generated/HashMap.swift @@ -88,92 +88,6 @@ public struct HashMap { @JavaMethod public func wait() throws } -extension HashMap { - @JavaClass("java.util.AbstractMap$SimpleEntry", extends: JavaObject.self) - public struct SimpleEntry { - @JavaMethod - public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) - - @JavaMethod - public func equals(_ arg0: JavaObject?) -> Bool - - @JavaMethod - public func toString() -> String - - @JavaMethod - public func hashCode() -> Int32 - - @JavaMethod - public func getValue() -> JavaObject! - - @JavaMethod - public func getKey() -> JavaObject! - - @JavaMethod - public func setValue(_ arg0: JavaObject?) -> JavaObject! - - @JavaMethod - public func getClass() -> JavaClass! - - @JavaMethod - public func notify() - - @JavaMethod - public func notifyAll() - - @JavaMethod - public func wait(_ arg0: Int64) throws - - @JavaMethod - public func wait(_ arg0: Int64, _ arg1: Int32) throws - - @JavaMethod - public func wait() throws - } -} -extension HashMap { - @JavaClass("java.util.AbstractMap$SimpleImmutableEntry", extends: JavaObject.self) - public struct SimpleImmutableEntry { - @JavaMethod - public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) - - @JavaMethod - public func equals(_ arg0: JavaObject?) -> Bool - - @JavaMethod - public func toString() -> String - - @JavaMethod - public func hashCode() -> Int32 - - @JavaMethod - public func getValue() -> JavaObject! - - @JavaMethod - public func getKey() -> JavaObject! - - @JavaMethod - public func setValue(_ arg0: JavaObject?) -> JavaObject! - - @JavaMethod - public func getClass() -> JavaClass! - - @JavaMethod - public func notify() - - @JavaMethod - public func notifyAll() - - @JavaMethod - public func wait(_ arg0: Int64) throws - - @JavaMethod - public func wait(_ arg0: Int64, _ arg1: Int32) throws - - @JavaMethod - public func wait() throws - } -} extension JavaClass { @JavaStaticMethod public func newHashMap(_ arg0: Int32) -> HashMap! where ObjectType == HashMap diff --git a/Sources/JavaKitCollection/generated/TreeMap.swift b/Sources/JavaKitCollection/generated/TreeMap.swift index f6943b48..3d7d858a 100644 --- a/Sources/JavaKitCollection/generated/TreeMap.swift +++ b/Sources/JavaKitCollection/generated/TreeMap.swift @@ -106,3 +106,89 @@ public struct TreeMap { @JavaMethod public func getOrDefault(_ arg0: JavaObject?, _ arg1: JavaObject?) -> JavaObject! } +extension TreeMap { + @JavaClass("java.util.AbstractMap$SimpleEntry", extends: JavaObject.self) + public struct SimpleEntry { + @JavaMethod + public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func hashCode() -> Int32 + + @JavaMethod + public func getValue() -> JavaObject! + + @JavaMethod + public func getKey() -> JavaObject! + + @JavaMethod + public func setValue(_ arg0: JavaObject?) -> JavaObject! + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func notify() + + @JavaMethod + public func notifyAll() + + @JavaMethod + public func wait(_ arg0: Int64) throws + + @JavaMethod + public func wait(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + public func wait() throws + } +} +extension TreeMap { + @JavaClass("java.util.AbstractMap$SimpleImmutableEntry", extends: JavaObject.self) + public struct SimpleImmutableEntry { + @JavaMethod + public init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil) + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func hashCode() -> Int32 + + @JavaMethod + public func getValue() -> JavaObject! + + @JavaMethod + public func getKey() -> JavaObject! + + @JavaMethod + public func setValue(_ arg0: JavaObject?) -> JavaObject! + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func notify() + + @JavaMethod + public func notifyAll() + + @JavaMethod + public func wait(_ arg0: Int64) throws + + @JavaMethod + public func wait(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + public func wait() throws + } +} diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 74127a0c..a9274436 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -478,7 +478,74 @@ class Java2SwiftTests: XCTestCase { """, ] ) + } + func testCovariantInJavaNotInSwiftOverride() throws { + try assertTranslatedClass( + Method.self, + swiftTypeName: "Method", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Class" : ("JavaClass", "JavaKit"), + "java.lang.reflect.Executable": ("Executable", "JavaKitReflection"), + "java.lang.reflect.Method": ("Method", "JavaKitReflection"), + "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaKitReflection"), + ], + expectedChunks: [ + "import JavaKitReflection", + """ + @JavaClass("java.lang.reflect.Method") + open class Method: Executable { + """, + """ + @JavaMethod + open func getTypeParameters() -> [TypeVariable?] + """, + """ + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] + """, + """ + @JavaMethod + open override func getDeclaringClass() -> JavaClass! + """, + ] + ) + } + + func testCovariantInJavaNotInSwiftOverride2() throws { + try assertTranslatedClass( + Constructor.self, + swiftTypeName: "Constructor", + asClass: true, + translatedClasses: [ + "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Class" : ("JavaClass", "JavaKit"), + "java.lang.reflect.Executable": ("Executable", "JavaKitReflection"), + "java.lang.reflect.Method": ("Method", "JavaKitReflection"), + "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaKitReflection"), + ], + expectedChunks: [ + "import JavaKitReflection", + """ + @JavaClass("java.lang.reflect.Constructor") + open class Constructor: Executable { + """, + """ + @JavaMethod + open func getTypeParameters() -> [TypeVariable>?] + """, + """ + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] + """, + """ + @JavaMethod + open override func getDeclaringClass() -> JavaClass! + """, + ] + ) } } @@ -514,6 +581,22 @@ public struct MySupplier { } public struct MyJavaIntFunction { } +@JavaClass("java.lang.reflect.Method", extends: Executable.self) +public struct Method { +} + +@JavaClass("java.lang.reflect.Constructor", extends: Executable.self) +public struct Constructor { +} + +@JavaClass("java.lang.reflect.Executable") +public struct Executable { +} + +@JavaInterface("java.lang.reflect.TypeVariable") +public struct TypeVariable { +} + /// Translate a Java class and assert that the translated output contains /// each of the expected "chunks" of text. func assertTranslatedClass( From 0e3fc536684cdc037ccdc2dad8e5c1af0afe759f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 31 Oct 2024 01:00:48 -0700 Subject: [PATCH 17/17] Java2Swift: Use `@_nonoverride` on generated convenience initializers Swift has inheritance of initializers, while Java does not have inheritance of constructors. This means that Swift's attempt to treat a convenience initializer as an override within a subclass can cause problems with mismatched signatures. Use `@_nonoverride` to avoid this problem. --- Sources/Java2SwiftLib/JavaClassTranslator.swift | 3 ++- Tests/Java2SwiftTests/Java2SwiftTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index 061c8744..afcac639 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -492,9 +492,10 @@ extension JavaClassTranslator { let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" let convenienceModifier = translateAsClass ? "convenience " : "" + let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" return """ @JavaMethod - \(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) + \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index a9274436..445c501c 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -280,7 +280,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public convenience init(environment: JNIEnvironment? = nil) + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ @JavaMethod @@ -314,7 +314,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public convenience init(environment: JNIEnvironment? = nil) + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ @JavaMethod