diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift new file mode 100644 index 00000000..962ec502 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftJava + +/// Sum all bytes in the buffer +public func sumOfBytes(data: UnsafeRawBufferPointer) -> Int64 { + var sum: Int64 = 0 + for byte in data { + sum += Int64(byte) + } + return sum +} + +/// Return the count of bytes in the buffer +public func bufferCount(data: UnsafeRawBufferPointer) -> Int64 { + Int64(data.count) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java new file mode 100644 index 00000000..98542e26 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 static org.junit.jupiter.api.Assertions.*; + +public class UnsafeRawBufferPointerTest { + @Test + void sumOfBytes() { + byte[] input = new byte[] { 1, 2, 3, 4, 5 }; + assertEquals(15, MySwiftLibrary.sumOfBytes(input)); + } + + @Test + void sumOfBytes_empty() { + byte[] input = new byte[] {}; + assertEquals(0, MySwiftLibrary.sumOfBytes(input)); + } + + @Test + void bufferCount() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertEquals(4, MySwiftLibrary.bufferCount(input)); + } + + @Test + void bufferCount_empty() { + byte[] input = new byte[] {}; + assertEquals(0, MySwiftLibrary.bufferCount(input)); + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index dc28006b..4c76ef37 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -242,7 +242,7 @@ extension JNISwift2JavaGenerator { let javaName = javaIdentifiers.makeJavaMethodName(decl) // Swift -> Java - var translatedFunctionSignature = try translate( + var translatedFunctionSignature = try self.translate( functionSignature: decl.functionSignature, methodName: javaName, parentName: parentName, @@ -509,6 +509,12 @@ extension JNISwift2JavaGenerator { case .foundationData, .essentialsData: break // Handled as wrapped struct + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .array(.byte)), + conversion: .placeholder + ) + case .foundationUUID, .essentialsUUID: return TranslatedParameter( parameter: JavaParameter(name: parameterName, type: .javaUtilUUID), diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 6b010794..e9ad13d7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -125,6 +125,16 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .array(.byte)) + ], + conversion: .jniByteArrayToUnsafeRawBufferPointer(.placeholder, name: parameterName), + indirectConversion: nil, + conversionCheck: nil + ) + case .foundationDate, .essentialsDate, .foundationData, .essentialsData: // Handled as wrapped struct break @@ -1154,6 +1164,9 @@ extension JNISwift2JavaGenerator { /// `SwiftType(inner)` indirect case labelessInitializer(NativeSwiftConversionStep, swiftType: SwiftType) + /// Converts a jbyteArray to UnsafeRawBufferPointer via GetByteArrayElements + indirect case jniByteArrayToUnsafeRawBufferPointer(NativeSwiftConversionStep, name: String) + /// Constructs a Swift tuple from individually-converted elements. /// E.g. `(label0: conv0, conv1)` for `(label0: Int, String)` indirect case tupleConstruct(elements: [(label: String?, conversion: NativeSwiftConversionStep)]) @@ -1713,6 +1726,21 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(\(inner))" + case .jniByteArrayToUnsafeRawBufferPointer(let inner, let name): + let inner = inner.render(&printer, placeholder) + let countVar = "\(name)$count" + let ptrVar = "\(name)$ptr" + let rbpVar = "\(name)$rbp" + printer.print( + """ + let \(countVar) = Int(environment.interface.GetArrayLength(environment, \(inner))) + let \(ptrVar) = environment.interface.GetByteArrayElements(environment, \(inner), nil)! + defer { environment.interface.ReleaseByteArrayElements(environment, \(inner), \(ptrVar), jint(JNI_ABORT)) } + let \(rbpVar) = UnsafeRawBufferPointer(start: \(ptrVar), count: \(countVar)) + """ + ) + return rbpVar + case .tupleConstruct(let elements): let parts = elements.enumerated().map { idx, element in let converted = element.conversion.render(&printer, "\(placeholder)_\(idx)") diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 5f52d87c..34cb7f45 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -58,4 +58,5 @@ extension JavaType { static var javaUtilUUID: JavaType { .class(package: "java.util", name: "UUID") } + } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 87bb5047..20e34b3a 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -93,7 +93,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Operators: `+`, `-`, user defined | ❌ | ❌ | | Subscripts: `subscript()` | ✅ | ✅ | | Equatable | ❌ | ❌ | -| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Pointers: `UnsafeRawPointer` | 🟡 | ❌ | +| Pointers as parameters: `UnsafeRawBufferPointer` (as `byte[]`) | ❌ | ✅ | | Nested types: `struct Hello { struct World {} }` | ❌ | ✅ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | @@ -461,4 +462,4 @@ public final class FishBox ... { ``` > NOTE: Currently no helpers are available to convert between unspecialized types to specialized ones, but this can be offered -> as additional `box.as(FishBox.class)` conversion methods in the future. \ No newline at end of file +> as additional `box.as(FishBox.class)` conversion methods in the future. diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index 79113ec3..7ca45f07 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -183,4 +183,111 @@ final class ByteArrayTests { expectedChunks: expectedSwiftChunks ) } + + // ==== ----------------------------------------------------------------------- + // MARK: JNI mode tests + + @Test("Import: accept [UInt8] array (JNI)") + func func_accept_array_uint8_jni() throws { + let text = "public func acceptArray(array: [UInt8])" + try assertOutput( + input: text, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024acceptArray___3B") + public func Java_com_example_swift_SwiftModule__00024acceptArray___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) { + SwiftModule.acceptArray(array: [UInt8](fromJNI: array, in: environment)) + } + """ + ] + ) + try assertOutput( + input: text, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static void acceptArray(@Unsigned byte[] array) { + SwiftModule.$acceptArray(Objects.requireNonNull(array, "array must not be null")); + } + """, + "private static native void $acceptArray(byte[] array);", + ] + ) + } + + @Test("Import: return [UInt8] array (JNI)") + func func_return_array_uint8_jni() throws { + let text = "public func returnArray() -> [UInt8]" + try assertOutput( + input: text, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024returnArray__") + public func Java_com_example_swift_SwiftModule__00024returnArray__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jbyteArray? { + return SwiftModule.returnArray().getJNILocalRefValue(in: environment) + } + """ + ] + ) + try assertOutput( + input: text, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + @Unsigned + public static byte[] returnArray() { + return SwiftModule.$returnArray(); + } + """, + "private static native byte[] $returnArray();", + ] + ) + } + + @Test("Import: accept UnsafeRawBufferPointer (JNI)") + func func_accept_unsafeRawBufferPointer_jni() throws { + let text = "public func receiveBuffer(data: UnsafeRawBufferPointer)" + try assertOutput( + input: text, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024receiveBuffer___3B") + public func Java_com_example_swift_SwiftModule__00024receiveBuffer___3B(environment: UnsafeMutablePointer!, thisClass: jclass, data: jbyteArray?) { + let data$count = Int(environment.interface.GetArrayLength(environment, data)) + let data$ptr = environment.interface.GetByteArrayElements(environment, data, nil)! + defer { environment.interface.ReleaseByteArrayElements(environment, data, data$ptr, jint(JNI_ABORT)) } + let data$rbp = UnsafeRawBufferPointer(start: data$ptr, count: data$count) + SwiftModule.receiveBuffer(data: data$rbp) + } + """ + ] + ) + try assertOutput( + input: text, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static void receiveBuffer(byte[] data) { + SwiftModule.$receiveBuffer(data); + } + """, + "private static native void $receiveBuffer(byte[] data);", + ] + ) + } }