From cbb53beb30a96dd1bb2c229b3ba1b370c88636e7 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Fri, 14 Aug 2026 18:43:30 +0200 Subject: [PATCH] jextract: Support Java DoublePredicate func interface --- .../MySwiftLibrary/MySwiftLibrary.swift | 4 ++ .../com/example/swift/MySwiftLibraryTest.java | 6 ++ .../MySwiftLibrary/MySwiftLibrary.swift | 4 ++ .../com/example/swift/MySwiftLibraryTest.java | 6 ++ .../Sources/MySwiftLibrary/Closures.swift | 4 ++ .../java/com/example/swift/ClosuresTest.java | 6 ++ .../ExampleSwiftLibrary/MySwiftLibrary.swift | 4 ++ .../JavaTypes/JavaType+JDK.swift | 5 ++ .../KnownFunctionalInterfaces.swift | 10 +++ .../FuncCallbackImportTests.swift | 44 +++++++++++++ .../JNI/JNIClosureTests.swift | 63 +++++++++++++++++-- 11 files changed, 150 insertions(+), 6 deletions(-) diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index 26a871d22..530c821d5 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -72,6 +72,10 @@ public func globalCallMeLongPredicate(run: (Int64) -> Bool) -> Bool { run(1) } +public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { + run(1.0) +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java index a4cb1714b..a809d299c 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -104,4 +104,10 @@ void call_globalCallMeLongPredicate_noThrow() { boolean result = MySwiftLibrary.globalCallMeLongPredicate((long a) -> { return true; }); assertEquals(true, result); } + + @Test + void call_globalCallMeDoublePredicate_noThrow() { + boolean result = MySwiftLibrary.globalCallMeDoublePredicate((double a) -> { return true; }); + assertEquals(true, result); + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index f374125d4..cd2ceefd7 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -84,6 +84,10 @@ public func globalCallMeLongPredicate(run: (Int64) -> Bool) -> Bool { run(1) } +public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { + run(1.0) +} + public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { buf.count } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index b85b05b02..70658e008 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -216,4 +216,10 @@ void call_globalCallMeLongPredicate_noThrow() { boolean result = MySwiftLibrary.globalCallMeLongPredicate((long a) -> { return true; }); assertEquals(true, result); } + + @Test + void call_globalCallMeDoublePredicate_noThrow() { + boolean result = MySwiftLibrary.globalCallMeDoublePredicate((double a) -> { return true; }); + assertEquals(true, result); + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index e57c50b25..91350a839 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -57,6 +57,10 @@ public func globalCallMeLongPredicate(run: (Int64) -> Bool) -> Bool { run(1) } +public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { + run(1.0) +} + public func closureMultipleArguments( input1: Int64, input2: Int64, diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java index 6b53ed31c..473afbcf0 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -98,4 +98,10 @@ void globalCallMeLongPredicate() { boolean result = MySwiftLibrary.globalCallMeLongPredicate((long a) -> { return true; }); assertEquals(true, result); } + + @Test + void globalCallMeDoublePredicate() { + boolean result = MySwiftLibrary.globalCallMeDoublePredicate((double a) -> { return true; }); + assertEquals(true, result); + } } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 51540e78b..495fd41f1 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -79,6 +79,10 @@ public func globalCallMeLongPredicate(run: (Int64) -> Bool) -> Bool { run(1) } +public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { + run(1.0) +} + public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { buf.count } diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 188241871..cbeb4b3dd 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -75,6 +75,11 @@ extension JavaType { .class(package: "java.util.function", name: "LongPredicate") } + /// The description of the type java.util.function.DoublePredicate. + static var javaUtilFunctionDoublePredicate: JavaType { + .class(package: "java.util.function", name: "DoublePredicate") + } + /// The description of the type java.lang.Class. static var javaLangClass: JavaType { .class(package: "java.lang", name: "Class") diff --git a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift index 7d7fb23e8..2a2a66fa0 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -92,6 +92,13 @@ struct KnownJavaFunctionalInterface: Sendable { result: .boolean ) + static let doublePredicate = KnownJavaFunctionalInterface( + JavaType.javaUtilFunctionDoublePredicate, + method: "test", + parameters: [.double], + result: .boolean + ) + static let all: [KnownJavaFunctionalInterface] = [ .runnable, .booleanSupplier, @@ -103,6 +110,7 @@ struct KnownJavaFunctionalInterface: Sendable { .doubleConsumer, .intPredicate, .longPredicate, + .doublePredicate, ] static func find(parameters: [JavaType], result: JavaType) -> KnownJavaFunctionalInterface? { @@ -170,6 +178,8 @@ struct KnownJavaFunctionalInterface: Sendable { intPredicate case _ where parameter.isInt64: longPredicate + case _ where parameter.isDouble: + doublePredicate default: nil } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 71b38fced..9add79130 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -45,6 +45,7 @@ final class FuncCallbackImportTests { public func callMeIntPredicate(callback: (Int32) -> Bool) public func callMeLongPredicate(callback: (Int64) -> Bool) + public func callMeDoublePredicate(callback: (Double) -> Bool) public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) public func withBuffer(body: (UnsafeRawBufferPointer) -> Int) @@ -673,6 +674,49 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func callMecallMeDoublePredicateFunc(callback: (Double) -> Bool)") + func func_callMecallMeDoublePredicateFunc_callback() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = makeSwiftJavaAnalyzer(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) + + let funcDecl = st.extractedGlobalFuncs.first { $0.name == "callMeDoublePredicate" }! + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = JavaPrinter.toString { printer in + generator.printFunctionDowncallMethods(&printer, funcDecl) + } + + assertOutput( + output, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func callMeDoublePredicate(callback: (Double) -> Bool) + * } + */ + public static void callMeDoublePredicate(java.util.function.DoublePredicate callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeDoublePredicate_callback.call(callMeDoublePredicate.$toUpcallStub(callback, arena$)); + } + } + """ + ] + ) + } + @Test("Import: public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)") func func_withBuffer_body() throws { var config = Configuration() diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 1cd9e976c..c91e08eea 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -31,6 +31,7 @@ struct JNIClosureTests { public func closureIntPredicate(closure: (Int32) -> Bool) {} public func closureLongPredicate(closure: (Int64) -> Bool) {} + public func closureDoublePredicate(closure: (Double) -> Bool) {} public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) {} """ @@ -285,6 +286,31 @@ struct JNIClosureTests { ) } + @Test + func closureDoublePredicate_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureDoublePredicate(closure: (Double) -> Bool) + * } + */ + public static void closureDoublePredicate(java.util.function.DoublePredicate closure) { + SwiftModule.$closureDoublePredicate(closure); + } + """, + """ + private static native void $closureDoublePredicate(java.util.function.DoublePredicate closure); + """, + ] + ) + } + @Test func emptyClosure_swiftThunks() throws { try assertOutput( @@ -460,6 +486,31 @@ struct JNIClosureTests { ) } + @Test + func closureDoubleConsumer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureDoubleConsumer__Ljava_util_function_DoubleConsumer_2") + public func Java_com_example_swift_SwiftModule__00024closureDoubleConsumer__Ljava_util_function_DoubleConsumer_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureDoubleConsumer(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "accept", "(D)V")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [_0.getJValue(in: environment)] + environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + } + ) + } + """ + ] + ) + } + @Test func closureIntPredicate_swiftThunks() throws { try assertOutput( @@ -511,7 +562,7 @@ struct JNIClosureTests { } @Test - func closureDoubleConsumer_swiftThunks() throws { + func closureDoublePredicate_swiftThunks() throws { try assertOutput( input: source, .jni, @@ -519,14 +570,14 @@ struct JNIClosureTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024closureDoubleConsumer__Ljava_util_function_DoubleConsumer_2") - public func Java_com_example_swift_SwiftModule__00024closureDoubleConsumer__Ljava_util_function_DoubleConsumer_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { - SwiftModule.closureDoubleConsumer(closure: { + @_cdecl("Java_com_example_swift_SwiftModule__00024closureDoublePredicate__Ljava_util_function_DoublePredicate_2") + public func Java_com_example_swift_SwiftModule__00024closureDoublePredicate__Ljava_util_function_DoublePredicate_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureDoublePredicate(closure: { let class$ = environment.interface.GetObjectClass(environment, closure) - let methodID$ = environment.interface.GetMethodID(environment, class$, "accept", "(D)V")! + let methodID$ = environment.interface.GetMethodID(environment, class$, "test", "(D)Z")! environment.interface.DeleteLocalRef(environment, class$) let arguments$: [jvalue] = [_0.getJValue(in: environment)] - environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + return Bool(fromJNI: environment.interface.CallBooleanMethodA(environment, closure, methodID$, arguments$), in: environment) } ) }