Skip to content

Commit

Permalink
JavaKit: add CustomJavaClassLoaderConstructible protocol
Browse files Browse the repository at this point in the history
Swift projections of Java classes can conform to
CustomJavaClassLoaderConstructible, to provide a custom class loader to be used
when constructing new instances.

Fixes: swiftlang#154
  • Loading branch information
lhoward committed Nov 7, 2024
1 parent 3436f46 commit f9291ed
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 58 deletions.
46 changes: 43 additions & 3 deletions Sources/JavaKit/AnyJavaObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 CustomJavaClassLoaderConstructible: 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 {
Expand Down Expand Up @@ -81,13 +94,40 @@ 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<T>(
in environment: JNIEnvironment,
_ body: (jclass) throws -> T
) throws -> T {
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<T>(
_ classLoader: JavaClassLoader,
in environment: JNIEnvironment,
_ body: (jclass) throws -> T
) throws -> T {
let resolvedClass = try classLoader.findClass(fullJavaClassName)
return try body(resolvedClass!.javaThis)
}

/// Retrieve the Java class for this type and execute body().
internal static func withJNIClass<T>(
in environment: JNIEnvironment,
_ body: (jclass) throws -> T
) throws -> T {
if let customConstructible = self as? CustomJavaClassLoaderConstructible.Type,
let customClassLoader = try customConstructible.getJavaClassLoader(in: environment) {
try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body)
} else {
try _withJNIClassFromDefaultClassLoader(in: environment, body)
}
}
}
8 changes: 3 additions & 5 deletions Sources/JavaKit/Exceptions/ExceptionHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ extension JNIEnvironment {
}

// Otherwise, create a exception with a message.
_ = interface.ThrowNew(
self,
try! JavaClass<Exception>.getJNIClass(in: self),
String(describing: error)
)
_ = try! JavaClass<Exception>.withJNIClass(in: self) { exceptionClass in
interface.ThrowNew(self, exceptionClass, String(describing: error))
}
}
}
1 change: 1 addition & 0 deletions Sources/JavaKit/Java2Swift.config
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 6 additions & 4 deletions Sources/JavaKit/JavaClass+Initialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(javaThis: javaClassHolder.object!, environment: environment)
}
}
20 changes: 9 additions & 11 deletions Sources/JavaKit/JavaObject+Inheritance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,17 @@ extension AnyJavaObject {
private func isInstanceOf<OtherClass: AnyJavaObject>(
_ 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
Expand Down
32 changes: 16 additions & 16 deletions Sources/JavaKit/JavaObject+MethodCalls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions Sources/JavaKit/Optional+JavaObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
72 changes: 72 additions & 0 deletions Sources/JavaKit/generated/JavaClassLoader.swift
Original file line number Diff line number Diff line change
@@ -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<JavaObject>!

@JavaMethod
open func loadClass(_ arg0: String) throws -> JavaClass<JavaObject>!

@JavaMethod
open func setSigners(_ arg0: JavaClass<JavaObject>?, _ arg1: [JavaObject?])

@JavaMethod
open func getClassLoadingLock(_ arg0: String) -> JavaObject!

@JavaMethod
open func findLoadedClass(_ arg0: String) -> JavaClass<JavaObject>!

@JavaMethod
open func findClass(_ arg0: String) throws -> JavaClass<JavaObject>!

@JavaMethod
open func findClass(_ arg0: String, _ arg1: String) -> JavaClass<JavaObject>!

@JavaMethod
open func resolveClass(_ arg0: JavaClass<JavaObject>?)

@JavaMethod
open func defineClass(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> JavaClass<JavaObject>!

@JavaMethod
open func defineClass(_ arg0: String, _ arg1: [Int8], _ arg2: Int32, _ arg3: Int32) throws -> JavaClass<JavaObject>!

@JavaMethod
open func findLibrary(_ arg0: String) -> String

@JavaMethod
open func findSystemClass(_ arg0: String) throws -> JavaClass<JavaObject>!

@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<JavaClassLoader> {
@JavaStaticMethod
public func getPlatformClassLoader() -> JavaClassLoader!

@JavaStaticMethod
public func getSystemClassLoader() -> JavaClassLoader!

@JavaStaticMethod
public func registerAsParallelCapable() -> Bool
}
37 changes: 20 additions & 17 deletions Tests/Java2SwiftTests/Java2SwiftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

import JavaKit
@testable import JavaKit
import Java2SwiftLib
import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43

Expand Down Expand Up @@ -661,24 +661,27 @@ func assertTranslatedClass<JavaClassType: AnyJavaObject>(
translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil)
translator.nestedClasses = nestedClasses
translator.startNewFile()
let translatedDecls = try translator.translateClass(
JavaClass<JavaObject>(
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<JavaObject>(
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)
}
}
}

0 comments on commit f9291ed

Please sign in to comment.