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,28 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
import SwiftJava

public func echoUUID(_ uuid: UUID) -> UUID {
return uuid
}

public func makeUUID() -> UUID {
return UUID()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;

public class UUIDTest {
@Test
void echoUUID() {
var uuid = UUID.randomUUID();
assertEquals(uuid, MySwiftLibrary.echoUUID(uuid));
}

@Test
void makeUUID() {
var uuid = MySwiftLibrary.makeUUID();
assertEquals(4, uuid.version());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ extension SwiftKnownTypeDeclKind {
case .void: .void
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol,
.essentialsData, .essentialsDataProtocol, .optional, .foundationDate, .essentialsDate:
.essentialsData, .essentialsDataProtocol, .optional, .foundationDate, .essentialsDate, .foundationUUID, .essentialsUUID:
nil
}
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enum JNIJavaTypeTranslator {
.unsafePointer, .unsafeMutablePointer,
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer,
.optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array, .foundationDate, .essentialsDate:
.optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array, .foundationDate, .essentialsDate, .foundationUUID, .essentialsUUID:
return nil
}
}
Expand All @@ -62,7 +62,7 @@ enum JNIJavaTypeTranslator {
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer,
.optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol,
.array, .foundationDate, .essentialsDate:
.array, .foundationDate, .essentialsDate, .foundationUUID, .essentialsUUID:
nil
}
}
Expand All @@ -79,7 +79,7 @@ enum JNIJavaTypeTranslator {
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer,
.optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol,
.array, .foundationDate, .essentialsDate:
.array, .foundationDate, .essentialsDate, .foundationUUID, .essentialsUUID:
nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,12 @@ extension JNISwift2JavaGenerator {
// Handled as wrapped struct
break

case .foundationUUID, .essentialsUUID:
return TranslatedParameter(
parameter: JavaParameter(name: parameterName, type: .javaUtilUUID),
conversion: .method(.placeholder, function: "toString")
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
throw JavaTranslationError.unsupportedSwiftType(swiftType)
Expand Down Expand Up @@ -695,6 +701,17 @@ extension JNISwift2JavaGenerator {
// Handled as wrapped struct
break

case .foundationUUID, .essentialsUUID:
return TranslatedResult(
javaType: .javaUtilUUID,
outParameters: [],
conversion: .method(
.constant("java.util.UUID"),
function: "fromString",
arguments: [.placeholder]
)
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
throw JavaTranslationError.unsupportedSwiftType(swiftType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,33 @@ extension JNISwift2JavaGenerator {
// Handled as wrapped struct
break

case .foundationUUID, .essentialsUUID:
let uuidStringVariable = "\(parameterName)_string$"
let initUUIDStep = NativeSwiftConversionStep.unwrapOptional(
.method(
.constant("UUID"),
function: "init",
arguments: [("uuidString", .placeholder)]
),
name: parameterName,
fatalErrorMessage: "Invalid UUID string passed from Java: \\(\(uuidStringVariable))"
)

return NativeParameter(
parameters: [
JavaParameter(name: parameterName, type: .javaLangString)
],
conversion: .replacingPlaceholder(
.aggregate(
variable: uuidStringVariable,
[initUUIDStep]
),
placeholder: .initFromJNI(.placeholder, swiftType: self.knownTypes.string)
),
indirectConversion: nil,
conversionCheck: nil
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),
javaType.implementsJavaValue else {
Expand Down Expand Up @@ -515,6 +542,13 @@ extension JNISwift2JavaGenerator {
// Handled as wrapped struct
break

case .foundationUUID, .essentialsUUID:
return NativeResult(
javaType: .javaLangString,
conversion: .getJNIValue(.member(.placeholder, member: "uuidString")),
outParameters: []
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else {
throw JavaTranslationError.unsupportedSwiftType(swiftResult.type)
Expand Down Expand Up @@ -766,6 +800,10 @@ extension JNISwift2JavaGenerator {

indirect case labelessAssignmentOfVariable(NativeSwiftConversionStep, swiftType: SwiftType)

indirect case aggregate(variable: String, [NativeSwiftConversionStep])

indirect case replacingPlaceholder(NativeSwiftConversionStep, placeholder: NativeSwiftConversionStep)

/// Returns the conversion string applied to the placeholder.
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
Expand Down Expand Up @@ -1166,8 +1204,21 @@ extension JNISwift2JavaGenerator {
}
}
return printer.finalize()

case .labelessAssignmentOfVariable(let name, let swiftType):
return "\(swiftType)(\(JNISwift2JavaGenerator.indirectVariableName(for: name.render(&printer, placeholder))))"

case .aggregate(let variable, let steps):
precondition(!steps.isEmpty, "Aggregate must contain steps")
printer.print("let \(variable) = \(placeholder)")
let steps = steps.map {
$0.render(&printer, variable)
}
return steps.last!

case .replacingPlaceholder(let inner, let newPlaceholder):
let newPlaceholder = newPlaceholder.render(&printer, placeholder)
return inner.render(&printer, newPlaceholder)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ extension JavaType {
static func future(_ T: JavaType) -> JavaType {
.class(package: "java.util.concurrent", name: "Future", typeParameters: [T.boxedType])
}

static var javaUtilUUID: JavaType {
.class(package: "java.util", name: "UUID")
}
}
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ private let foundationEssentialsSourceFile: SourceFileSyntax = """
/// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 1970 by a given number of seconds.
public init(timeIntervalSince1970: Double)
}

public struct UUID {}
"""

private var foundationSourceFile: SourceFileSyntax {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ enum SwiftKnownTypeDeclKind: String, Hashable {
case essentialsData = "FoundationEssentials.Data"
case foundationDate = "Foundation.Date"
case essentialsDate = "FoundationEssentials.Date"
case foundationUUID = "Foundation.UUID"
case essentialsUUID = "FoundationEssentials.UUID"

var moduleAndName: (module: String, name: String) {
let qualified = self.rawValue
Expand Down
7 changes: 5 additions & 2 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ struct SwiftKnownTypes {
var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) }
var unsafeRawBufferPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawBufferPointer])) }
var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) }

var string: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.string])) }

var foundationDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationDataProtocol])) }
var foundationData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationData])) }
var essentialsDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsDataProtocol])) }
var essentialsData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsData])) }

var foundationUUID: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationUUID])) }
var essentialsUUID: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsUUID]))}

/// `(UnsafeRawPointer, Long) -> ()` function type.
///
/// Commonly used to initialize a buffer using the passed bytes and length.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S
| Existential return types `f() -> any Collection` | ❌ | ❌ |
| Foundation Data and DataProtocol: `f(x: any DataProtocol) -> Data` | ✅ | ❌ |
| Foundation Date: `f(date: Date) -> Date` | ❌ | ✅ |
| Foundation UUID: `f(uuid: UUID) -> UUID` | ❌ | ✅ |
| Opaque parameters: `func take(worker: some Builder) -> some Builder` | ❌ | ✅ |
| Opaque return types: `func get() -> some Builder` | ❌ | ❌ |
| Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ |
Expand Down
115 changes: 115 additions & 0 deletions Tests/JExtractSwiftTests/UUIDTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//===----------------------------------------------------------------------===//
//
// 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 SwiftJavaConfigurationShared
import Testing

struct UUIDTests {
@Test(
"Import: accept UUID",
arguments: [
(
JExtractGenerationMode.jni,
/* expected Java chunks */
[
"""
public static void acceptUUID(java.util.UUID uuid) {
SwiftModule.$acceptUUID(uuid.toString());
}
"""
],
/* expected Swift chunks */
[
"""
@_cdecl("Java_com_example_swift_SwiftModule__00024acceptUUID__Ljava_lang_String_2")
public func Java_com_example_swift_SwiftModule__00024acceptUUID__Ljava_lang_String_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, uuid: jstring?) {
let uuid_string$ = String(fromJNI: uuid, in: environment)
guard let uuid_unwrapped$ = UUID.init(uuidString: uuid_string$) else {
fatalError("Invalid UUID string passed from Java: \\(uuid_string$)")
}
SwiftModule.acceptUUID(uuid: uuid_unwrapped$)
}
"""
],
)
]
)
func func_accept_uuid(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws {
let text =
"""
import Foundation

public func acceptUUID(uuid: UUID)
"""

try assertOutput(
input: text,
mode, .java,
detectChunkByInitialLines: 1,
expectedChunks: expectedJavaChunks)

try assertOutput(
input: text,
mode, .swift,
detectChunkByInitialLines: 1,
expectedChunks: expectedSwiftChunks)
}

@Test(
"Import: return UUID",
arguments: [
(
JExtractGenerationMode.jni,
/* expected Java chunks */
[
"""
public static java.util.UUID returnUUID() {
return java.util.UUID.fromString(SwiftModule.$returnUUID());
}
"""
],
/* expected Swift chunks */
[
"""
@_cdecl("Java_com_example_swift_SwiftModule__00024returnUUID__")
public func Java_com_example_swift_SwiftModule__00024returnUUID__(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass) -> jstring? {
return SwiftModule.returnUUID().uuidString.getJNIValue(in: environment)
}
"""
]
)
]
)
func func_return_UUID(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws {
let text =
"""
import Foundation
public func returnUUID() -> UUID
"""

try assertOutput(
input: text,
mode, .java,
detectChunkByInitialLines: 1,
expectedChunks: expectedJavaChunks
)

try assertOutput(
input: text,
mode, .swift,
detectChunkByInitialLines: 1,
expectedChunks: expectedSwiftChunks)
}
}
Loading