Skip to content

Commit 05481c7

Browse files
committed
JavaKit: add CustomJavaClassLoaderConstructible protocol
Swift projections of Java classes can conform to CustomJavaClassLoaderConstructible, to provide a custom class loader to be used when constructing new instances.
1 parent d26c020 commit 05481c7

File tree

9 files changed

+173
-58
lines changed

9 files changed

+173
-58
lines changed

Sources/JavaKit/AnyJavaObject.swift

Lines changed: 43 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 CustomJavaClassLoaderConstructible {
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,40 @@ 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<T>(
99+
in environment: JNIEnvironment,
100+
_ body: (jclass) throws -> T
101+
) throws -> T {
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<T>(
113+
_ classLoader: JavaClassLoader,
114+
in environment: JNIEnvironment,
115+
_ body: (jclass) throws -> T
116+
) throws -> T {
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+
internal static func withJNIClass<T>(
123+
in environment: JNIEnvironment,
124+
_ body: (jclass) throws -> T
125+
) throws -> T {
126+
if let customConstructible = self as? CustomJavaClassLoaderConstructible.Type,
127+
let customClassLoader = try customConstructible.getJavaClassLoader(in: environment) {
128+
try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body)
129+
} else {
130+
try _withJNIClassFromDefaultClassLoader(in: environment, body)
131+
}
92132
}
93133
}

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(javaThis: javaClassHolder.object!, environment: environment)
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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

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

@@ -661,24 +661,27 @@ func assertTranslatedClass<JavaClassType: AnyJavaObject>(
661661
translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil)
662662
translator.nestedClasses = nestedClasses
663663
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()
670664

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-
"""
665+
try javaType.withJNIClass(in: environment) { javaClass in
666+
let translatedDecls = try translator.translateClass(
667+
JavaClass<JavaObject>(
668+
javaThis: javaClass,
669+
environment: environment)
670+
)
671+
let importDecls = translator.getImportDecls()
676672

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

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

0 commit comments

Comments
 (0)