Skip to content

Commit cd87fb4

Browse files
committed
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
1 parent 79237aa commit cd87fb4

File tree

9 files changed

+174
-57
lines changed

9 files changed

+174
-57
lines changed

Sources/JavaKit/AnyJavaObject.swift

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ public protocol AnyJavaObject {
4949
var javaHolder: JavaObjectHolder { get }
5050
}
5151

52+
/// Protocol that allows Swift types to specify a custom Java class loader on
53+
/// initialization. This is useful for platforms (e.g. Android) where the default
54+
/// class loader does not make all application classes visible.
55+
public protocol CustomJavaClassLoader: AnyJavaObject {
56+
static func getJavaClassLoader(in environment: JNIEnvironment) throws -> JavaClassLoader!
57+
}
58+
59+
/// Add getClassLoader() to JavaObject as it is otherwise recursively defined
60+
extension JavaObject {
61+
@JavaMethod
62+
public func getClassLoader() throws -> JavaClassLoader!
63+
}
64+
5265
extension AnyJavaObject {
5366
/// Retrieve the underlying Java object.
5467
public var javaThis: jobject {
@@ -81,13 +94,41 @@ extension AnyJavaObject {
8194
JavaClass(javaThis: jniClass!, environment: javaEnvironment)
8295
}
8396

84-
/// Retrieve the Java class for this type.
85-
public static func getJNIClass(in environment: JNIEnvironment) throws -> jclass {
86-
try environment.translatingJNIExceptions {
97+
/// Retrieve the Java class for this type using the default class loader.
98+
private static func _withJNIClassFromDefaultClassLoader<Result>(
99+
in environment: JNIEnvironment,
100+
_ body: (jclass) throws -> Result
101+
) throws -> Result {
102+
let resolvedClass = try environment.translatingJNIExceptions {
87103
environment.interface.FindClass(
88104
environment,
89105
fullJavaClassNameWithSlashes
90106
)
91107
}!
108+
return try body(resolvedClass)
109+
}
110+
111+
/// Retrieve the Java class for this type using a specific class loader.
112+
private static func _withJNIClassFromCustomClassLoader<Result>(
113+
_ classLoader: JavaClassLoader,
114+
in environment: JNIEnvironment,
115+
_ body: (jclass) throws -> Result
116+
) throws -> Result {
117+
let resolvedClass = try classLoader.findClass(fullJavaClassName)
118+
return try body(resolvedClass!.javaThis)
119+
}
120+
121+
/// Retrieve the Java class for this type and execute body().
122+
@_spi(Testing)
123+
public static func withJNIClass<Result>(
124+
in environment: JNIEnvironment,
125+
_ body: (jclass) throws -> Result
126+
) throws -> Result {
127+
if let customJavaClassLoader = self as? CustomJavaClassLoader.Type,
128+
let customClassLoader = try customJavaClassLoader.getJavaClassLoader(in: environment) {
129+
try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body)
130+
} else {
131+
try _withJNIClassFromDefaultClassLoader(in: environment, body)
132+
}
92133
}
93134
}

Sources/JavaKit/Exceptions/ExceptionHandling.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,8 @@ extension JNIEnvironment {
3939
}
4040

4141
// Otherwise, create a exception with a message.
42-
_ = interface.ThrowNew(
43-
self,
44-
try! JavaClass<Exception>.getJNIClass(in: self),
45-
String(describing: error)
46-
)
42+
_ = try! JavaClass<Exception>.withJNIClass(in: self) { exceptionClass in
43+
interface.ThrowNew(self, exceptionClass, String(describing: error))
44+
}
4745
}
4846
}

Sources/JavaKit/Java2Swift.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"java.lang.Byte" : "JavaByte",
66
"java.lang.Character" : "JavaCharacter",
77
"java.lang.Class" : "JavaClass",
8+
"java.lang.ClassLoader" : "JavaClassLoader",
89
"java.lang.Double" : "JavaDouble",
910
"java.lang.Error" : "JavaError",
1011
"java.lang.Exception" : "Exception",

Sources/JavaKit/JavaClass+Initialization.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ extension JavaClass {
2121
@_nonoverride
2222
public convenience init(environment: JNIEnvironment? = nil) throws {
2323
let environment = try environment ?? JavaVirtualMachine.shared().environment()
24-
self.init(
25-
javaThis: try ObjectType.getJNIClass(in: environment),
26-
environment: environment
27-
)
24+
var javaClassHolder: JavaObjectHolder!
25+
26+
javaClassHolder = try ObjectType.withJNIClass(in: environment) { javaClass in
27+
JavaObjectHolder(object: javaClass, environment: environment)
28+
}
29+
self.init(javaHolder: javaClassHolder)
2830
}
2931
}

Sources/JavaKit/JavaObject+Inheritance.swift

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,17 @@ extension AnyJavaObject {
2222
private func isInstanceOf<OtherClass: AnyJavaObject>(
2323
_ otherClass: OtherClass.Type
2424
) -> jclass? {
25-
guard let otherJavaClass = try? otherClass.getJNIClass(in: javaEnvironment) else {
26-
return nil
27-
}
25+
try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in
26+
if javaEnvironment.interface.IsInstanceOf(
27+
javaEnvironment,
28+
javaThis,
29+
otherJavaClass
30+
) == 0 {
31+
return nil
32+
}
2833

29-
if javaEnvironment.interface.IsInstanceOf(
30-
javaEnvironment,
31-
javaThis,
32-
otherJavaClass
33-
) == 0 {
34-
return nil
34+
return otherJavaClass
3535
}
36-
37-
return otherJavaClass
3836
}
3937

4038
/// Determine whether this object is an instance of a specific

Sources/JavaKit/JavaObject+MethodCalls.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -253,23 +253,23 @@ extension AnyJavaObject {
253253
in environment: JNIEnvironment,
254254
arguments: repeat each Param
255255
) throws -> jobject {
256-
let thisClass = try Self.getJNIClass(in: environment)
257-
258-
// Compute the method signature so we can find the right method, then look up the
259-
// method within the class.
260-
let methodID = try Self.javaMethodLookup(
261-
thisClass: thisClass,
262-
methodName: javaConstructorName,
263-
parameterTypes: repeat (each Param).self,
264-
resultType: .void,
265-
in: environment
266-
)
256+
try Self.withJNIClass(in: environment) { thisClass in
257+
// Compute the method signature so we can find the right method, then look up the
258+
// method within the class.
259+
let methodID = try Self.javaMethodLookup(
260+
thisClass: thisClass,
261+
methodName: javaConstructorName,
262+
parameterTypes: repeat (each Param).self,
263+
resultType: .void,
264+
in: environment
265+
)
267266

268-
// Retrieve the constructor, then map the arguments and call it.
269-
let jniArgs = getJValues(repeat each arguments, in: environment)
270-
return try environment.translatingJNIExceptions {
271-
environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs)
272-
}!
267+
// Retrieve the constructor, then map the arguments and call it.
268+
let jniArgs = getJValues(repeat each arguments, in: environment)
269+
return try environment.translatingJNIExceptions {
270+
environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs)
271+
}!
272+
}
273273
}
274274

275275
/// Retrieve the JNI field ID for a field with the given name and type.

Sources/JavaKit/Optional+JavaObject.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ extension Optional: JavaValue where Wrapped: AnyJavaObject {
7070

7171
public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray {
7272
return { environment, size in
73-
let jniClass = try! Wrapped.getJNIClass(in: environment)
74-
return environment.interface.NewObjectArray(environment, size, jniClass, nil)
73+
try! Wrapped.withJNIClass(in: environment) { jniClass in
74+
environment.interface.NewObjectArray(environment, size, jniClass, nil)
75+
}
7576
}
7677
}
7778

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Auto-generated by Java-to-Swift wrapper generator.
2+
import JavaRuntime
3+
4+
@JavaClass("java.lang.ClassLoader")
5+
open class JavaClassLoader: JavaObject {
6+
@JavaMethod
7+
open func getName() -> String
8+
9+
@JavaMethod
10+
open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass<JavaObject>!
11+
12+
@JavaMethod
13+
open func loadClass(_ arg0: String) throws -> JavaClass<JavaObject>!
14+
15+
@JavaMethod
16+
open func setSigners(_ arg0: JavaClass<JavaObject>?, _ arg1: [JavaObject?])
17+
18+
@JavaMethod
19+
open func getClassLoadingLock(_ arg0: String) -> JavaObject!
20+
21+
@JavaMethod
22+
open func findLoadedClass(_ arg0: String) -> JavaClass<JavaObject>!
23+
24+
@JavaMethod
25+
open func findClass(_ arg0: String) throws -> JavaClass<JavaObject>!
26+
27+
@JavaMethod
28+
open func findClass(_ arg0: String, _ arg1: String) -> JavaClass<JavaObject>!
29+
30+
@JavaMethod
31+
open func resolveClass(_ arg0: JavaClass<JavaObject>?)
32+
33+
@JavaMethod
34+
open func defineClass(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> JavaClass<JavaObject>!
35+
36+
@JavaMethod
37+
open func defineClass(_ arg0: String, _ arg1: [Int8], _ arg2: Int32, _ arg3: Int32) throws -> JavaClass<JavaObject>!
38+
39+
@JavaMethod
40+
open func findLibrary(_ arg0: String) -> String
41+
42+
@JavaMethod
43+
open func findSystemClass(_ arg0: String) throws -> JavaClass<JavaObject>!
44+
45+
@JavaMethod
46+
open func isRegisteredAsParallelCapable() -> Bool
47+
48+
@JavaMethod
49+
open func getParent() -> JavaClassLoader!
50+
51+
@JavaMethod
52+
open func setDefaultAssertionStatus(_ arg0: Bool)
53+
54+
@JavaMethod
55+
open func setPackageAssertionStatus(_ arg0: String, _ arg1: Bool)
56+
57+
@JavaMethod
58+
open func setClassAssertionStatus(_ arg0: String, _ arg1: Bool)
59+
60+
@JavaMethod
61+
open func clearAssertionStatus()
62+
}
63+
extension JavaClass<JavaClassLoader> {
64+
@JavaStaticMethod
65+
public func getPlatformClassLoader() -> JavaClassLoader!
66+
67+
@JavaStaticMethod
68+
public func getSystemClassLoader() -> JavaClassLoader!
69+
70+
@JavaStaticMethod
71+
public func registerAsParallelCapable() -> Bool
72+
}

Tests/Java2SwiftTests/Java2SwiftTests.swift

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
@_spi(Testing)
1516
import JavaKit
1617
import Java2SwiftLib
1718
import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
@@ -661,24 +662,27 @@ func assertTranslatedClass<JavaClassType: AnyJavaObject>(
661662
translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil)
662663
translator.nestedClasses = nestedClasses
663664
translator.startNewFile()
664-
let translatedDecls = try translator.translateClass(
665-
JavaClass<JavaObject>(
666-
javaThis: javaType.getJNIClass(in: environment),
667-
environment: environment)
668-
)
669-
let importDecls = translator.getImportDecls()
670665

671-
let swiftFileText = """
672-
// Auto-generated by Java-to-Swift wrapper generator.
673-
\(importDecls.map { $0.description }.joined())
674-
\(translatedDecls.map { $0.description }.joined(separator: "\n"))
675-
"""
666+
try javaType.withJNIClass(in: environment) { javaClass in
667+
let translatedDecls = try translator.translateClass(
668+
JavaClass<JavaObject>(
669+
javaThis: javaClass,
670+
environment: environment)
671+
)
672+
let importDecls = translator.getImportDecls()
676673

677-
for expectedChunk in expectedChunks {
678-
if swiftFileText.contains(expectedChunk) {
679-
continue
680-
}
674+
let swiftFileText = """
675+
// Auto-generated by Java-to-Swift wrapper generator.
676+
\(importDecls.map { $0.description }.joined())
677+
\(translatedDecls.map { $0.description }.joined(separator: "\n"))
678+
"""
681679

682-
XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line)
680+
for expectedChunk in expectedChunks {
681+
if swiftFileText.contains(expectedChunk) {
682+
continue
683+
}
684+
685+
XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line)
686+
}
683687
}
684688
}

0 commit comments

Comments
 (0)