diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift new file mode 100644 index 00000000..00e2533c --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public struct MySwiftStruct { + private var cap: Int64 + public var len: Int64 + + public init(cap: Int64, len: Int64) { + self.cap = cap + self.len = len + } + + public func getCapacity() -> Int64 { + self.cap + } + + public mutating func increaseCap(by value: Int64) -> Int64 { + precondition(value > 0) + self.cap += value + return self.cap + } +} diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 8e7ca284..21cad317 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -52,6 +52,12 @@ static void examples() { } catch (Exception e) { System.out.println("Caught exception: " + e.getMessage()); } + + MySwiftStruct myStruct = MySwiftStruct.init(12, 34, arena); + System.out.println("myStruct.cap: " + myStruct.getCapacity()); + System.out.println("myStruct.len: " + myStruct.getLen()); + myStruct.increaseCap(10); + System.out.println("myStruct.cap after increase: " + myStruct.getCapacity()); } System.out.println("DONE."); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java new file mode 100644 index 00000000..9eeaf029 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftStructTest { + @Test + void init() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + assertEquals(1337, s.getCapacity()); + assertEquals(42, s.getLen()); + } + } + + @Test + void getAndSetLen() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + s.setLen(100); + assertEquals(100, s.getLen()); + } + } + + @Test + void increaseCap() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long newCap = s.increaseCap(10); + assertEquals(1347, newCap); + assertEquals(1347, s.getCapacity()); + } + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift new file mode 100644 index 00000000..0a883ed1 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIStructTests { + let source = """ + public struct MyStruct { + let x: Int64 + let y: Int64 + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public func doSomething(x: Int64) {} + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + + public final class MyStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + public MyStruct(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + MyStruct.$destroy($selfPointer); + } + }; + } + """ + ]) + } + + @Test + func initializer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ + public static MyStruct init(long x, long y, SwiftArena swiftArena$) { + long selfPointer = MyStruct.allocatingInit(x, y); + return new MyStruct(selfPointer, swiftArena$); + } + """, + """ + private static native long allocatingInit(long x, long y); + """, + ] + ) + } + + @Test + func initializer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") + func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test + func destroyFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024destroy__J") + func Java_com_example_swift_MyStruct__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + } + """ + ] + ) + } + + @Test + func memberMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ + public void doSomething(long x) { + long selfPointer = this.pointer(); + MyStruct.$doSomething(x, selfPointer); + } + """, + """ + private static native void $doSomething(long x, long selfPointer); + """ + ] + ) + } + + @Test + func memberMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") + func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + } + """, + ] + ) + } +}