Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)])
Expand Down Expand Up @@ -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)")
Expand Down
1 change: 1 addition & 0 deletions Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,5 @@ extension JavaType {
static var javaUtilUUID: JavaType {
.class(package: "java.util", name: "UUID")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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: () -> ())` | ✅ | ✅ |
Expand Down Expand Up @@ -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.
> as additional `box.as(FishBox.class)` conversion methods in the future.
107 changes: 107 additions & 0 deletions Tests/JExtractSwiftTests/ByteArrayTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<JNIEnv?>!, 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<JNIEnv?>!, 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<JNIEnv?>!, 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);",
]
)
}
}
Loading