diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java index d7ea7f22d..5ac8a2f69 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java @@ -42,6 +42,11 @@ public void greet(String name) { System.out.println("Salutations, " + name); } + // method called 'init' to check we're able to handle these and not clash with swift 'init' keyword + public long init(long value) { + return value; + } + public Predicate lessThanTen() { Predicate predicate = i -> (i < 10); return predicate; diff --git a/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitExampleRuntimeTests.swift similarity index 84% rename from Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift rename to Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitExampleRuntimeTests.swift index 9ed14cd05..c43bd4b64 100644 --- a/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift +++ b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitExampleRuntimeTests.swift @@ -19,16 +19,13 @@ import JavaUtilFunction import Testing @Suite -struct ManglingTests { +struct JavaKitExampleRuntimeTests { + + let jvm = try JavaKitSampleJVM.shared @Test func methodMangling() throws { - let jvm = try! JavaVirtualMachine.shared( - classpath: [ - ".build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java" - ] - ) - let env = try! jvm.environment() + let env = try jvm.environment() let helper = ThreadSafeHelperClass(environment: env) @@ -53,4 +50,14 @@ struct ManglingTests { #expect(#"Optional(21)"# == String(describing: longOpt)) } -} \ No newline at end of file + @Test + func methodNamedInit() throws { + let env = try jvm.environment() + + let hello = HelloSwift(environment: env) + + let reply = hello.`init`(128) + #expect(reply == 128) + } + +} diff --git a/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitSampleJVM.swift b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitSampleJVM.swift new file mode 100644 index 000000000..129575c4b --- /dev/null +++ b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitSampleJVM.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JavaKitExample + +import SwiftJava +import JavaUtilFunction + +/// Utility to configure the classpath and native libraries paths for writing tests against +/// classes defined in this JavaKitExample project +struct JavaKitSampleJVM { + + static var shared: JavaVirtualMachine = { + try! JavaVirtualMachine.shared( + classpath: [ + ".build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java", + ], + vmOptions: [ + KnownJavaVMOptions.javaLibraryPath(".build/\(SwiftPlatform.debugOrRelease)/"), + ] + ) + }() + +} \ No newline at end of file diff --git a/Samples/JavaKitSampleApp/ci-validate.sh b/Samples/JavaKitSampleApp/ci-validate.sh index 327baadf9..8568e115f 100755 --- a/Samples/JavaKitSampleApp/ci-validate.sh +++ b/Samples/JavaKitSampleApp/ci-validate.sh @@ -10,9 +10,14 @@ else DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts' fi -swift build $DISABLE_EXPERIMENTAL_PREBUILTS +swift build --build-tests $DISABLE_EXPERIMENTAL_PREBUILTS +echo "java application run: ..." "$JAVA_HOME/bin/java" \ -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \ -Djava.library.path=.build/debug \ "com.example.swift.JavaKitSampleMain" +echo "java application run: OK" + + +swift test $DISABLE_EXPERIMENTAL_PREBUILTS \ No newline at end of file diff --git a/Sources/SwiftJava/JNI.swift b/Sources/SwiftJava/JNI.swift index 161c520ae..0f334c607 100644 --- a/Sources/SwiftJava/JNI.swift +++ b/Sources/SwiftJava/JNI.swift @@ -28,7 +28,7 @@ package final class JNI { package fileprivate(set) static var shared: JNI? /// The default application class loader - package let applicationClassLoader: JavaClassLoader + package let applicationClassLoader: JavaClassLoader? init(fromVM javaVM: JavaVirtualMachine) { // Update the global JavaVM @@ -36,7 +36,20 @@ package final class JNI { $0 = javaVM } let environment = try! javaVM.environment() - self.applicationClassLoader = try! JavaClass(environment: environment).currentThread().getContextClassLoader() + do { + let clazz = try JavaClass(environment: environment) + guard let thread: JavaThread = clazz.currentThread() else { + applicationClassLoader = nil + return + } + guard let cl = thread.getContextClassLoader() else { + applicationClassLoader = nil + return + } + self.applicationClassLoader = cl + } catch { + fatalError("Failed to get current thread's ContextClassLoader: \(error)") + } } } diff --git a/Sources/SwiftJava/KnownJavaVMOptions.swift b/Sources/SwiftJava/KnownJavaVMOptions.swift new file mode 100644 index 000000000..0dffc2213 --- /dev/null +++ b/Sources/SwiftJava/KnownJavaVMOptions.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 KnownJavaVMOptions { + + /// Helper for the option to configure where native libraries should be searched for: `-Djava.library.path` + public static func javaLibraryPath(_ path: String) -> String { + return "-Djava.library.path=" + path + } + +} \ No newline at end of file diff --git a/Sources/SwiftJava/SwiftPlatform.swift b/Sources/SwiftJava/SwiftPlatform.swift new file mode 100644 index 000000000..08c1f42ea --- /dev/null +++ b/Sources/SwiftJava/SwiftPlatform.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI + +/// Helpers for forming platform specific directory names and paths. +public struct SwiftPlatform { + + public static var debugOrRelease: String { + #if DEBUG + "debug" + #else + "release" + #endif + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index 54c3178e5..3880ea61a 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -56,14 +56,17 @@ public final class _JNIMethodIDCache: Sendable { environment.interface.ExceptionClear(environment) // OK to force unwrap, we are in a jextract environment. - if let javaClass = try? JNI.shared!.applicationClassLoader.loadClass( + guard let jni = JNI.shared else { + fatalError("Cannot get JNI.shared, it should have been initialized by JNI_OnLoad when loading the library") + } + guard let javaClass = try? jni.applicationClassLoader?.loadClass( className.replacingOccurrences(of: "/", with: ".") - ) { - clazz = javaClass.javaThis - self.javaObjectHolder = javaClass.javaHolder - } else { + ) else { fatalError("Class \(className) could not be found!") } + + clazz = javaClass.javaThis + self.javaObjectHolder = javaClass.javaHolder } self._class = clazz diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 94140cf64..36d311dc7 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -698,6 +698,11 @@ extension JavaClassTranslator { } // Do we need to record any generic information, in order to enable type-erasure for the upcalls? var parameters: [String] = [] + // If the method name is "init", we need to explicitly specify it in the annotation + // because "init" is a Swift keyword and will be escaped in the function name via `init` + if javaMethod.getName() == "init" { + parameters.append("\"init\"") + } if hasTypeEraseGenericResultType { parameters.append("typeErasedResult: \"\(resultType)\"") } diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift index 462311aee..e6f00ee83 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift @@ -99,6 +99,7 @@ final class BasicWrapJavaTests: XCTestCase { class SuperClass { public static final long serialVersionUID = 1L; + public void init() throws Exception {} } class SubClass extends SuperClass { @@ -128,4 +129,37 @@ final class BasicWrapJavaTests: XCTestCase { ] ) } + + // Test that Java methods named "init" get @JavaMethod("init") annotation. + // Since "init" is a Swift keyword and gets escaped with backticks in the function name, + // we explicitly specify the Java method name in the annotation. + // See KeyAgreement.init() methods as a real-world example. + func test_wrapJava_initMethodAnnotation() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class TestClass { + public void init(String arg) throws Exception {} + public void init() throws Exception {} + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.TestClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod("init") + open func `init`(_ arg0: String) throws + """, + """ + @JavaMethod("init") + open func `init`() throws + """, + ] + ) + } }