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 1df6d683..afcac639 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>] @@ -37,6 +42,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? @@ -103,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, @@ -114,14 +124,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 } @@ -161,9 +181,15 @@ struct JavaClassTranslator { } // Gather methods. - for method in javaClass.getMethods() { + let methods = translateAsClass + ? javaClass.getDeclaredMethods() + : javaClass.getMethods() + for method in methods { guard let method 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 // we want to see non-public methods as well. @@ -178,6 +204,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 +236,12 @@ extension JavaClassTranslator { return } + // Don't include inherited fields when translating to a class. + if translateAsClass && + !field.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) { + return + } + fields.append(field) } @@ -283,8 +316,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 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 @@ -297,10 +340,11 @@ extension JavaClassTranslator { // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; + let introducer = 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: inheritanceClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } """ @@ -447,9 +491,11 @@ extension JavaClassTranslator { let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" + let convenienceModifier = translateAsClass ? "convenience " : "" + let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" return """ @JavaMethod - \(raw: accessModifier)init(\(raw: parametersStr))\(raw: throwsStr) + \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } @@ -483,9 +529,14 @@ extension JavaClassTranslator { let methodAttribute: AttributeSyntax = implementedInSwift ? "" : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; - let accessModifier = implementedInSwift ? "" : "public " + let accessModifier = implementedInSwift ? "" + : (javaMethod.isStatic || !translateAsClass) ? "public " + : "open " + let overrideOpt = (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) """ } @@ -532,20 +583,23 @@ extension JavaClassTranslator { } """ + let convenienceModifier = 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 { try! JavaVirtualMachine.shared().environment() } - let classObj = try! JavaClass(environment: _environment) + let classObj = try! JavaClass<\(raw: swiftInnermostTypeName)>(environment: _environment) switch enumValue { \(raw: enumConstants.map { return """ case .\($0.getName()): if let \($0.getName()) = classObj.\($0.getName()) { - self = \($0.getName()) + \(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") } @@ -611,3 +665,171 @@ 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 { + 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 + } + + // If this superclass declares a method with the same parameter types, + // we have an override. + 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 { + } + } + + 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/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index 5889a193..69edac6c 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -26,6 +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 @@ -61,10 +65,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 } 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/JavaKit/generated/JavaCharacter.swift b/Sources/JavaKit/generated/JavaCharacter.swift index 1ebb7625..82b92a23 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 @@ -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 { @@ -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/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 dd0e30ab..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") +@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 9da57d98..a76d645e 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 @@ -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 de063bb9..ac6ade3b 100644 --- a/Sources/JavaKitJar/generated/JarInputStream.swift +++ b/Sources/JavaKitJar/generated/JarInputStream.swift @@ -2,8 +2,14 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.jar.JarInputStream") +@JavaClass("java.util.jar.JarInputStream", extends: JavaObject.self) public struct JarInputStream { + @JavaMethod + public func getNextEntry() throws -> ZipEntry! + + @JavaMethod + public func getNextJarEntry() throws -> JarEntry! + @JavaMethod public func read(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32 @@ -11,7 +17,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 +29,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 +43,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..04b544c8 100644 --- a/Sources/JavaKitJar/generated/JarOutputStream.swift +++ b/Sources/JavaKitJar/generated/JarOutputStream.swift @@ -2,8 +2,14 @@ import JavaKit import JavaRuntime -@JavaClass("java.util.jar.JarOutputStream") +@JavaClass("java.util.jar.JarOutputStream", extends: JavaObject.self) public struct JarOutputStream { + @JavaMethod + public func putNextEntry(_ arg0: ZipEntry?) throws + + @JavaMethod + public func closeEntry() throws + @JavaMethod public func write(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws @@ -22,9 +28,6 @@ public struct JarOutputStream { @JavaMethod public func setLevel(_ arg0: Int32) - @JavaMethod - public func closeEntry() throws - @JavaMethod public func flush() 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 +} 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..9a2dd4a5 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,102 @@ 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" + } + + let classNameAccessSpecifier = isSwiftClass ? "open" : "public" + members.append(""" /// The full Java class name for this Swift type. - public static var fullJavaClassName: String { \(literal: className) } + \(raw: classNameAccessSpecifier) \(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. - 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 [ - fullJavaClassNameMember, - superclassTypealias, - javaHolderMember, - initMember, - nonOptionalAs, - ] + return members } } @@ -112,3 +160,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/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/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/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) 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/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 80c86fc0..445c501c 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -266,8 +266,299 @@ 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 + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + """, + """ + @JavaMethod + open func toString() -> String + """, + """ + @JavaMethod + open func wait() throws + """, + """ + @JavaMethod + open func clone() throws -> JavaObject! + """, + ] + ) + } + + 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 + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + """, + """ + @JavaMethod + open override func toString() -> String + """, + """ + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool + """, + """ + @JavaMethod + open func intern() -> String + """, + """ + @JavaStaticMethod + public func valueOf(_ arg0: Int64) -> String + """, + ] + ) + } + + 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) {", + """ + let classObj = try! JavaClass(environment: _environment) + """, + """ + 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 + // 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! + """, + ] + ) + } + + 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 + """, + ] + ) + } + + 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! + """, + ] + ) + } + + 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! + """, + ] + ) + } } +@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 { } @@ -290,11 +581,28 @@ 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( _ javaType: JavaClassType.Type, swiftTypeName: String, + asClass: Bool = false, translatedClasses: [ String: (swiftType: String, swiftModule: String?) ] = [:], @@ -306,7 +614,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..e639800b 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift @@ -115,5 +115,131 @@ 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] + } + set { + self[javaFieldName: "myField", fieldType: Int64.self] = newValue + } + } + public var objectField: JavaObject! { + get { + self[javaFieldName: "objectField", fieldType: JavaObject?.self] + } + 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. + open override class var fullJavaClassName: String { + "org.swift.example.HelloWorld" + } + + public required init(javaHolder: JavaObjectHolder) { + super.init(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. + open class var fullJavaClassName: String { + "java.lang.Object" + } + + public var javaHolder: JavaObjectHolder + + public required init(javaHolder: JavaObjectHolder) { + self.javaHolder = javaHolder + } + } + """, + macros: Self.javaKitMacros + ) + } }