From cd87fb4e83235bc816da642c2f0ae8f66d5b8592 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Thu, 7 Nov 2024 10:18:55 +1100 Subject: [PATCH 1/3] JavaKit: add CustomJavaClassLoader protocol Swift projections of Java classes can conform to CustomJavaClassLoader, to provide a custom class loader to be used when initializing new instances. This is useful for platforms such as Android which modify class and class loader visibility depending on the call stack. Fixes: #154 --- Sources/JavaKit/AnyJavaObject.swift | 47 +++++++++++- .../Exceptions/ExceptionHandling.swift | 8 +-- Sources/JavaKit/Java2Swift.config | 1 + .../JavaKit/JavaClass+Initialization.swift | 10 +-- Sources/JavaKit/JavaObject+Inheritance.swift | 20 +++--- Sources/JavaKit/JavaObject+MethodCalls.swift | 32 ++++----- Sources/JavaKit/Optional+JavaObject.swift | 5 +- .../JavaKit/generated/JavaClassLoader.swift | 72 +++++++++++++++++++ Tests/Java2SwiftTests/Java2SwiftTests.swift | 36 +++++----- 9 files changed, 174 insertions(+), 57 deletions(-) create mode 100644 Sources/JavaKit/generated/JavaClassLoader.swift diff --git a/Sources/JavaKit/AnyJavaObject.swift b/Sources/JavaKit/AnyJavaObject.swift index 6e39766a..27c3e06d 100644 --- a/Sources/JavaKit/AnyJavaObject.swift +++ b/Sources/JavaKit/AnyJavaObject.swift @@ -49,6 +49,19 @@ public protocol AnyJavaObject { var javaHolder: JavaObjectHolder { get } } +/// Protocol that allows Swift types to specify a custom Java class loader on +/// initialization. This is useful for platforms (e.g. Android) where the default +/// class loader does not make all application classes visible. +public protocol CustomJavaClassLoader: AnyJavaObject { + static func getJavaClassLoader(in environment: JNIEnvironment) throws -> JavaClassLoader! +} + +/// Add getClassLoader() to JavaObject as it is otherwise recursively defined +extension JavaObject { + @JavaMethod + public func getClassLoader() throws -> JavaClassLoader! +} + extension AnyJavaObject { /// Retrieve the underlying Java object. public var javaThis: jobject { @@ -81,13 +94,41 @@ extension AnyJavaObject { JavaClass(javaThis: jniClass!, environment: javaEnvironment) } - /// Retrieve the Java class for this type. - public static func getJNIClass(in environment: JNIEnvironment) throws -> jclass { - try environment.translatingJNIExceptions { + /// Retrieve the Java class for this type using the default class loader. + private static func _withJNIClassFromDefaultClassLoader( + in environment: JNIEnvironment, + _ body: (jclass) throws -> Result + ) throws -> Result { + let resolvedClass = try environment.translatingJNIExceptions { environment.interface.FindClass( environment, fullJavaClassNameWithSlashes ) }! + return try body(resolvedClass) + } + + /// Retrieve the Java class for this type using a specific class loader. + private static func _withJNIClassFromCustomClassLoader( + _ classLoader: JavaClassLoader, + in environment: JNIEnvironment, + _ body: (jclass) throws -> Result + ) throws -> Result { + let resolvedClass = try classLoader.findClass(fullJavaClassName) + return try body(resolvedClass!.javaThis) + } + + /// Retrieve the Java class for this type and execute body(). + @_spi(Testing) + public static func withJNIClass( + in environment: JNIEnvironment, + _ body: (jclass) throws -> Result + ) throws -> Result { + if let customJavaClassLoader = self as? CustomJavaClassLoader.Type, + let customClassLoader = try customJavaClassLoader.getJavaClassLoader(in: environment) { + try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body) + } else { + try _withJNIClassFromDefaultClassLoader(in: environment, body) + } } } diff --git a/Sources/JavaKit/Exceptions/ExceptionHandling.swift b/Sources/JavaKit/Exceptions/ExceptionHandling.swift index cf063d0a..02537281 100644 --- a/Sources/JavaKit/Exceptions/ExceptionHandling.swift +++ b/Sources/JavaKit/Exceptions/ExceptionHandling.swift @@ -39,10 +39,8 @@ extension JNIEnvironment { } // Otherwise, create a exception with a message. - _ = interface.ThrowNew( - self, - try! JavaClass.getJNIClass(in: self), - String(describing: error) - ) + _ = try! JavaClass.withJNIClass(in: self) { exceptionClass in + interface.ThrowNew(self, exceptionClass, String(describing: error)) + } } } diff --git a/Sources/JavaKit/Java2Swift.config b/Sources/JavaKit/Java2Swift.config index ed284d31..d02a10ed 100644 --- a/Sources/JavaKit/Java2Swift.config +++ b/Sources/JavaKit/Java2Swift.config @@ -5,6 +5,7 @@ "java.lang.Byte" : "JavaByte", "java.lang.Character" : "JavaCharacter", "java.lang.Class" : "JavaClass", + "java.lang.ClassLoader" : "JavaClassLoader", "java.lang.Double" : "JavaDouble", "java.lang.Error" : "JavaError", "java.lang.Exception" : "Exception", diff --git a/Sources/JavaKit/JavaClass+Initialization.swift b/Sources/JavaKit/JavaClass+Initialization.swift index 9881e0dc..d443120f 100644 --- a/Sources/JavaKit/JavaClass+Initialization.swift +++ b/Sources/JavaKit/JavaClass+Initialization.swift @@ -21,9 +21,11 @@ extension JavaClass { @_nonoverride public convenience init(environment: JNIEnvironment? = nil) throws { let environment = try environment ?? JavaVirtualMachine.shared().environment() - self.init( - javaThis: try ObjectType.getJNIClass(in: environment), - environment: environment - ) + var javaClassHolder: JavaObjectHolder! + + javaClassHolder = try ObjectType.withJNIClass(in: environment) { javaClass in + JavaObjectHolder(object: javaClass, environment: environment) + } + self.init(javaHolder: javaClassHolder) } } diff --git a/Sources/JavaKit/JavaObject+Inheritance.swift b/Sources/JavaKit/JavaObject+Inheritance.swift index a8557b97..43d86c2a 100644 --- a/Sources/JavaKit/JavaObject+Inheritance.swift +++ b/Sources/JavaKit/JavaObject+Inheritance.swift @@ -22,19 +22,17 @@ extension AnyJavaObject { private func isInstanceOf( _ otherClass: OtherClass.Type ) -> jclass? { - guard let otherJavaClass = try? otherClass.getJNIClass(in: javaEnvironment) else { - return nil - } + try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in + if javaEnvironment.interface.IsInstanceOf( + javaEnvironment, + javaThis, + otherJavaClass + ) == 0 { + return nil + } - if javaEnvironment.interface.IsInstanceOf( - javaEnvironment, - javaThis, - otherJavaClass - ) == 0 { - return nil + return otherJavaClass } - - return otherJavaClass } /// Determine whether this object is an instance of a specific diff --git a/Sources/JavaKit/JavaObject+MethodCalls.swift b/Sources/JavaKit/JavaObject+MethodCalls.swift index 28558d49..9f02a58d 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/JavaKit/JavaObject+MethodCalls.swift @@ -253,23 +253,23 @@ extension AnyJavaObject { 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 - // method within the class. - let methodID = try Self.javaMethodLookup( - thisClass: thisClass, - methodName: javaConstructorName, - parameterTypes: repeat (each Param).self, - resultType: .void, - in: environment - ) + try Self.withJNIClass(in: environment) { thisClass in + // Compute the method signature so we can find the right method, then look up the + // method within the class. + let methodID = try Self.javaMethodLookup( + thisClass: thisClass, + methodName: javaConstructorName, + parameterTypes: repeat (each Param).self, + resultType: .void, + in: environment + ) - // Retrieve the constructor, then map the arguments and call it. - let jniArgs = getJValues(repeat each arguments, in: environment) - return try environment.translatingJNIExceptions { - environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs) - }! + // Retrieve the constructor, then map the arguments and call it. + let jniArgs = getJValues(repeat each arguments, in: environment) + return try environment.translatingJNIExceptions { + environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs) + }! + } } /// Retrieve the JNI field ID for a field with the given name and type. diff --git a/Sources/JavaKit/Optional+JavaObject.swift b/Sources/JavaKit/Optional+JavaObject.swift index b2f115db..7aff4294 100644 --- a/Sources/JavaKit/Optional+JavaObject.swift +++ b/Sources/JavaKit/Optional+JavaObject.swift @@ -70,8 +70,9 @@ extension Optional: JavaValue where Wrapped: AnyJavaObject { public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { return { environment, size in - let jniClass = try! Wrapped.getJNIClass(in: environment) - return environment.interface.NewObjectArray(environment, size, jniClass, nil) + try! Wrapped.withJNIClass(in: environment) { jniClass in + environment.interface.NewObjectArray(environment, size, jniClass, nil) + } } } diff --git a/Sources/JavaKit/generated/JavaClassLoader.swift b/Sources/JavaKit/generated/JavaClassLoader.swift new file mode 100644 index 00000000..43eaeb4e --- /dev/null +++ b/Sources/JavaKit/generated/JavaClassLoader.swift @@ -0,0 +1,72 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import JavaRuntime + +@JavaClass("java.lang.ClassLoader") +open class JavaClassLoader: JavaObject { + @JavaMethod + open func getName() -> String + + @JavaMethod + open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! + + @JavaMethod + open func loadClass(_ arg0: String) throws -> JavaClass! + + @JavaMethod + open func setSigners(_ arg0: JavaClass?, _ arg1: [JavaObject?]) + + @JavaMethod + open func getClassLoadingLock(_ arg0: String) -> JavaObject! + + @JavaMethod + open func findLoadedClass(_ arg0: String) -> JavaClass! + + @JavaMethod + open func findClass(_ arg0: String) throws -> JavaClass! + + @JavaMethod + open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! + + @JavaMethod + open func resolveClass(_ arg0: JavaClass?) + + @JavaMethod + open func defineClass(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> JavaClass! + + @JavaMethod + open func defineClass(_ arg0: String, _ arg1: [Int8], _ arg2: Int32, _ arg3: Int32) throws -> JavaClass! + + @JavaMethod + open func findLibrary(_ arg0: String) -> String + + @JavaMethod + open func findSystemClass(_ arg0: String) throws -> JavaClass! + + @JavaMethod + open func isRegisteredAsParallelCapable() -> Bool + + @JavaMethod + open func getParent() -> JavaClassLoader! + + @JavaMethod + open func setDefaultAssertionStatus(_ arg0: Bool) + + @JavaMethod + open func setPackageAssertionStatus(_ arg0: String, _ arg1: Bool) + + @JavaMethod + open func setClassAssertionStatus(_ arg0: String, _ arg1: Bool) + + @JavaMethod + open func clearAssertionStatus() +} +extension JavaClass { + @JavaStaticMethod + public func getPlatformClassLoader() -> JavaClassLoader! + + @JavaStaticMethod + public func getSystemClassLoader() -> JavaClassLoader! + + @JavaStaticMethod + public func registerAsParallelCapable() -> Bool +} diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 259193b1..48440522 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +@_spi(Testing) import JavaKit import Java2SwiftLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -661,24 +662,27 @@ func assertTranslatedClass( translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil) translator.nestedClasses = nestedClasses translator.startNewFile() - let translatedDecls = try translator.translateClass( - JavaClass( - javaThis: javaType.getJNIClass(in: environment), - environment: environment) - ) - let importDecls = translator.getImportDecls() - let swiftFileText = """ - // Auto-generated by Java-to-Swift wrapper generator. - \(importDecls.map { $0.description }.joined()) - \(translatedDecls.map { $0.description }.joined(separator: "\n")) - """ + try javaType.withJNIClass(in: environment) { javaClass in + let translatedDecls = try translator.translateClass( + JavaClass( + javaThis: javaClass, + environment: environment) + ) + let importDecls = translator.getImportDecls() - for expectedChunk in expectedChunks { - if swiftFileText.contains(expectedChunk) { - continue - } + let swiftFileText = """ + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(translatedDecls.map { $0.description }.joined(separator: "\n")) + """ - XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line) + for expectedChunk in expectedChunks { + if swiftFileText.contains(expectedChunk) { + continue + } + + XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line) + } } } From abc6af1629b225aea509d8809c206cb845a29212 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 7 Nov 2024 16:22:26 -0800 Subject: [PATCH 2/3] build: add proper search path for Windows The Windows includes are under `win32` at least with the Oracle JDK. This is required to be able to build on Windows. --- Package.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 0ea37b36..fc8e344f 100644 --- a/Package.swift +++ b/Package.swift @@ -35,9 +35,8 @@ let javaIncludePath = "\(javaHome)/include" let javaPlatformIncludePath = "\(javaIncludePath)/linux" #elseif os(macOS) let javaPlatformIncludePath = "\(javaIncludePath)/darwin" -#else - // TODO: Handle windows as well - #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32" #endif let package = Package( From 0f4002d1893befe7ecc79b038a31ac2911c94f28 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 8 Nov 2024 09:23:47 -0800 Subject: [PATCH 3/3] Adapt to swift-syntax changes in generic argument handling --- Sources/JExtractSwift/TranslatedType.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index 367c92cc..ca1ecf73 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -55,7 +55,12 @@ extension Swift2JavaVisitor { // Translate the generic arguments to the C-compatible types. let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in try genericArgumentClause.arguments.map { argument in - try cCompatibleType(for: argument.argument) + switch argument.argument { + case .type(let argumentType): + try cCompatibleType(for: argumentType) + @unknown default: + throw TypeTranslationError.unimplementedType(TypeSyntax(memberType)) + } } } @@ -71,7 +76,12 @@ extension Swift2JavaVisitor { // Translate the generic arguments to the C-compatible types. let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in try genericArgumentClause.arguments.map { argument in - try cCompatibleType(for: argument.argument) + switch argument.argument { + case .type(let argumentType): + try cCompatibleType(for: argumentType) + @unknown default: + throw TypeTranslationError.unimplementedType(TypeSyntax(identifierType)) + } } }