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 32ac9549..131aa23c 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/JavaKit/JavaObject+MethodCalls.swift @@ -277,23 +277,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( - in: environment, - thisClass: thisClass, - methodName: javaConstructorName, - parameterTypes: repeat (each Param).self, - resultType: .void - ) + 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( + in: environment, + thisClass: thisClass, + methodName: javaConstructorName, + parameterTypes: repeat (each Param).self, + resultType: .void + ) - // 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) + } } }