Skip to content

Commit 555d0cc

Browse files
committed
Showcase returning java object created in Swift JNI call
We must reinvent how we promote values to global refs etc. Without the promotion we get a null. We need to manage the retains automatically as we return from a downcall in any wrapped java wrapper class etc...
1 parent f1dfc4e commit 555d0cc

File tree

9 files changed

+178
-5
lines changed

9 files changed

+178
-5
lines changed

Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ enum SwiftWrappedError: Error {
2121

2222
@JavaImplementation("com.example.swift.HelloSwift")
2323
extension HelloSwift: HelloSwiftNativeMethods {
24+
25+
@JavaMethod
26+
func compute(_ a: JavaInteger?, _ b: JavaInteger?) -> JavaInteger? {
27+
guard let a else { fatalError("parameter 'a' must not be null!") }
28+
guard let b else { fatalError("parameter 'b' must not be null!") }
29+
30+
print("[swift] a = \(a.intValue())")
31+
print("[swift] b = \(b.intValue())")
32+
print("[swift] a + b = \(a.intValue() + b.intValue())")
33+
let result = JavaInteger(a.intValue() + b.intValue())
34+
let x = JavaObjectHolder(object: result.javaThis, environment: try! JavaVirtualMachine.shared().environment())
35+
print("[swift] Integer(a + b) = \(result)")
36+
return result
37+
}
38+
2439
@JavaMethod
2540
func sayHello(_ i: Int32, _ j: Int32) -> Int32 {
2641
print("Hello from Swift!")

Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public HelloSwift() {
3030
}
3131

3232
native int sayHello(int x, int y);
33+
34+
native Integer compute(Integer x, Integer y);
35+
3336
native String throwMessageFromSwift(String message) throws Exception;
3437

3538
// To be called back by the native code

Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/JavaKitSampleMain.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@
2020
*/
2121
public class JavaKitSampleMain {
2222
public static void main(String[] args) {
23-
int result = new HelloSubclass("Swift").sayHello(17, 25);
24-
System.out.println("sayHello(17, 25) = " + result);
23+
var subclass = new HelloSubclass("Swift");
24+
25+
int intResult = subclass.sayHello(17, 25);
26+
System.out.println("sayHello(17, 25) = " + intResult);
27+
28+
Integer integerResult = subclass.compute(16, 25);
29+
System.out.println("compute(17, 25) = " + integerResult);
30+
31+
if (integerResult == null) {
32+
throw AssertionError("integerResult was null!")
33+
}
2534
}
2635
}

Samples/SwiftKitSampleApp/com_example_swift_HelloJava2Swift.h

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/Java2SwiftLib/JavaClassTranslator.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,8 @@ extension JavaClassTranslator {
546546
let resultType = try translator.getSwiftTypeNameAsString(
547547
javaMethod.getGenericReturnType()!,
548548
preferValueTypes: true,
549-
outerOptional: .implicitlyUnwrappedOptional
549+
// outerOptional: .implicitlyUnwrappedOptional // FIXME: why? it's allowed to return null objects hmmm
550+
outerOptional: .optional // FIXME: why? it's allowed to return null objects hmmm
550551
)
551552

552553
// FIXME: cleanup the checking here
@@ -555,6 +556,7 @@ extension JavaClassTranslator {
555556
} else {
556557
resultTypeStr = ""
557558
}
559+
assert(!resultTypeStr.contains("!"), "contained !: '\(resultTypeStr)'")
558560

559561
let throwsStr = javaMethod.throwsCheckedException ? "throws" : ""
560562
let swiftMethodName = javaMethod.getName().escapedSwiftName
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import JavaTypes
16+
17+
// TODO: extension JavaValue: CustomStringConvertible where JNIType == jobject? {
18+
extension JavaInteger: CustomStringConvertible {
19+
public var description: String {
20+
"JavaKit.\(Self.self)(\(toString()))"
21+
}
22+
}
23+
24+
extension JavaInteger: JavaValue {
25+
public typealias JNIType = jobject?
26+
27+
public static var jvalueKeyPath: WritableKeyPath<jvalue, JNIType> { \.l }
28+
29+
public static var javaType: JavaType {
30+
.class(package: "java.lang", name: "Integer")
31+
}
32+
33+
// FIXME: cannot implement in extension, need to fix source generator
34+
// public required init(fromJNI value: JNIType, in environment: JNIEnvironment) {
35+
// fatalError()
36+
// }
37+
38+
39+
public func getJNIValue(in environment: JNIEnvironment) -> JNIType {
40+
fatalError()
41+
}
42+
43+
public static func jniMethodCall(in environment: JNIEnvironment) -> JNIMethodCall<JNIType> {
44+
environment.interface.CallObjectMethodA
45+
}
46+
47+
public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet<JNIType> {
48+
environment.interface.GetObjectField
49+
}
50+
51+
public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet<JNIType> {
52+
environment.interface.SetObjectField
53+
}
54+
55+
public static func jniStaticMethodCall(in environment: JNIEnvironment) -> JNIStaticMethodCall<JNIType> {
56+
environment.interface.CallStaticObjectMethodA
57+
}
58+
59+
public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet<JNIType> {
60+
environment.interface.GetStaticObjectField
61+
}
62+
63+
public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet<JNIType> {
64+
environment.interface.SetStaticObjectField
65+
}
66+
67+
public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray {
68+
return { environment, size in
69+
// FIXME: Introduce a JavaString class that we can use for this.
70+
let clazz = environment.interface.FindClass(environment, "java/lang/Integer")
71+
return environment.interface.NewObjectArray(environment, size, clazz, nil)
72+
}
73+
}
74+
75+
public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion<JNIType> {
76+
return { environment, array, start, length, outPointer in
77+
let buffer = UnsafeMutableBufferPointer(start: outPointer, count: Int(length))
78+
for i in 0..<length {
79+
buffer.initializeElement(
80+
at: Int(i),
81+
to: environment.interface.GetObjectArrayElement(environment, array, Int32(start + i))
82+
)
83+
}
84+
}
85+
}
86+
87+
public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion<JNIType> {
88+
return { environment, array, start, length, outPointer in
89+
let buffer = UnsafeBufferPointer(start: outPointer, count: Int(length))
90+
for i in start..<start + length {
91+
environment.interface.SetObjectArrayElement(environment, array, i, buffer[Int(i)])
92+
}
93+
}
94+
}
95+
96+
public static var jniPlaceholderValue: jobject? {
97+
nil
98+
}
99+
}

Sources/JavaKit/JavaObject+MethodCalls.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ extension AnyJavaObject {
187187
parameterTypes: repeat (each Param).self,
188188
resultType: Result.self
189189
)
190+
print("[swift][jni] Call for result \(Result.self)")
190191
return try javaMethodCall(
191192
method: methodID,
192193
args: repeat each arguments

Sources/JavaKit/JavaObjectHolder.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import JavaRuntime
1717
/// Stores a reference to a Java object, managing it as a global reference so
1818
/// that the Java virtual machine will not move or deallocate the object
1919
/// while this instance is live.
20-
public class JavaObjectHolder {
20+
public final class JavaObjectHolder {
21+
// FIXME: thread safety!
2122
public private(set) var object: jobject?
2223
public let environment: JNIEnvironment
2324

@@ -38,6 +39,6 @@ public class JavaObjectHolder {
3839
}
3940

4041
deinit {
41-
self.forget()
42+
// self.forget()
4243
}
4344
}

Sources/JavaKit/generated/JavaInteger.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import JavaRuntime
33

44
@JavaClass("java.lang.Integer")
55
open class JavaInteger: JavaNumber {
6+
7+
// FIXME: move ot extension?
8+
public required init(fromJNI value: JNIType, in environment: JNIEnvironment) {
9+
fatalError()
10+
}
11+
612
@JavaMethod
713
@_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil)
814

0 commit comments

Comments
 (0)