diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 3fdd5d4b..90854db0 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -12,7 +12,7 @@ runs: with: distribution: ${{ matrix.jdk_vendor }} java-version: | - 24 + 25 17 cache: 'gradle' - name: Set JAVA_HOME_{N} @@ -23,10 +23,10 @@ runs: elif [[ -n "$JAVA_HOME_21_ARM64" ]]; then echo "JAVA_HOME_21=$JAVA_HOME_21_ARM64" >> $GITHUB_ENV fi - if [[ -n "$JAVA_HOME_24_X64" ]]; then - echo "JAVA_HOME_24=$JAVA_HOME_24_X64" >> $GITHUB_ENV - elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then - echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV + if [[ -n "$JAVA_HOME_25_X64" ]]; then + echo "JAVA_HOME_25=$JAVA_HOME_25_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_25_ARM64" ]]; then + echo "JAVA_HOME_25=$JAVA_HOME_25_ARM64" >> $GITHUB_ENV fi # - name: Check Java environment # shell: bash diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ffc42e3c..a0f2eda5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,8 @@ name: pull_request +permissions: + contents: read + on: pull_request: types: [opened, reopened, synchronize] @@ -23,11 +26,11 @@ jobs: name: Documentation check runs-on: ubuntu-latest container: - image: 'swift:6.1-noble' + image: 'swift:6.2-noble' strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] steps: @@ -35,7 +38,7 @@ jobs: - name: Prepare CI Environment uses: ./.github/actions/prepare_env - name: Swift Build - run: swift build + run: swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - name: Run documentation check run: ./.github/scripts/validate_docs.sh @@ -45,7 +48,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -71,7 +74,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -95,7 +98,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -109,13 +112,35 @@ jobs: - name: Gradle compile JMH benchmarks run: ./gradlew compileJmh --info + benchmark-swift: + name: Benchmark (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + swift_version: ['6.2'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install jemalloc + run: apt-get update && apt-get install -y libjemalloc-dev + - name: Swift Benchmarks + run: swift package --package-path Benchmarks/ --disable-experimental-prebuilts benchmark # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + test-swift: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -127,9 +152,9 @@ jobs: - name: Prepare CI Environment uses: ./.github/actions/prepare_env - name: Swift Build - run: "swift build --build-tests --disable-sandbox" + run: swift build --build-tests --disable-sandbox --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - name: Swift Test - run: "swift test" + run: swift test --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 test-swift-macos: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) @@ -137,7 +162,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -151,13 +176,37 @@ jobs: - name: Swift Test run: "swift test" + build-swift-android: + name: Sample SwiftJavaExtractJNISampleApp (Android) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} android:${{matrix.sdk_triple}}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: ['nightly-main'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + sdk_triple: ['x86_64-unknown-linux-android28'] + ndk_version: ['r27d'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install Swift SDK for Android and build + run: | + apt-get -q update && apt-get -yq install curl + cd Samples/SwiftJavaExtractJNISampleApp + curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/install-and-build-with-sdk.sh | \ + bash -s -- --android --build-command="swift build" --android-sdk-triple="${{ matrix.sdk_triple }}" --android-ndk-version="${{ matrix.ndk_version }}" ${{ matrix.swift_version }} + verify-samples: name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -185,7 +234,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] # no nightly testing on macOS + swift_version: ['6.2'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts index df0a6633..1f2df6e5 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts @@ -23,7 +23,7 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(24) + languageVersion = JavaLanguageVersion.of(25) } } diff --git a/Package.swift b/Package.swift index 1c0e9481..7d872795 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -14,6 +14,9 @@ func findJavaHome() -> String { print("JAVA_HOME = \(home)") return home } + if let opts = ProcessInfo.processInfo.environment["SWIFT_JAVA_JAVA_OPTS"] { + print("SWIFT_JAVA_JAVA_OPTS = \(opts)") + } // This is a workaround for envs (some IDEs) which have trouble with // picking up env variables during the build process @@ -169,9 +172,14 @@ let package = Package( // Support library written in Swift for SwiftKit "Java" .library( - name: "SwiftKitSwift", + name: "SwiftJavaRuntimeSupport", + targets: ["SwiftJavaRuntimeSupport"] + ), + + .library( + name: "SwiftRuntimeFunctions", type: .dynamic, - targets: ["SwiftKitSwift"] + targets: ["SwiftRuntimeFunctions"] ), .library( @@ -197,9 +205,10 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-log", from: "1.2.0"), // // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package; // // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions @@ -213,7 +222,8 @@ let package = Package( name: "SwiftJavaDocumentation", dependencies: [ "SwiftJava", - "SwiftKitSwift", + "SwiftJavaRuntimeSupport", + "SwiftRuntimeFunctions", ] ), @@ -245,7 +255,8 @@ let package = Package( exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])), + .unsafeFlags(["-Xfrontend", "-sil-verify-none"], .when(configuration: .release)), // Workaround for https://github.com/swiftlang/swift/issues/84899 ], linkerSettings: [ .unsafeFlags( @@ -350,8 +361,11 @@ let package = Package( ] ), .target( - name: "SwiftKitSwift", - dependencies: [], + name: "SwiftJavaRuntimeSupport", + dependencies: [ + "CSwiftJavaJNI", + "SwiftJava" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -359,13 +373,21 @@ let package = Package( ), .target( - name: "CSwiftJavaJNI", + name: "SwiftRuntimeFunctions", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), + .target( + name: "CSwiftJavaJNI", + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) + ] + ), + .target( name: "SwiftJavaConfigurationShared" ), @@ -377,6 +399,7 @@ let package = Package( .target( name: "SwiftJavaToolLib", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 49ed3c4d..8d0be455 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -93,6 +93,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { $0.pathExtension == "swift" } + // Output Swift files are just Java filename based converted to Swift files one-to-one var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in guard sourceFileURL.isFileURL else { return nil as URL? @@ -102,7 +103,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { guard sourceFilePath.starts(with: sourceDir) else { fatalError("Could not get relative path for source file \(sourceFilePath)") } - var outputURL = outputSwiftDirectory + let outputURL = outputSwiftDirectory .appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1))) let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent @@ -116,11 +117,12 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // If the module uses 'Data' type, the thunk file is emitted as if 'Data' is declared // in that module. Declare the thunk file as the output. - // FIXME: Make this conditional. outputSwiftFiles += [ - outputSwiftDirectory.appending(path: "Data+SwiftJava.swift") + outputSwiftDirectory.appending(path: "Foundation+SwiftJava.swift") ] + print("[swift-java-plugin] Output swift files:\n - \(outputSwiftFiles.map({$0.absoluteString}).joined(separator: "\n - "))") + return [ .buildCommand( displayName: "Generate Java wrappers for Swift types", diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index e12e13e8..f90150fd 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -30,11 +30,11 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // Note: Target doesn't have a directoryURL counterpart to directory, // so we cannot eliminate this deprecation warning. - let sourceDir = target.directory.string + let sourceDir = URL(filePath: target.directory.string) // The name of the configuration file SwiftJava.config from the target for // which we are generating Swift wrappers for Java classes. - let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") + let configFile = sourceDir.appending(path: "swift-java.config") let config: Configuration? if let configData = try? Data(contentsOf: configFile) { @@ -51,13 +51,14 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { } let sourceFilePath = sourceFileURL.path - guard sourceFilePath.starts(with: sourceDir) else { + let sourceDirPath = sourceDir.path + guard sourceFilePath.starts(with: sourceDirPath) else { fatalError("Could not get relative path for source file \(sourceFilePath)") } return URL(filePath: context.pluginWorkDirectoryURL.path) .appending(path: "Java") - .appending(path: String(sourceFilePath.dropFirst(sourceDir.count))) + .appending(path: String(sourceFilePath.dropFirst(sourceDirPath.count))) .deletingPathExtension() .appendingPathExtension("class") } @@ -65,14 +66,19 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { let javaHome = URL(filePath: findJavaHome()) let javaClassFileURL = context.pluginWorkDirectoryURL .appending(path: "Java") + #if os(Windows) + let javac = "javac.exe" + #else + let javac = "javac" + #endif return [ .buildCommand( displayName: "Compiling \(javaFiles.count) Java files for target \(sourceModule.name) to \(javaClassFileURL)", executable: javaHome .appending(path: "bin") - .appending(path: "javac"), - arguments: javaFiles.map { $0.path(percentEncoded: false) } + [ - "-d", javaClassFileURL.path(), + .appending(path: javac), + arguments: javaFiles.map { $0.path } + [ + "-d", javaClassFileURL.path, "-parameters", // keep parameter names, which allows us to emit them in generated Swift decls ] + (config?.compilerVersionArgs ?? []), inputFiles: javaFiles, diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 863cf99c..8278c645 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -66,12 +66,12 @@ extension PluginContext { .appending(path: "generated") .appending(path: "java") } - + var outputSwiftDirectory: URL { self.pluginWorkDirectoryURL .appending(path: "Sources") } - + func cachedClasspathFile(swiftModule: String) -> URL { self.pluginWorkDirectoryURL .appending(path: "\(swiftModule)", directoryHint: .notDirectory) diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 57641eff..7908932d 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -165,6 +165,11 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("No dependencies to fetch for target \(sourceModule.name)") } + // Add all the core Java stdlib modules as --depends-on + let javaStdlibModules = getExtractedJavaStdlibModules() + log("Include Java standard library SwiftJava modules: \(javaStdlibModules)") + arguments += javaStdlibModules.flatMap { ["--depends-on", $0] } + if !outputSwiftFiles.isEmpty { arguments += [ configFile.path(percentEncoded: false) ] @@ -236,3 +241,29 @@ extension SwiftJavaBuildToolPlugin { outputDirectory(context: context, generated: generated).appending(path: filename) } } + +func getExtractedJavaStdlibModules() -> [String] { + let fileManager = FileManager.default + let sourcesPath = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent("Sources") + .appendingPathComponent("JavaStdlib") + + guard let stdlibDirContents = try? fileManager.contentsOfDirectory( + at: sourcesPath, + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ) else { + return [] + } + + return stdlibDirContents.compactMap { url in + guard let resourceValues = try? url.resourceValues(forKeys: [.isDirectoryKey]), + let isDirectory = resourceValues.isDirectory, + isDirectory else { + return nil + } + return url.lastPathComponent + }.sorted() +} \ No newline at end of file diff --git a/README.md b/README.md index ff643c49..abca3b9d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,42 @@ # Swift Java Interoperability Tools and Libraries -This repository contains two approaches to Swift/Java interoperability. +This project contains tools and libraries that facilitate **Swift & Java Interoperability**. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. -- The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. +- The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. -## :construction: Early Development :construction: +## Introduction -**:construction: This is a *very early* prototype and everything is subject to change. :construction:** +If you'd like to check out a quick introduction to Swift & Java interoperability, you may be interested in this presentation from WWDC25: [WWDC25: Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA). -Parts of this project are incomplete, not fleshed out, and subject to change without any notice. +While we work on more quickstarts and documentation, please refer to the Sample projects located in [Samples/](Samples/) that showcase the various ways you can use swift-java in your Swift or Java projects. -The primary purpose of this repository is to create an environment for collaboration and joint exploration of the Swift/Java interoperability story. The project will transition to a more structured approach once key goals have been outlined. +## Dependencies + +### Required JDK versions -### :construction: Self-publish support Java libraries (SwiftKit) +Note that this project consists of multiple modules which currently have different Swift and Java runtime requirements. -While we work out how to provide the necessary support libraries for the Java side of Java code generated by `swift-java jextract`, -you will currently need to publish them locally and depend on them this way; +You'll need to install the necessary JDK version locally. On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using: + +```bash +$ brew install openjdk +# and create a symlink into /Library/Java/JavaVirtualMachines +$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk + +# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines +$ brew install --cask corretto +``` + +Alternatively, you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable: + +```bash +$ export JAVA_HOME="$(sdk home java current)" +``` + +## Self-publish supporting Java libraries + +Swift-java relies on supporting libraries that are under active development and not yet published to Maven Central. To use the project, you'll need to self-publish these libraries locally so your Java project can depend on them. To publish the libraries to your local maven repository (`$HOME/.m2`), you can run: @@ -36,29 +56,6 @@ repositories { We anticipate simplifying this in the future. -## Dependencies - -### Required JDK versions - -This project consists of different modules which have different Swift and Java runtime requirements. - -On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using - -```bash -$ brew install openjdk -# and create a symlink into /Library/Java/JavaVirtualMachines -$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk - -# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines -$ brew install --cask corretto -``` - -or you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable - -```bash -$ export JAVA_HOME="$(sdk home java current)" -``` - ## SwiftJava macros SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. @@ -66,8 +63,8 @@ SwiftJava is a Swift library offering macros which simplify writing JNI code "by It is possible to generate Swift bindings to Java libraries using SwiftJava by using the `swift-java wrap-java` command. Required language/runtime versions: -- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration -- **Swift 6.0.x**, because the library uses modern Swift macros +- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integratio +- **Swift 6.2.x**, because the library uses modern Swift macros **swift-java jextract** @@ -82,9 +79,9 @@ This does require the use of the relatively recent [JEP-454: Foreign Function & This is the primary way we envision calling Swift code from server-side Java libraries and applications. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files -- **JDK 24+** - - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-24. +- **Swift 6.2**, because of dependence on rich swift interface files +- **JDK 25+** + - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25. ## swift-java jextract --mode=jni @@ -94,7 +91,7 @@ This mode is more limited in some performance and flexibility that it can offer, We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **Java 7+**, including @@ -103,8 +100,8 @@ Required language/runtime versions: This project contains multiple builds, living side by side together. You will need to have: -- Swift (6.1.x+) -- Java (24+ for FFM, even though we support lower JDK targets) +- Swift (6.2.x+) +- Java (25+ for FFM, even though we support lower JDK targets) - Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) ### Preparing your environment @@ -113,7 +110,7 @@ Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/ This should automatically install a recent Swift, but you can always make sure by running: ```bash -swiftly install 6.1.2 --use +swiftly install 6.2 --use ``` Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well, @@ -123,12 +120,12 @@ however any recent enough Java distribution should work correctly. You can use s # Install sdkman from: https://sdkman.io curl -s "https://get.sdkman.io" | bash sdk install java 17.0.15-amzn -sdk install java 24.0.1-amzn +sdk install java 25.0.1-amzn -sdk use java 24.0.1-amzn +sdk use java 25.0.1-amzn ``` -The use of JDK 24 is required to build the project, even though the libraries being published may target lower Java versions. +The use of JDK 25 is required to build the project, even though the libraries being published may target lower Java versions. ❗️ Please make sure to `export JAVA_HOME` such that swift-java can find the necessary java libraries! When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: @@ -233,3 +230,9 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc # Monitoring /Users/ktoso/code/swift-java/Sources/SwiftJavaDocumentation/Documentation.docc for changes... ``` + +## Project Status + +**This project is under active development. We welcome feedback about any issues you encounter.** + +There is no guarantee about API stability until the project reaches a 1.0 release. diff --git a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config index 3b685159..dc38efd9 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config +++ b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config @@ -1,11 +1,13 @@ { "classes" : { "org.apache.commons.io.FilenameUtils" : "FilenameUtils", - "org.apache.commons.io.IOCase" : "IOCase", "org.apache.commons.csv.CSVFormat" : "CSVFormat", "org.apache.commons.csv.CSVParser" : "CSVParser", "org.apache.commons.csv.CSVRecord" : "CSVRecord" }, + "filterExclude" : [ + "org.apache.commons.csv.CSVFormat$Predefined", + ], "dependencies" : [ "org.apache.commons:commons-csv:1.12.0" ] diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index 2758e6aa..feeb8767 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -4,11 +4,17 @@ set -e set -x # invoke resolve as part of a build run -swift run --disable-sandbox +swift build \ + --disable-experimental-prebuilts \ + --disable-sandbox # explicitly invoke resolve without explicit path or dependency # the dependencies should be uses from the --swift-module -swift run swift-java resolve \ + +# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift run \ + --disable-experimental-prebuilts \ + swift-java resolve \ Sources/JavaCommonsCSV/swift-java.config \ --swift-module JavaCommonsCSV \ --output-directory .build/plugins/outputs/javadependencysampleapp/JavaCommonsCSV/destination/SwiftJavaPlugin/ diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 32451e92..881b8d0b 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -35,9 +35,8 @@ let javaIncludePath = "\(javaHome)/include" let javaPlatformIncludePath = "\(javaIncludePath)/linux" #elseif os(macOS) let javaPlatformIncludePath = "\(javaIncludePath)/darwin" -#else - // TODO: Handle windows as well - #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32)" #endif let package = Package( diff --git a/Samples/JavaProbablyPrime/ci-validate.sh b/Samples/JavaProbablyPrime/ci-validate.sh index 0bdd86d1..dc624996 100755 --- a/Samples/JavaProbablyPrime/ci-validate.sh +++ b/Samples/JavaProbablyPrime/ci-validate.sh @@ -3,4 +3,7 @@ set -e set -x -swift run JavaProbablyPrime 1337 \ No newline at end of file +# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift run \ + --disable-experimental-prebuilts \ + JavaProbablyPrime 1337 \ No newline at end of file diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index c350a0e7..32ffbb28 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -63,7 +63,7 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index 96b25e22..e6404adb 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -38,7 +38,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -46,6 +46,7 @@ dependencies { implementation(project(':SwiftKitCore')) implementation(project(':SwiftKitFFM')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } @@ -54,16 +55,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -115,7 +116,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index 4fde0ef0..2daddc61 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -10,19 +10,19 @@ SWIFT_VERSION="$(swift -version | awk '/Swift version/ { print $3 }')" # This is how env variables are set by setup-java if [ "$(uname -m)" = 'arm64' ]; then ARCH=ARM64 - JAVAC="${JAVA_HOME_24_ARM64}/bin/javac" - JAVA="${JAVA_HOME_24_ARM64}/bin/java" + JAVAC="${JAVA_HOME_25_ARM64}/bin/javac" + JAVA="${JAVA_HOME_25_ARM64}/bin/java" else ARCH=X64 - JAVAC="${JAVA_HOME_24_X64}/bin/javac" - JAVA="${JAVA_HOME_24_X64}/bin/java" + JAVAC="${JAVA_HOME_25_X64}/bin/javac" + JAVA="${JAVA_HOME_25_X64}/bin/java" fi -if [ -n "$JAVA_HOME_24_$ARCH" ]; then - export JAVA_HOME="$JAVA_HOME_24_$ARCH" +if [ -n "$JAVA_HOME_25_$ARCH" ]; then + export JAVA_HOME="$JAVA_HOME_25_$ARCH" elif [ "$(uname -s)" = 'Linux' ] then - export PATH="${PATH}:/usr/lib/jvm/jdk-24/bin" # we need to make sure to use the latest JDK to actually compile/run the executable + export PATH="${PATH}:/usr/lib/jvm/jdk-25/bin" # we need to make sure to use the latest JDK to actually compile/run the executable fi # check if we can compile a plain Example file that uses the generated Java bindings that should be in the generated jar diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index b9765c24..98d1bd33 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift new file mode 100644 index 00000000..234cb357 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// This file exists to exercise the swiftpm plugin generating separate output Java files +// for the public types; because Java public types must be in a file with the same name as the type. + +public struct PublicTypeOne { + public init() {} + public func test() {} +} + +public struct PublicTypeTwo { + public init() {} + public func test() {} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle index 048c984d..dcfacdd3 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle +++ b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle @@ -31,7 +31,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -39,16 +39,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -101,7 +101,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", @@ -150,6 +151,7 @@ dependencies { implementation(project(':SwiftKitCore')) implementation(project(':SwiftKitFFM')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh index c7a68d22..ff5c32c8 100755 --- a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh @@ -3,5 +3,7 @@ set -x set -e +swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java new file mode 100644 index 00000000..d3cd791c --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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.*; +import org.swift.swiftkit.ffm.*; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class MultipleTypesFromSingleFileTest { + + @Test + void bothTypesMustHaveBeenGenerated() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + PublicTypeOne.init(arena); + PublicTypeTwo.init(arena); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index c7d5d717..6343e0db 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -62,7 +62,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), ], exclude: [ "swift-java.config" diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift new file mode 100644 index 00000000..b6f050c1 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +public func booleanArray(array: [Bool]) -> [Bool] { + return array +} + +public func byteArray(array: [UInt8]) -> [UInt8] { + return array +} + +public func byteArrayExplicit(array: Array) -> Array { + return array +} + +public func charArray(array: [UInt16]) -> [UInt16] { + return array +} + +public func shortArray(array: [Int16]) -> [Int16] { + return array +} + +public func intArray(array: [Int32]) -> [Int32] { + return array +} + +public func longArray(array: [Int64]) -> [Int64] { + return array +} + +public func floatArray(array: [Float]) -> [Float] { + return array +} + +public func doubleArray(array: [Double]) -> [Double] { + return array +} + +public func stringArray(array: [String]) -> [String] { + return array +} + +public func objectArray(array: [MySwiftClass]) -> [MySwiftClass] { + return array +} + diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift new file mode 100644 index 00000000..99f3a393 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { + return i1 + i2 +} + +public func asyncSleep() async throws { + try await Task.sleep(for: .milliseconds(500)) +} + +public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { + let new = MySwiftClass(x: myClass.x, y: myClass.y) + try await Task.sleep(for: .milliseconds(500)) + return new +} + +public func asyncOptional(i: Int64) async throws -> Int64? { + try await Task.sleep(for: .milliseconds(100)) + return i +} + +public func asyncThrows() async throws { + throw MySwiftError.swiftError +} + +public func asyncString(input: String) async -> String { + return input +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift new file mode 100644 index 00000000..b7a02d78 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// This file exists to exercise the swiftpm plugin generating separate output Java files +// for the public types; because Java public types must be in a file with the same name as the type. + +public struct PublicTypeOne { + public init() {} + public func test() {} +} + +public struct PublicTypeTwo { + public init() {} + public func test() {} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 4b334a5e..e6aed287 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -59,6 +59,14 @@ public class MySwiftClass { self.y = 5 } + convenience public init(throwing: Bool) throws { + if throwing { + throw MySwiftError.swiftError + } else { + self.init() + } + } + deinit { p("deinit called!") } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift new file mode 100644 index 00000000..d9d77d38 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +enum MySwiftError: Error { + case swiftError +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 09903638..e278326e 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -19,6 +19,8 @@ #if os(Linux) import Glibc +#elseif os(Android) + import Android #else import Darwin.C #endif diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift index 00e2533c..ddd77132 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -21,6 +21,14 @@ public struct MySwiftStruct { self.len = len } + public init?(doInit: Bool) { + if doInit { + self.init(cap: 10, len: 10) + } else { + return nil + } + } + public func getCapacity() -> Int64 { self.cap } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift new file mode 100644 index 00000000..fb2b4924 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +public class A { + public init() {} + + public class B { + public init() {} + + public struct C { + public init() {} + + public func g(a: A, b: B, bbc: BB.C) {} + } + } + + public class BB { + public init() {} + + public struct C { + public init() {} + } + } + + public func f(a: A, b: A.B, c: A.B.C, bb: BB, bbc: BB.C) {} +} + +public enum NestedEnum { + case one(OneStruct) + + public struct OneStruct { + public init() {} + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 3e122102..ca1d7458 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -62,6 +62,10 @@ public func optionalJavaKitLong(input: Optional) -> Int64? { } } +public func optionalThrowing() throws -> Int64? { + throw MySwiftError.swiftError +} + public func multipleOptionals( input1: Optional, input2: Optional, diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index 54bd725a..a8ce51d2 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -32,7 +32,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -40,16 +40,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -100,9 +100,19 @@ def jextract = tasks.register("jextract", Exec) { } } + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + def cmdArgs = ["build", "--disable-experimental-prebuilts"] + + // Check if the 'swiftSdk' project property was passed + if (project.hasProperty('swiftSdk')) { + // If it was, add the --sdk argument and its value + cmdArgs.add("--swift-sdk") + cmdArgs.add(project.property('swiftSdk').toString()) + } + workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + args(cmdArgs) // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", @@ -150,6 +160,7 @@ tasks.clean { dependencies { implementation(project(':SwiftKitCore')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh index c7a68d22..ff5c32c8 100755 --- a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh @@ -3,5 +3,7 @@ set -x set -e +swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java new file mode 100644 index 00000000..9cc74246 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.swift.swiftkit.core.ClosableSwiftArena; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(value = 1, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +public class AsyncBenchmark { + /** + * Parameter for the number of parallel tasks to launch. + */ + @Param({"100", "500", "1000"}) + public int taskCount; + + @Setup(Level.Trial) + public void beforeAll() {} + + @TearDown(Level.Trial) + public void afterAll() {} + + @Benchmark + public void asyncSum(Blackhole bh) { + CompletableFuture[] futures = new CompletableFuture[taskCount]; + + // Launch all tasks in parallel using supplyAsync on our custom executor + for (int i = 0; i < taskCount; i++) { + futures[i] = MySwiftLibrary.asyncSum(10, 5).thenAccept(bh::consume); + } + + // Wait for all futures to complete. + CompletableFuture.allOf(futures).join(); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 3109f64e..407ebe7c 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -16,8 +16,6 @@ // Import swift-extract generated sources -// Import javakit/swiftkit support libraries - import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.SwiftLibraries; diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java new file mode 100644 index 00000000..bf19b370 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class ArraysTest { + @Test + void booleanArray() { + boolean[] input = new boolean[] { true, false, false, true }; + assertArrayEquals(input, MySwiftLibrary.booleanArray(input)); + } + + @Test + void byteArray() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.byteArray(input)); + } + + @Test + void byteArray_empty() { + byte[] input = new byte[] {}; + assertArrayEquals(input, MySwiftLibrary.byteArray(input)); + } + + @Test + void byteArray_null() { + assertThrows(NullPointerException.class, () -> MySwiftLibrary.byteArray(null)); + } + + @Test + void byteArrayExplicit() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.byteArrayExplicit(input)); + } + + @Test + void charArray() { + char[] input = new char[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.charArray(input)); + } + + @Test + void shortArray() { + short[] input = new short[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.shortArray(input)); + } + + @Test + void intArray() { + int[] input = new int[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.intArray(input)); + } + + @Test + void longArray() { + long[] input = new long[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.longArray(input)); + } + + @Test + void stringArray() { + String[] input = new String[] { "hey", "there", "my", "friend" }; + assertArrayEquals(input, MySwiftLibrary.stringArray(input)); + } + + @Test + void floatArray() { + float[] input = new float[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.floatArray(input)); + } + + @Test + void doubleArray() { + double[] input = new double[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.doubleArray(input)); + } + + @Test + void objectArray() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass[] input = new MySwiftClass[]{MySwiftClass.init(arena), MySwiftClass.init(arena), MySwiftClass.init(arena) }; + assertEquals(3, MySwiftLibrary.objectArray(input, arena).length); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java new file mode 100644 index 00000000..5fe7c131 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// 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 com.example.swift.MySwiftClass; +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.time.Duration; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class AsyncTest { + @Test + void asyncSum() { + CompletableFuture future = MySwiftLibrary.asyncSum(10, 12); + + Long result = future.join(); + assertEquals(22, result); + } + + @Test + void asyncSleep() { + CompletableFuture future = MySwiftLibrary.asyncSleep(); + future.join(); + } + + @Test + void asyncCopy() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass obj = MySwiftClass.init(10, 5, arena); + CompletableFuture future = MySwiftLibrary.asyncCopy(obj, arena); + + MySwiftClass result = future.join(); + + assertEquals(10, result.getX()); + assertEquals(5, result.getY()); + } + } + + @Test + void asyncThrows() { + CompletableFuture future = MySwiftLibrary.asyncThrows(); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + Throwable cause = ex.getCause(); + assertNotNull(cause); + assertEquals(Exception.class, cause.getClass()); + assertEquals("swiftError", cause.getMessage()); + } + + @Test + void asyncOptional() { + CompletableFuture future = MySwiftLibrary.asyncOptional(42); + assertEquals(OptionalLong.of(42), future.join()); + } + + @Test + void asyncString() { + CompletableFuture future = MySwiftLibrary.asyncString("hey"); + assertEquals("hey", future.join()); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java new file mode 100644 index 00000000..ae02b872 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class MultipleTypesFromSingleFileTest { + + @Test + void bothTypesMustHaveBeenGenerated() { + try (var arena = SwiftArena.ofConfined()) { + PublicTypeOne.init(arena); + PublicTypeTwo.init(arena); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 2e9a7e62..a5838629 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -40,6 +40,17 @@ void init_withParameters() { } } + @Test + void init_throwing() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftClass.init(true, arena)); + assertEquals("swiftError", exception.getMessage()); + + MySwiftClass c = assertDoesNotThrow(() -> MySwiftClass.init(false, arena)); + assertNotNull(c); + } + } + @Test void sum() { try (var arena = SwiftArena.ofConfined()) { @@ -152,4 +163,12 @@ void addXWithJavaLong() { assertEquals(70, c1.addXWithJavaLong(javaLong)); } } + + @Test + void getAsyncVariable() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + assertEquals(42, c1.getGetAsync().join()); + } + } } \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java index c2c1170b..e52e1959 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -18,6 +18,8 @@ import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import org.swift.swiftkit.core.SwiftArena; +import java.util.Optional; + import static org.junit.jupiter.api.Assertions.*; public class MySwiftStructTest { @@ -30,6 +32,17 @@ void init() { } } + @Test + void init_optional() { + try (var arena = SwiftArena.ofConfined()) { + assertEquals(Optional.empty(), MySwiftStruct.init(false, arena)); + + Optional optionalStruct = MySwiftStruct.init(true, arena); + assertTrue(optionalStruct.isPresent()); + assertEquals(10, optionalStruct.get().getLen()); + } + } + @Test void getAndSetLen() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java new file mode 100644 index 00000000..b006ea91 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class NestedTypesTest { + @Test + void testClassesAndStructs() { + try (var arena = SwiftArena.ofConfined()) { + var a = A.init(arena); + var b = A.B.init(arena); + var c = A.B.C.init(arena); + var bb = A.BB.init(arena); + var abbc = A.BB.C.init(arena); + + a.f(a, b, c, bb, abbc); + c.g(a, b, abbc); + } + } + + @Test + void testStructInEnum() { + try (var arena = SwiftArena.ofConfined()) { + var obj = NestedEnum.one(NestedEnum.OneStruct.init(arena), arena); + var one = obj.getAsOne(arena); + assertTrue(one.isPresent()); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index d60ff6d5..d26de1d1 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -14,6 +14,7 @@ package com.example.swift; +import com.example.swift.MySwiftLibrary; import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.SwiftArena; @@ -22,8 +23,7 @@ import java.util.OptionalInt; import java.util.OptionalLong; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class OptionalsTest { @Test @@ -113,4 +113,13 @@ void multipleOptionals() { assertEquals(result, OptionalLong.of(1L)); } } + + @Test + void optionalThrows() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.optionalThrowing()); + + assertEquals("swiftError", exception.getMessage()); + } + } } \ No newline at end of file diff --git a/Sources/CSwiftJavaJNI/AndroidSupport.cpp b/Sources/CSwiftJavaJNI/AndroidSupport.cpp new file mode 100644 index 00000000..a223530a --- /dev/null +++ b/Sources/CSwiftJavaJNI/AndroidSupport.cpp @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifdef __ANDROID__ + +#include +#include +#include + +// these are not exported by the Android SDK + +extern "C" { + using JavaRuntime_GetDefaultJavaVMInitArgs_fn = jint (*)(void *vm_args); + using JavaRuntime_CreateJavaVM_fn = jint (*)(JavaVM **, JNIEnv **, void *); + using JavaRuntime_GetCreatedJavaVMs_fn = jint (*)(JavaVM **, jsize, jsize *); +} + +static JavaRuntime_GetDefaultJavaVMInitArgs_fn + JavaRuntime_GetDefaultJavaVMInitArgs; +static JavaRuntime_CreateJavaVM_fn JavaRuntime_CreateJavaVM; +static JavaRuntime_GetCreatedJavaVMs_fn JavaRuntime_GetCreatedJavaVMs; + +static void *JavaRuntime_dlhandle; + +__attribute__((constructor)) static void JavaRuntime_init(void) { + JavaRuntime_dlhandle = dlopen("libnativehelper.so", RTLD_NOW | RTLD_LOCAL); + if (JavaRuntime_dlhandle == nullptr) { + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "failed to open libnativehelper.so: %s", dlerror()); + return; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetDefaultJavaVMInitArgs")); + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetDefaultJavaVMInitArgs not found: %s", + dlerror()); + + JavaRuntime_CreateJavaVM = reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_CreateJavaVM")); + if (JavaRuntime_CreateJavaVM == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_CreateJavaVM not found: %s", dlerror()); + + JavaRuntime_GetCreatedJavaVMs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetCreatedJavaVMs")); + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetCreatedJavaVMs not found: %s", dlerror()); +} + +__attribute__((destructor)) static void JavaRuntime_deinit(void) { + if (JavaRuntime_dlhandle) { + dlclose(JavaRuntime_dlhandle); + JavaRuntime_dlhandle = nullptr; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = nullptr; + JavaRuntime_CreateJavaVM = nullptr; + JavaRuntime_GetCreatedJavaVMs = nullptr; +} + +jint JNI_GetDefaultJavaVMInitArgs(void *vm_args) { + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetDefaultJavaVMInitArgs)(vm_args); +} + +jint JNI_CreateJavaVM(JavaVM **vm, JNIEnv **env, void *vm_args) { + if (JavaRuntime_CreateJavaVM == nullptr) + return JNI_ERR; + + return (*JavaRuntime_CreateJavaVM)(vm, env, vm_args); +} + +jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetCreatedJavaVMs)(vmBuf, bufLen, nVMs); +} + +#endif diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index 8c22fbee..1497a61a 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -70,14 +70,33 @@ public struct CodePrinter { } } + public mutating func printHashIfBlock( + _ header: Any, + function: String = #function, + file: String = #fileID, + line: UInt = #line, + body: (inout CodePrinter) throws -> () + ) rethrows { + print("#if \(header)") + indent() + try body(&self) + outdent() + print("#endif // end of \(header)", .sloc, function: function, file: file, line: line) + } + public mutating func printBraceBlock( _ header: Any, + parameters: [String]? = nil, function: String = #function, file: String = #fileID, line: UInt = #line, body: (inout CodePrinter) throws -> () ) rethrows { - print("\(header) {") + print("\(header) {", .continue) + if let parameters { + print(" (\(parameters.joined(separator: ", "))) in", .continue) + } + println() indent() try body(&self) outdent() @@ -216,14 +235,28 @@ extension CodePrinter { /// - Returns: the output path of the generated file, if any (i.e. not in accumulate in memory mode) package mutating func writeContents( - outputDirectory: String, + outputDirectory _outputDirectory: String, javaPackagePath: String?, - filename: String + filename _filename: String ) throws -> URL? { + + // We handle 'filename' that has a path, since that simplifies passing paths from root output directory enourmously. + // This just moves the directory parts into the output directory part in order for us to create the sub-directories. + let outputDirectory: String + let filename: String + if _filename.contains(PATH_SEPARATOR) { + let parts = _filename.split(separator: PATH_SEPARATOR) + outputDirectory = _outputDirectory.appending(PATH_SEPARATOR).appending(parts.dropLast().joined(separator: PATH_SEPARATOR)) + filename = "\(parts.last!)" + } else { + outputDirectory = _outputDirectory + filename = _filename + } + guard self.mode != .accumulateAll else { // if we're accumulating everything, we don't want to finalize/flush any contents // let's mark that this is where a write would have happened though: - print("// ^^^^ Contents of: \(outputDirectory)/\(filename)") + print("// ^^^^ Contents of: \(outputDirectory)\(PATH_SEPARATOR)\(filename)") return nil } @@ -233,7 +266,7 @@ extension CodePrinter { "// ==== ---------------------------------------------------------------------------------------------------" ) if let javaPackagePath { - print("// \(javaPackagePath)/\(filename)") + print("// \(javaPackagePath)\(PATH_SEPARATOR)\(filename)") } else { print("// \(filename)") } @@ -242,9 +275,15 @@ extension CodePrinter { } let targetDirectory = [outputDirectory, javaPackagePath].compactMap { $0 }.joined(separator: PATH_SEPARATOR) - log.trace("Prepare target directory: \(targetDirectory)") - try FileManager.default.createDirectory( - atPath: targetDirectory, withIntermediateDirectories: true) + log.debug("Prepare target directory: '\(targetDirectory)' for file \(filename.bold)") + do { + try FileManager.default.createDirectory( + atPath: targetDirectory, withIntermediateDirectories: true) + } catch { + // log and throw since it can be confusing what the reason for failing the write was otherwise + log.warning("Failed to create directory: \(targetDirectory)") + throw error + } let outputPath = Foundation.URL(fileURLWithPath: targetDirectory).appendingPathComponent(filename) try contents.write( diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index cb849e79..645e5aa4 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -25,7 +25,7 @@ extension JavaType { case .long: return "J" case .float: return "F" case .double: return "D" - case .class(let package, let name): + case .class(let package, let name, _): let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$") if let package { return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);" @@ -111,4 +111,39 @@ extension JavaType { false } } + + /// Returns the boxed type, or self if the type is already a Java class. + var boxedType: JavaType { + switch self { + case .boolean: + return .class(package: "java.lang", name: "Boolean") + case .byte: + return .class(package: "java.lang", name: "Byte") + case .char: + return .class(package: "java.lang", name: "Character") + case .short: + return .class(package: "java.lang", name: "Short") + case .int: + return .class(package: "java.lang", name: "Integer") + case .long: + return .class(package: "java.lang", name: "Long") + case .float: + return .class(package: "java.lang", name: "Float") + case .double: + return .class(package: "java.lang", name: "Double") + case .void: + return .class(package: "java.lang", name: "Void") + default: + return self + } + } + + var requiresBoxing: Bool { + switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double: + true + default: + false + } + } } diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index 8a58f42a..d3902aa4 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftDiagnostics -import SwiftSyntax +@_spi(ExperimentalLanguageFeatures) import SwiftSyntax extension WithModifiersSyntax { var accessControlModifiers: DeclModifierListSyntax { @@ -218,6 +218,8 @@ extension DeclSyntaxProtocol { } else { "var" } + case .usingDecl(let node): + node.nameForDebug } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 193b545f..3f821a1b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite, .array: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -125,7 +125,9 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol, .optional: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol, + .essentialsData, .essentialsDataProtocol, .optional, .array: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 5dfa5b71..832aff26 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -282,7 +282,7 @@ struct CdeclLowering { ) } - case .data: + case .foundationData, .essentialsData: break default: @@ -347,6 +347,9 @@ struct CdeclLowering { case .composite: throw LoweringError.unhandledType(type) + + case .array: + throw LoweringError.unhandledType(type) } } @@ -375,7 +378,7 @@ struct CdeclLowering { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { - case .data: + case .foundationData, .essentialsData: break case .unsafeRawPointer, .unsafeMutableRawPointer: throw LoweringError.unhandledType(.optional(wrappedType)) @@ -387,7 +390,7 @@ struct CdeclLowering { throw LoweringError.unhandledType(.optional(wrappedType)) case .void, .string: throw LoweringError.unhandledType(.optional(wrappedType)) - case .dataProtocol: + case .foundationDataProtocol, .essentialsDataProtocol: throw LoweringError.unhandledType(.optional(wrappedType)) default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. @@ -415,7 +418,7 @@ struct CdeclLowering { } throw LoweringError.unhandledType(.optional(wrappedType)) - case .function, .metatype, .optional, .composite: + case .function, .metatype, .optional, .composite, .array: throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -505,7 +508,7 @@ struct CdeclLowering { ]) ) - case .data: + case .foundationData, .essentialsData: break default: @@ -516,7 +519,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite, .array: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -606,7 +609,7 @@ struct CdeclLowering { case .void: return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) - case .data: + case .foundationData, .essentialsData: break case .string, .optional: @@ -670,7 +673,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .genericParameter, .function, .optional, .existential, .opaque, .composite: + case .genericParameter, .function, .optional, .existential, .opaque, .composite, .array: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 188f5e8b..a4485fff 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -453,7 +453,7 @@ extension FFMSwift2JavaGenerator { func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description - } else if case .class(package: _, name: let customClass) = javaType { + } else if case .class(package: _, name: let customClass, _) = javaType { return ForeignValueLayout(customType: customClass).description } else { fatalError("renderMemoryLayoutValue not supported for \(javaType)") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 438393cb..72da323c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -411,7 +411,7 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) - case .data: + case .foundationData, .essentialsData: break default: @@ -475,6 +475,9 @@ extension FFMSwift2JavaGenerator { case .composite: throw JavaTranslationError.unhandledType(swiftType) + + case .array(let elementType): + throw JavaTranslationError.unhandledType(swiftType) } } @@ -515,7 +518,7 @@ extension FFMSwift2JavaGenerator { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { - case .data, .dataProtocol: + case .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: break default: throw JavaTranslationError.unhandledType(.optional(swiftType)) @@ -658,7 +661,7 @@ extension FFMSwift2JavaGenerator { ) ) - case .data: + case .foundationData, .essentialsData: break case .unsafePointer, .unsafeMutablePointer: @@ -694,7 +697,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .genericParameter, .optional, .function, .existential, .opaque, .composite: + case .genericParameter, .optional, .function, .existential, .opaque, .composite, .array: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index f35973eb..f233ef00 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -22,8 +22,16 @@ extension FFMSwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { + let pendingFileCount = self.expectedOutputSwiftFiles.count + guard pendingFileCount > 0 else { + return // no need to write any empty files, yay + } + + print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + for expectedFileName in self.expectedOutputSwiftFiles { - log.trace("Write empty file: \(expectedFileName) ...") + log.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -46,7 +54,7 @@ extension FFMSwift2JavaGenerator { outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: moduleFilename) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + log.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") self.expectedOutputSwiftFiles.remove(moduleFilename) } } catch { @@ -54,24 +62,41 @@ extension FFMSwift2JavaGenerator { } // === All types - // FIXME: write them all into the same file they were declared from +SwiftJava - for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" - let filename = "\(fileNameBase).swift" - log.debug("Printing contents: \(filename)") + // We have to write all types to their corresponding output file that matches the file they were declared in, + // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. + for group: (key: String, value: [Dictionary.Element]) in Dictionary(grouping: self.analysis.importedTypes, by: { $0.value.sourceFilePath }) { + log.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") + + let importedTypesForThisFile = group.value + .map(\.value) + .sorted(by: { $0.qualifiedName < $1.qualifiedName }) + + let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" + let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift") + + for ty in importedTypesForThisFile { + log.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") + printer.printSeparator("Thunks for \(ty.qualifiedName)") + + do { + try printSwiftThunkSources(&printer, ty: ty) + } catch { + log.warning("Failed to print to Swift thunks for type'\(ty.qualifiedName)' to '\(filename)', error: \(error)") + } + + } + log.warning("Write Swift thunks file: \(filename.bold)") do { - try printSwiftThunkSources(&printer, ty: ty) - if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: filename) { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + log.info("Done writing Swift thunks to: \(outputFile.absoluteString)") self.expectedOutputSwiftFiles.remove(filename) } } catch { - log.warning("Failed to write to Swift thunks: \(filename)") + log.warning("Failed to write to Swift thunks: \(filename), error: \(error)") } } } @@ -83,7 +108,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftRuntimeFunctions """) @@ -111,7 +136,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftRuntimeFunctions """ ) @@ -125,11 +150,55 @@ extension FFMSwift2JavaGenerator { } func printSwiftThunkImports(_ printer: inout CodePrinter) { + let mainSymbolSourceModules = Set( + self.lookupContext.symbolTable.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) + ) + for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { guard module != "Swift" else { continue } - printer.print("import \(module)") + + guard let alternativeModules = self.lookupContext.symbolTable.importedModules[module]?.alternativeModules else { + printer.print("import \(module)") + continue + } + + // Try to print only on main module from relation chain as it has every other module. + guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else { + if !alternativeModules.isMainSourceOfSymbols { + printer.print("import \(module)") + } + continue + } + + var importGroups: [String: [String]] = [:] + for name in alternativeModules.moduleNames { + guard let otherModule = self.lookupContext.symbolTable.importedModules[name] else { continue } + + let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName + importGroups[groupKey, default: []].append(otherModule.moduleName) + } + + for (index, group) in importGroups.keys.sorted().enumerated() { + if index > 0 && importGroups.keys.count > 1 { + printer.print("#elseif canImport(\(group))") + } else { + printer.print("#if canImport(\(group))") + } + + for groupModule in importGroups[group] ?? [] { + printer.print("import \(groupModule)") + } + } + + if (importGroups.keys.isEmpty) { + printer.print("import \(module)") + } else { + printer.print("#else") + printer.print("import \(module)") + printer.print("#endif") + } } printer.println() } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index c9a5028b..c445d5c9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -60,16 +60,14 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in - guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else { + guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { return nil } return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - - // FIXME: Can we avoid this? - self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") + self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } @@ -77,16 +75,12 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { func generate() throws { try writeSwiftThunkSources() - print("[swift-java] Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") + log.info("Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") try writeExportedJavaSources() - print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") + log.info("Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") - let pendingFileCount = self.expectedOutputSwiftFiles.count - if pendingFileCount > 0 { - print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/") - try writeSwiftExpectedEmptySources() - } + try writeSwiftExpectedEmptySources() } } @@ -134,7 +128,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { - print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + log.info("Generated: \((ty.swiftNominal.name.bold + ".java").bold) (at \(outputFile.absoluteString))") } } @@ -148,7 +142,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename) { - print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") + log.info("Generated: \((self.swiftModuleName + ".java").bold) (at \(outputFile.absoluteString))") } } } @@ -195,7 +189,7 @@ extension FFMSwift2JavaGenerator { private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); return true; } @@ -345,7 +339,7 @@ extension FFMSwift2JavaGenerator { private static SymbolLookup getSymbolLookup() { if (SwiftLibraries.AUTO_LOAD_LIBS) { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 7a071d9a..9ba1cd6f 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -27,20 +27,28 @@ package enum SwiftAPIKind { /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been /// imported and is being translated into Java. -package class ImportedNominalType: ImportedDecl { +package final class ImportedNominalType: ImportedDecl { let swiftNominal: SwiftNominalTypeDeclaration + // The short path from module root to the file in which this nominal was originally declared. + // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. + package var sourceFilePath: String { + self.swiftNominal.sourceFilePath + } + package var initializers: [ImportedFunc] = [] package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] var inheritedTypes: [SwiftType] + package var parent: SwiftNominalTypeDeclaration? init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap { try? SwiftType($0.type, lookupContext: lookupContext) } ?? [] + self.parent = swiftNominal.parent } var swiftType: SwiftType { @@ -186,6 +194,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { self.functionSignature.effectSpecifiers.contains(.throws) } + var isAsync: Bool { + self.functionSignature.isAsync + } + init( module: String, swiftDecl: any DeclSyntaxProtocol, diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift index cdb13e3f..b0521946 100644 --- a/Sources/JExtractSwiftLib/JNI/JNICaching.swift +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -14,15 +14,15 @@ enum JNICaching { static func cacheName(for type: ImportedNominalType) -> String { - cacheName(for: type.swiftNominal.name) + cacheName(for: type.swiftNominal.qualifiedName) } static func cacheName(for type: SwiftNominalType) -> String { - cacheName(for: type.nominalTypeDecl.name) + cacheName(for: type.nominalTypeDecl.qualifiedName) } - private static func cacheName(for name: String) -> String { - "_JNI_\(name)" + private static func cacheName(for qualifiedName: String) -> String { + "_JNI_\(qualifiedName.replacingOccurrences(of: ".", with: "_"))" } static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 983396ac..9bd9a7d8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -45,13 +45,14 @@ enum JNIJavaTypeTranslator { case .void: return .void case .string: return .javaLangString + case .int, .uint, // FIXME: why not supported int/uint? .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, - .optional, .data, .dataProtocol: + .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array: return nil } } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 258b537e..07d23dc0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -25,7 +25,7 @@ extension JNISwift2JavaGenerator { "java.util.concurrent.atomic.AtomicBoolean", // NonNull, Unsigned and friends - "org.swift.swiftkit.core.annotations.*", + "org.swift.swiftkit.core.annotations.*" ] } @@ -40,7 +40,9 @@ extension JNISwift2JavaGenerator { package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) - for (_, ty) in importedTypes { + // Each parent type goes into its own file + // any nested types are printed inside the body as `static class` + for (_, ty) in importedTypes.filter({ _, type in type.parent == nil }) { let filename = "\(ty.swiftNominal.name).java" logger.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) @@ -145,6 +147,15 @@ extension JNISwift2JavaGenerator { """ ) + let nestedTypes = self.analysis.importedTypes.filter { _, type in + type.parent == decl.swiftNominal + } + + for nestedType in nestedTypes { + printConcreteType(&printer, nestedType.value) + printer.println() + } + printer.print( """ /** @@ -255,13 +266,18 @@ extension JNISwift2JavaGenerator { if decl.swiftNominal.isSendable { printer.print("@ThreadSafe // Sendable") } + var modifiers = ["public"] + if decl.parent != nil { + modifiers.append("static") + } + modifiers.append("final") var implements = ["JNISwiftInstance"] implements += decl.inheritedTypes .compactMap(\.asNominalTypeDeclaration) .filter { $0.kind == .protocol } .map(\.name) let implementsClause = implements.joined(separator: ", ") - printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in + printer.printBraceBlock("\(modifiers.joined(separator: " ")) class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in body(&printer) } } @@ -429,7 +445,7 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } - let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" + let throwsClause = translatedDecl.isThrowing && !translatedDecl.isAsync ? " throws Exception" : "" let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in guard case .generic(let name, let extends) = parameter.parameter.type else { @@ -483,7 +499,6 @@ extension JNISwift2JavaGenerator { printNativeFunction(&printer, translatedDecl) } - } private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { @@ -523,7 +538,7 @@ extension JNISwift2JavaGenerator { // Indirect return receivers for outParameter in translatedFunctionSignature.resultType.outParameters { - printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render(type: outParameter.type));") arguments.append(outParameter.name) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a0178d3c..9c3cdbf1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -118,6 +118,7 @@ extension JNISwift2JavaGenerator { name: getAsCaseName, isStatic: false, isThrowing: false, + isAsync: false, nativeFunctionName: "$\(getAsCaseName)", parentName: enumName, functionTypes: [], @@ -181,13 +182,13 @@ extension JNISwift2JavaGenerator { } // Swift -> Java - let translatedFunctionSignature = try translate( + var translatedFunctionSignature = try translate( functionSignature: decl.functionSignature, methodName: javaName, parentName: parentName ) // Java -> Java (native) - let nativeFunctionSignature = try nativeTranslation.translate( + var nativeFunctionSignature = try nativeTranslation.translate( functionSignature: decl.functionSignature, translatedFunctionSignature: translatedFunctionSignature, methodName: javaName, @@ -212,10 +213,21 @@ extension JNISwift2JavaGenerator { } } + // Handle async methods + if decl.functionSignature.isAsync { + self.convertToAsync( + translatedFunctionSignature: &translatedFunctionSignature, + nativeFunctionSignature: &nativeFunctionSignature, + originalFunctionSignature: decl.functionSignature, + mode: config.effectiveAsyncFuncMode + ) + } + return TranslatedFunctionDecl( name: javaName, isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, isThrowing: decl.isThrowing, + isAsync: decl.isAsync, nativeFunctionName: "$\(javaName)", parentName: parentName, functionTypes: funcTypes, @@ -295,9 +307,7 @@ extension JNISwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> [TranslatedParameter] { - try parameters.enumerated().map { - idx, - param in + try parameters.enumerated().map { idx, param in let parameterName = param.name ?? "arg\(idx)" return try translateParameter( swiftType: param.type, @@ -361,7 +371,7 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.name + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName if let knownType = nominalType.nominalTypeDecl.knownTypeKind { switch knownType { @@ -374,6 +384,15 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -451,6 +470,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) + case .array(let elementType): + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -470,6 +495,48 @@ extension JNISwift2JavaGenerator { } } + func convertToAsync( + translatedFunctionSignature: inout TranslatedFunctionSignature, + nativeFunctionSignature: inout NativeFunctionSignature, + originalFunctionSignature: SwiftFunctionSignature, + mode: JExtractAsyncFuncMode + ) { + switch mode { + case .completableFuture: + // Update translated function + + let nativeFutureType = JavaType.completableFuture(nativeFunctionSignature.result.javaType) + + let futureOutParameter = OutParameter( + name: "$future", + type: nativeFutureType, + allocation: .new + ) + + let result = translatedFunctionSignature.resultType + translatedFunctionSignature.resultType = TranslatedResult( + javaType: .completableFuture(translatedFunctionSignature.resultType.javaType), + annotations: result.annotations, + outParameters: result.outParameters + [futureOutParameter], + conversion: .aggregate(variable: nil, [ + .print(.placeholder), // Make the downcall + .method(.constant("$future"), function: "thenApply", arguments: [ + .lambda(args: ["futureResult$"], body: .replacingPlaceholder(result.conversion, placeholder: "futureResult$")) + ]) + ]) + ) + + // Update native function + nativeFunctionSignature.result.conversion = .asyncCompleteFuture( + swiftFunctionResultType: originalFunctionSignature.result.type, + nativeFunctionSignature: nativeFunctionSignature, + isThrowing: originalFunctionSignature.isThrowing + ) + nativeFunctionSignature.result.javaType = .void + nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) + } + } + func translateProtocolParameter( protocolType: SwiftType, parameterName: String, @@ -599,6 +666,14 @@ extension JNISwift2JavaGenerator { } return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateArrayResult( + elementType: elementType + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -632,6 +707,11 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + case .array(let elementType): + return try translateArrayResult( + elementType: elementType + ) + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -719,6 +799,107 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } } + + func translateArrayParameter( + elementType: SwiftType, + parameterName: String + ) throws -> TranslatedParameter { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + + switch elementType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .array(javaType), annotations: parameterAnnotations), + conversion: .requireNonNull(.placeholder, message: "\(parameterName) must not be null") + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + // Assume JExtract imported class + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .array(.class(package: nil, name: nominalTypeName)), + annotations: parameterAnnotations + ), + conversion: .method( + .method( + .arraysStream(.requireNonNull(.placeholder, message: "\(parameterName) must not be null")), + function: "mapToLong", + arguments: [.constant("\(nominalTypeName)::$memoryAddress")] + ), + function: "toArray", + arguments: [] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } + + func translateArrayResult( + elementType: SwiftType + ) throws -> TranslatedResult { + let annotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + + switch elementType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return TranslatedResult( + javaType: .array(javaType), + annotations: annotations, + outParameters: [], + conversion: .placeholder + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + let objectType = JavaType.class(package: nil, name: nominalTypeName) + // We assume this is a JExtract class. + return TranslatedResult( + javaType: .array(objectType), + annotations: annotations, + outParameters: [], + conversion: .method( + .method( + .arraysStream(.placeholder), + function: "mapToObj", + arguments: [ + .lambda( + args: ["pointer"], + body: .wrapMemoryAddressUnsafe(.constant("pointer"), objectType) + ) + ] + ), + function: "toArray", + arguments: [.constant("\(objectType)[]::new")] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } } struct TranslatedEnumCase { @@ -753,6 +934,8 @@ extension JNISwift2JavaGenerator { let isThrowing: Bool + let isAsync: Bool + /// The name of the native function let nativeFunctionName: String @@ -812,11 +995,15 @@ extension JNISwift2JavaGenerator { struct OutParameter { enum Allocation { case newArray(JavaType, size: Int) + case new - func render() -> String { + func render(type: JavaType) -> String { switch self { case .newArray(let javaType, let size): "new \(javaType)[\(size)]" + + case .new: + "new \(type)()" } } } @@ -912,6 +1099,19 @@ extension JNISwift2JavaGenerator { /// Access a member of the value indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) + /// `(args) -> { return body; }` + indirect case lambda(args: [String] = [], body: JavaNativeConversionStep) + + /// Prints the conversion step, ignoring the output. + indirect case print(JavaNativeConversionStep) + + indirect case requireNonNull(JavaNativeConversionStep, message: String) + + /// `Arrays.stream(args)` + static func arraysStream(_ argument: JavaNativeConversionStep) -> JavaNativeConversionStep { + .method(.constant("Arrays"), function: "stream", arguments: [argument]) + } + /// 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. @@ -1024,6 +1224,27 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, let placeholder): return inner.render(&printer, placeholder) + + case .lambda(let args, let body): + var printer = CodePrinter() + printer.printBraceBlock("(\(args.joined(separator: ", "))) ->") { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body);") + } else { + printer.print("return;") + } + } + return printer.finalize() + + case .print(let inner): + let inner = inner.render(&printer, placeholder) + printer.print("\(inner);") + return "" + + case .requireNonNull(let inner, let message): + let inner = inner.render(&printer, placeholder) + return #"Objects.requireNonNull(\#(inner), "\#(message)")"# } } @@ -1074,6 +1295,15 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, _): return inner.requiresSwiftArena + + case .lambda(_, let body): + return body.requiresSwiftArena + + case .print(let inner): + return inner.requiresSwiftArena + + case .requireNonNull(let inner, _): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index bdbfe2f1..1994dce0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -59,34 +59,15 @@ extension JNISwift2JavaGenerator { nil } - return try NativeFunctionSignature( + let result = try translate(swiftResult: functionSignature.result) + + return NativeFunctionSignature( selfParameter: nativeSelf, parameters: parameters, - result: translate(swiftResult: functionSignature.result) + result: result ) } - func translateParameters( - _ parameters: [SwiftParameter], - translatedParameters: [TranslatedParameter], - methodName: String, - parentName: String, - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] - ) throws -> [NativeParameter] { - try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in - let parameterName = translatedParameter.parameter.name - return try translateParameter( - type: swiftParameter.type, - parameterName: parameterName, - methodName: methodName, - parentName: parentName, - genericParameters: genericParameters, - genericRequirements: genericRequirements - ) - } - } - func translateParameter( type: SwiftType, parameterName: String, @@ -110,6 +91,12 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + return try translateArrayParameter(elementType: elementType, parameterName: parameterName) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -207,6 +194,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(type) + case .array(let elementType): + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } @@ -375,7 +368,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, conversion: .optionalRaisingIndirectReturn( - .getJNIValue(.allocateSwiftValue(name: "_result", swiftType: swiftType)), + .getJNIValue(.allocateSwiftValue(.placeholder, name: "_result", swiftType: swiftType)), returnType: .long, discriminatorParameterName: discriminatorName, placeholderValue: .constant("0") @@ -419,7 +412,7 @@ extension JNISwift2JavaGenerator { outParameters: [] ) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite, .array: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -448,7 +441,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite, .array: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -467,6 +460,12 @@ extension JNISwift2JavaGenerator { } return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + return try translateArrayResult(elementType: elementType, resultName: resultName) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -486,7 +485,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: resultName, swiftType: swiftResult.type)), + conversion: .getJNIValue(.allocateSwiftValue(.placeholder, name: resultName, swiftType: swiftResult.type)), outParameters: [] ) @@ -500,16 +499,114 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + case .array(let elementType): + return try translateArrayResult(elementType: elementType, resultName: resultName) + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } + + func translateArrayResult( + elementType: SwiftType, + resultName: String + ) throws -> NativeResult { + switch elementType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + return NativeResult( + javaType: .array(javaType), + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + // Assume JExtract imported class + return NativeResult( + javaType: .array(.long), + conversion: + .getJNIValue( + .method( + .placeholder, + function: "map", + arguments: [ + (nil, .closure( + args: ["object$"], + body: .allocateSwiftValue(.constant("object$"), name: "object$", swiftType: elementType) + )) + ] + ) + ), + outParameters: [] + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + } + + func translateArrayParameter( + elementType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch elementType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .array(javaType)), + ], + conversion: .initFromJNI(.placeholder, swiftType: .array(elementType)) + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + // Assume JExtract wrapped class + return NativeParameter( + parameters: [JavaParameter(name: parameterName, type: .array(.long))], + conversion: .method( + .initFromJNI(.placeholder, swiftType: .array(self.knownTypes.int64)), + function: "map", + arguments: [ + (nil, .closure( + args: ["pointer$"], + body: .pointee(.extractSwiftValue( + .constant("pointer$"), + swiftType: elementType, + allowNil: false, + convertLongFromJNI: false + )))) + ] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } } struct NativeFunctionSignature { let selfParameter: NativeParameter? - let parameters: [NativeParameter] - let result: NativeResult + var parameters: [NativeParameter] + var result: NativeResult } struct NativeParameter { @@ -522,8 +619,8 @@ extension JNISwift2JavaGenerator { } struct NativeResult { - let javaType: JavaType - let conversion: NativeSwiftConversionStep + var javaType: JavaType + var conversion: NativeSwiftConversionStep /// Out parameters for populating the indirect return values. var outParameters: [JavaParameter] @@ -545,7 +642,7 @@ extension JNISwift2JavaGenerator { /// `value.getJValue(in:)` indirect case getJValue(NativeSwiftConversionStep) - /// `SwiftType(from: value, in: environment!)` + /// `SwiftType(from: value, in: environment)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) indirect case extractSwiftProtocolValue( @@ -558,11 +655,12 @@ extension JNISwift2JavaGenerator { indirect case extractSwiftValue( NativeSwiftConversionStep, swiftType: SwiftType, - allowNil: Bool = false + allowNil: Bool = false, + convertLongFromJNI: Bool = true ) /// Allocate memory for a Swift value and outputs the pointer - case allocateSwiftValue(name: String, swiftType: SwiftType) + indirect case allocateSwiftValue(NativeSwiftConversionStep, name: String, swiftType: SwiftType) /// The thing to which the pointer typed, which is the `pointee` property /// of the `Unsafe(Mutable)Pointer` types in Swift. @@ -588,6 +686,15 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) + indirect case asyncCompleteFuture( + swiftFunctionResultType: SwiftType, + nativeFunctionSignature: NativeFunctionSignature, + isThrowing: Bool + ) + + /// `{ (args) -> return body }` + indirect case closure(args: [String] = [], body: 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. @@ -604,15 +711,15 @@ extension JNISwift2JavaGenerator { case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJNIValue(in: environment!)" + return "\(inner).getJNIValue(in: environment)" case .getJValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJValue(in: environment!)" + return "\(inner).getJValue(in: environment)" case .initFromJNI(let inner, let swiftType): let inner = inner.render(&printer, placeholder) - return "\(swiftType)(fromJNI: \(inner), in: environment!)" + return "\(swiftType)(fromJNI: \(inner), in: environment)" case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): let inner = inner.render(&printer, placeholder) @@ -624,11 +731,11 @@ extension JNISwift2JavaGenerator { // TODO: Remove the _openExistential when we decide to only support language mode v6+ printer.print( """ - guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment!))) else { + guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment))) else { fatalError("\(typeMetadataVariableName) memory address was null") } let \(inner)DynamicType$: Any.Type = unsafeBitCast(\(inner)TypeMetadataPointer$, to: Any.Type.self) - guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment!))) else { + guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment))) else { fatalError("\(inner) memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -643,18 +750,18 @@ extension JNISwift2JavaGenerator { ) return existentialName - case .extractSwiftValue(let inner, let swiftType, let allowNil): + case .extractSwiftValue(let inner, let swiftType, let allowNil, let convertLongFromJNI): let inner = inner.render(&printer, placeholder) let pointerName = "\(inner)$" if !allowNil { printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) } - printer.print( - """ - let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) - let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) - """ - ) + if convertLongFromJNI { + printer.print("let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment))") + } else { + printer.print("let \(inner)Bits$ = Int(\(inner))") + } + printer.print("let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$)") if !allowNil { printer.print( """ @@ -666,13 +773,14 @@ extension JNISwift2JavaGenerator { } return pointerName - case .allocateSwiftValue(let name, let swiftType): + case .allocateSwiftValue(let inner, let name, let swiftType): + let inner = inner.render(&printer, placeholder) let pointerName = "\(name)$" let bitsName = "\(name)Bits$" printer.print( """ let \(pointerName) = UnsafeMutablePointer<\(swiftType)>.allocate(capacity: 1) - \(pointerName).initialize(to: \(placeholder)) + \(pointerName).initialize(to: \(inner)) let \(bitsName) = Int64(Int(bitPattern: \(pointerName))) """ ) @@ -709,13 +817,13 @@ extension JNISwift2JavaGenerator { printer.print( """ - let class$ = environment!.interface.GetObjectClass(environment, \(placeholder)) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let class$ = environment.interface.GetObjectClass(environment, \(placeholder)) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))] """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -731,7 +839,7 @@ extension JNISwift2JavaGenerator { case .initializeJavaKitWrapper(let inner, let wrapperName): let inner = inner.render(&printer, placeholder) - return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + return "\(wrapperName)(javaThis: \(inner), environment: environment)" case .optionalLowering(let valueConversion, let discriminatorName, let valueName): let value = valueConversion.render(&printer, valueName) @@ -815,6 +923,108 @@ extension JNISwift2JavaGenerator { """ ) return unwrappedName + + case .asyncCompleteFuture( + let swiftFunctionResultType, + let nativeFunctionSignature, + let isThrowing + ): + var globalRefs: [String] = ["globalFuture"] + + // Global ref all indirect returns + for outParameter in nativeFunctionSignature.result.outParameters { + printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))") + globalRefs.append(outParameter.name) + } + + // We also need to global ref any objects passed in + for parameter in nativeFunctionSignature.parameters.flatMap(\.parameters) where !parameter.type.isPrimitive { + printer.print("let \(parameter.name) = environment.interface.NewGlobalRef(environment, \(parameter.name))") + globalRefs.append(parameter.name) + } + + printer.print( + """ + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + """ + ) + + func printDo(printer: inout CodePrinter) { + printer.print("let swiftResult$ = await \(placeholder)") + printer.print("environment = try! JavaVirtualMachine.shared().environment()") + let inner = nativeFunctionSignature.result.conversion.render(&printer, "swiftResult$") + if swiftFunctionResultType.isVoid { + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") + } else { + let result: String + if nativeFunctionSignature.result.javaType.requiresBoxing { + printer.print("let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)") + result = "boxedResult$" + } else { + result = inner + } + + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: \(result))])") + } + } + + func printTaskBody(printer: inout CodePrinter) { + printer.printBraceBlock("defer") { printer in + // Defer might on any thread, so we need to attach environment. + printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") + for globalRef in globalRefs { + printer.print("deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") + } + } + if isThrowing { + printer.printBraceBlock("do") { printer in + printDo(printer: &printer) + } + printer.printBraceBlock("catch") { printer in + // We might not be on the same thread after the suspension, so we need to attach the thread again. + printer.print( + """ + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + """ + ) + } + } else { + printDo(printer: &printer) + } + } + + printer.print("var task: Task? = nil") + printer.printHashIfBlock("swift(>=6.2)") { printer in + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("task = Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTaskBody(printer: &printer) + } + } + } + + printer.printBraceBlock("if task == nil") { printer in + printer.printBraceBlock("task = Task") { printer in + // We can be on any thread, so we need to attach the thread. + printer.print("var environment = try! JavaVirtualMachine.shared().environment()") + printTaskBody(printer: &printer) + } + } + + return "" + + case .closure(let args, let body): + var printer = CodePrinter() + printer.printBraceBlock("", parameters: args) { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body)") + } + } + return printer.finalize() } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3848ceac..91a0b0e7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -26,8 +26,16 @@ extension JNISwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { + let pendingFileCount = self.expectedOutputSwiftFiles.count + guard pendingFileCount > 0 else { + return // no need to write any empty files, yay + } + + print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + for expectedFileName in self.expectedOutputSwiftFiles { - logger.trace("Write empty file: \(expectedFileName) ...") + logger.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -52,27 +60,46 @@ extension JNISwift2JavaGenerator { javaPackagePath: nil, filename: moduleFilename ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + logger.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") self.expectedOutputSwiftFiles.remove(moduleFilename) } - for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" - let filename = "\(fileNameBase).swift" - logger.debug("Printing contents: \(filename)") + // === All types + // We have to write all types to their corresponding output file that matches the file they were declared in, + // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. + for group: (key: String, value: [Dictionary.Element]) in Dictionary(grouping: self.analysis.importedTypes, by: { $0.value.sourceFilePath }) { + logger.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") - do { - try printNominalTypeThunks(&printer, ty) + let importedTypesForThisFile = group.value + .map(\.value) + .sorted(by: { $0.qualifiedName < $1.qualifiedName }) + + let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" + let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift") + for ty in importedTypesForThisFile { + logger.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") + printer.printSeparator("Thunks for \(ty.qualifiedName)") + + do { + try printNominalTypeThunks(&printer, ty) + } catch { + logger.warning("Failed to print to Swift thunks for type'\(ty.qualifiedName)' to '\(filename)', error: \(error)") + } + + } + + logger.warning("Write Swift thunks file: \(filename.bold)") + do { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: filename) { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + logger.info("Done writing Swift thunks to: \(outputFile.absoluteString)") self.expectedOutputSwiftFiles.remove(filename) } } catch { - logger.warning("Failed to write to Swift thunks: \(filename)") + logger.warning("Failed to write to Swift thunks: \(filename), error: \(error)") } } } catch { @@ -224,16 +251,14 @@ extension JNISwift2JavaGenerator { """ ) let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in - // '0' is treated the same as a null pointer. - let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? 0" : "" + let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? nil" : "" let result = conversion.native.conversion.render(&printer, caseName) - return "\(result)\(nullConversion)" + return "jvalue(\(conversion.native.javaType.jniFieldName): \(result)\(nullConversion))" } printer.print( """ - return withVaList([\(upcallArguments.joined(separator: ", "))]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [\(upcallArguments.joined(separator: ", "))] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) """ ) } @@ -326,29 +351,42 @@ extension JNISwift2JavaGenerator { } // Lower the result. - let innerBody: String - if !decl.functionSignature.result.type.isVoid { + func innerBody(in printer: inout CodePrinter) -> String { let loweredResult = nativeSignature.result.conversion.render(&printer, result) - innerBody = "return \(loweredResult)" - } else { - innerBody = result + + if !decl.functionSignature.result.type.isVoid { + return "return \(loweredResult)" + } else { + return loweredResult + } } - if decl.isThrowing { - // TODO: Handle classes for dummy value - let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : "" + if decl.isThrowing, !decl.isAsync { + let dummyReturn: String + + if nativeSignature.result.javaType.isVoid { + dummyReturn = "" + } else { + // We assume it is something that implements JavaValue + dummyReturn = "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue" + } + + printer.print("do {") + printer.indent() + printer.print(innerBody(in: &printer)) + printer.outdent() + printer.print("} catch {") + printer.indent() printer.print( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ + """ + environment.throwAsException(error) + \(dummyReturn) + """ ) + printer.outdent() + printer.print("}") } else { - printer.print(innerBody) + printer.print(innerBody(in: &printer)) } } @@ -392,7 +430,7 @@ extension JNISwift2JavaGenerator { let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName.escapedJNIIdentifier)_" + + "_\(parentName.replacingOccurrences(of: ".", with: "$").escapedJNIIdentifier)_" + javaMethodName.escapedJNIIdentifier + "__" + jniSignature.escapedJNIIdentifier @@ -426,6 +464,7 @@ extension JNISwift2JavaGenerator { import SwiftJava import CSwiftJavaJNI + import SwiftJavaRuntimeSupport """ ) @@ -435,7 +474,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", - parentName: type.swiftNominal.name, + parentName: type.swiftNominal.qualifiedName, parameters: [], resultType: .long ) { printer in @@ -454,7 +493,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$destroy", - parentName: type.swiftNominal.name, + parentName: type.swiftNominal.qualifiedName, parameters: [ selfPointerParam ], diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index a677bcde..3b84cfb9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -67,16 +67,14 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in - guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else { + guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { return nil } return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - - // FIXME: Can we avoid this? - self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") + self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 43f5a2b4..0b243b3a 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -24,6 +24,7 @@ struct JavaParameter { switch self { case .concrete(let type): return type.jniTypeSignature + case .generic(_, let extends): guard !extends.isEmpty else { return "Ljava/lang/Object;" @@ -34,6 +35,15 @@ struct JavaParameter { } } + var isPrimitive: Bool { + switch self { + case .concrete(let javaType): + javaType.isPrimitive + case .generic(let name, let extends): + false + } + } + var jniTypeName: String { switch self { case .concrete(let type): type.jniTypeName diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 02850801..5e5a7268 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -39,4 +39,9 @@ extension JavaType { static var javaLangThrowable: JavaType { .class(package: "java.lang", name: "Throwable") } + + /// The description of the type java.util.concurrent.CompletableFuture + static func completableFuture(_ T: JavaType) -> JavaType { + .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) + } } diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 19241592..ed76483e 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -95,8 +95,8 @@ public struct SwiftToJava { try translator.analyze() - switch config.mode { - case .some(.ffm), .none: + switch config.effectiveMode { + case .ffm: let generator = FFMSwift2JavaGenerator( config: self.config, translator: translator, diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index ccbbd274..60b99a63 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -32,12 +32,7 @@ public final class Swift2JavaTranslator { // ==== Input - struct Input { - let filePath: String - let syntax: SourceFileSyntax - } - - var inputs: [Input] = [] + var inputs: [SwiftJavaInputFile] = [] /// A list of used Swift class names that live in dependencies, e.g. `JavaInteger` package var dependenciesClasses: [String] = [] @@ -85,15 +80,12 @@ extension Swift2JavaTranslator { package func add(filePath: String, text: String) { log.trace("Adding: \(filePath)") let sourceFileSyntax = Parser.parse(source: text) - self.inputs.append(Input(filePath: filePath, syntax: sourceFileSyntax)) + self.inputs.append(SwiftJavaInputFile(syntax: sourceFileSyntax, path: filePath)) } /// Convenient method for analyzing single file. - package func analyze( - file: String, - text: String - ) throws { - self.add(filePath: file, text: text) + package func analyze(path: String, text: String) throws { + self.add(filePath: path, text: text) try self.analyze() } @@ -104,16 +96,16 @@ extension Swift2JavaTranslator { let visitor = Swift2JavaVisitor(translator: self) for input in self.inputs { - log.trace("Analyzing \(input.filePath)") - visitor.visit(sourceFile: input.syntax) + log.trace("Analyzing \(input.path)") + visitor.visit(inputFile: input) } - // If any API uses 'Foundation.Data', import 'Data' as if it's declared in - // this module. - if let dataDecl = self.symbolTable[.data] { - let dataProtocolDecl = self.symbolTable[.dataProtocol]! + // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', + // import 'Data' as if it's declared in this module. + if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] { + let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { - visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) + visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift") } } } @@ -123,7 +115,7 @@ extension Swift2JavaTranslator { let symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, - inputs.map({ $0.syntax }) + [dependenciesSource], + inputs + [dependenciesSource], log: self.log ) self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) @@ -150,6 +142,8 @@ extension Swift2JavaTranslator { return types.contains(where: check) case .genericParameter: return false + case .array(let ty): + return check(ty) } } @@ -184,13 +178,14 @@ extension Swift2JavaTranslator { } /// Returns a source file that contains all the available dependency classes. - private func buildDependencyClassesSourceFile() -> SourceFileSyntax { + private func buildDependencyClassesSourceFile() -> SwiftJavaInputFile { let contents = self.dependenciesClasses.map { "public class \($0) {}" } .joined(separator: "\n") - return SourceFileSyntax(stringLiteral: contents) + let syntax = SourceFileSyntax(stringLiteral: contents) + return SwiftJavaInputFile(syntax: syntax, path: "FakeDependencyClassesSourceFile.swift") } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 13185a5c..247b2662 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -29,41 +29,42 @@ final class Swift2JavaVisitor { var log: Logger { translator.log } - func visit(sourceFile node: SourceFileSyntax) { + func visit(inputFile: SwiftJavaInputFile) { + let node = inputFile.syntax for codeItem in node.statements { if let declNode = codeItem.item.as(DeclSyntax.self) { - self.visit(decl: declNode, in: nil) + self.visit(decl: declNode, in: nil, sourceFilePath: inputFile.path) } } } - func visit(decl node: DeclSyntax, in parent: ImportedNominalType?) { + func visit(decl node: DeclSyntax, in parent: ImportedNominalType?, sourceFilePath: String) { switch node.as(DeclSyntaxEnum.self) { case .actorDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .classDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .structDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .enumDecl(let node): - self.visit(enumDecl: node, in: parent) + self.visit(enumDecl: node, in: parent, sourceFilePath: sourceFilePath) case .protocolDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .extensionDecl(let node): - self.visit(extensionDecl: node, in: parent) + self.visit(extensionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .typeAliasDecl: break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 case .associatedTypeDecl: - break // TODO: Implement + break // TODO: Implement associated types case .initializerDecl(let node): self.visit(initializerDecl: node, in: parent) case .functionDecl(let node): - self.visit(functionDecl: node, in: parent) + self.visit(functionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .variableDecl(let node): - self.visit(variableDecl: node, in: parent) + self.visit(variableDecl: node, in: parent, sourceFilePath: sourceFilePath) case .subscriptDecl: - // TODO: Implement + // TODO: Implement subscripts break case .enumCaseDecl(let node): self.visit(enumCaseDecl: node, in: parent) @@ -75,23 +76,32 @@ final class Swift2JavaVisitor { func visit( nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, - in parent: ImportedNominalType? + in parent: ImportedNominalType?, + sourceFilePath: String ) { guard let importedNominalType = translator.importedNominalType(node, parent: parent) else { return } for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType) + self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } } - func visit(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { - self.visit(nominalDecl: node, in: parent) + func visit( + enumDecl node: EnumDeclSyntax, + in parent: ImportedNominalType?, + sourceFilePath: String + ) { + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) self.synthesizeRawRepresentableConformance(enumDecl: node, in: parent) } - func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { + func visit( + extensionDecl node: ExtensionDeclSyntax, + in parent: ImportedNominalType?, + sourceFilePath: String + ) { guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore return @@ -100,11 +110,15 @@ final class Swift2JavaVisitor { return } for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType) + self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } } - func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + functionDecl node: FunctionDeclSyntax, + in typeContext: ImportedNominalType?, + sourceFilePath: String + ) { guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -139,7 +153,10 @@ final class Swift2JavaVisitor { } } - func visit(enumCaseDecl node: EnumCaseDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + enumCaseDecl node: EnumCaseDeclSyntax, + in typeContext: ImportedNominalType? + ) { guard let typeContext else { self.log.info("Enum case must be within a current type; \(node)") return @@ -182,7 +199,11 @@ final class Swift2JavaVisitor { } } - func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + variableDecl node: VariableDeclSyntax, + in typeContext: ImportedNominalType?, + sourceFilePath: String + ) { guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -232,7 +253,10 @@ final class Swift2JavaVisitor { } } - func visit(initializerDecl node: InitializerDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + initializerDecl node: InitializerDeclSyntax, + in typeContext: ImportedNominalType?, + ) { guard let typeContext else { self.log.info("Initializer must be within a current type; \(node)") return @@ -265,7 +289,10 @@ final class Swift2JavaVisitor { typeContext.initializers.append(imported) } - private func synthesizeRawRepresentableConformance(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + private func synthesizeRawRepresentableConformance( + enumDecl node: EnumDeclSyntax, + in parent: ImportedNominalType? + ) { guard let imported = translator.importedNominalType(node, parent: parent) else { return } @@ -279,14 +306,15 @@ final class Swift2JavaVisitor { { if !imported.variables.contains(where: { $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType }) { let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" - self.visit(decl: decl, in: imported) + self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } + // FIXME: why is this un-used imported.variables.first?.signatureString if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" && $0.functionSignature.parameters.first?.type == inheritanceType }) { let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" - self.visit(decl: decl, in: imported) + self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift index 409c81a7..79abe981 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift @@ -15,15 +15,74 @@ import SwiftSyntax /// Scan importing modules. -func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] { - var importingModuleNames: [String] = [] +func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] { + var importingModuleNames: [ImportedSwiftModule] = [] for item in sourceFile.statements { if let importDecl = item.item.as(ImportDeclSyntax.self) { guard let moduleName = importDecl.path.first?.name.text else { continue } - importingModuleNames.append(moduleName) + importingModuleNames.append(ImportedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: [])) + } else if let ifConfigDecl = item.item.as(IfConfigDeclSyntax.self) { + importingModuleNames.append(contentsOf: modules(from: ifConfigDecl)) } } return importingModuleNames } + +private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftModule] { + guard + let firstClause = ifConfigDecl.clauses.first, + let calledExpression = firstClause.condition?.as(FunctionCallExprSyntax.self)?.calledExpression.as(DeclReferenceExprSyntax.self), + calledExpression.baseName.text == "canImport" else { + return [] + } + + var modules: [ImportedSwiftModule] = [] + modules.reserveCapacity(ifConfigDecl.clauses.count) + + for (index, clause) in ifConfigDecl.clauses.enumerated() { + let importedModuleNames = clause.elements?.as(CodeBlockItemListSyntax.self)? + .compactMap { CodeBlockItemSyntax($0) } + .compactMap { $0.item.as(ImportDeclSyntax.self) } + .compactMap { $0.path.first?.name.text } ?? [] + + let importModuleName: String? = if + let funcCallExpr = clause.condition?.as(FunctionCallExprSyntax.self), + let calledDeclReference = funcCallExpr.calledExpression.as(DeclReferenceExprSyntax.self), + calledDeclReference.baseName.text == "canImport", + let moduleNameSyntax = funcCallExpr.arguments.first?.expression.as(DeclReferenceExprSyntax.self) { + moduleNameSyntax.baseName.text + } else { + nil + } + + let clauseModules = importedModuleNames.map { + ImportedSwiftModule(name: $0, + availableWithModuleName: importModuleName, + alternativeModuleNames: []) + } + + // Assume single import from #else statement is fallback and use it as main source of symbols + if + clauseModules.count == 1 && + index == (ifConfigDecl.clauses.count - 1) && + clause.poundKeyword.tokenKind == .poundElse { + var fallbackModule: ImportedSwiftModule = clauseModules[0] + var moduleNames: [String] = [] + moduleNames.reserveCapacity(modules.count) + + for i in 0.. + var isMainSourceOfSymbols: Bool + + init(name: String, availableWithModuleName: String? = nil, alternativeModuleNames: Set = [], isMainSourceOfSymbols: Bool = false) { + self.name = name + self.availableWithModuleName = availableWithModuleName + self.alternativeModuleNames = alternativeModuleNames + self.isMainSourceOfSymbols = isMainSourceOfSymbols + } + + static func ==(lhs: Self, rhs: Self) -> Bool { + lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(name) + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift index 9ba4d7ad..fb28586b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift @@ -14,4 +14,5 @@ enum SwiftEffectSpecifier: Equatable { case `throws` + case `async` } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index a5b01bee..1f2fbd36 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -30,6 +30,14 @@ public struct SwiftFunctionSignature: Equatable { var genericParameters: [SwiftGenericParameterDeclaration] var genericRequirements: [SwiftGenericRequirement] + var isAsync: Bool { + effectSpecifiers.contains(.async) + } + + var isThrowing: Bool { + effectSpecifiers.contains(.throws) + } + init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], @@ -195,7 +203,7 @@ extension SwiftFunctionSignature { guard parameterNode.specifier == nil else { throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) } - let param = try lookupContext.typeDeclaration(for: parameterNode) as! SwiftGenericParameterDeclaration + let param = try lookupContext.typeDeclaration(for: parameterNode, sourceFilePath: "FIXME_HAS_NO_PATH.swift") as! SwiftGenericParameterDeclaration params.append(param) if let inheritedNode = parameterNode.inheritedType { let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) @@ -245,8 +253,8 @@ extension SwiftFunctionSignature { if signature.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } - if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + if signature.effectSpecifiers?.asyncSpecifier != nil { + effectSpecifiers.append(.async) } let parameters = try signature.parameterClause.parameters.map { param in @@ -331,7 +339,7 @@ extension SwiftFunctionSignature { effectSpecifiers.append(.throws) } if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + effectSpecifiers.append(.async) } return effectSpecifiers } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 938d0c4a..1be8071c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -18,6 +18,7 @@ import SwiftSyntaxBuilder enum SwiftKnownModule: String { case swift = "Swift" case foundation = "Foundation" + case foundationEssentials = "FoundationEssentials" var name: String { return self.rawValue @@ -27,26 +28,41 @@ enum SwiftKnownModule: String { return switch self { case .swift: swiftSymbolTable case .foundation: foundationSymbolTable + case .foundationEssentials: foundationEssentialsSymbolTable } } var sourceFile: SourceFileSyntax { return switch self { case .swift: swiftSourceFile - case .foundation: foundationSourceFile + case .foundation: foundationEssentialsSourceFile + case .foundationEssentials: foundationEssentialsSourceFile } } } private var swiftSymbolTable: SwiftModuleSymbolTable { var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) - builder.handle(sourceFile: swiftSourceFile) + builder.handle(sourceFile: swiftSourceFile, sourceFilePath: "SwiftStdlib.swift") // FIXME: missing path here + return builder.finalize() +} + +private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder( + moduleName: "FoundationEssentials", + requiredAvailablityOfModuleWithName: "FoundationEssentials", + alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]), + importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationEssentialsSourceFile, sourceFilePath: "FakeFoundation.swift") return builder.finalize() } private var foundationSymbolTable: SwiftModuleSymbolTable { - var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable]) - builder.handle(sourceFile: foundationSourceFile) + var builder = SwiftParsedModuleSymbolTableBuilder( + moduleName: "Foundation", + alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]), + importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationSourceFile, sourceFilePath: "Foundation.swift") return builder.finalize() } @@ -78,6 +94,8 @@ private let swiftSourceFile: SourceFileSyntax = """ public struct Optional {} + public struct Array {} + // FIXME: Support 'typealias Void = ()' public struct Void {} @@ -87,7 +105,7 @@ private let swiftSourceFile: SourceFileSyntax = """ } """ -private let foundationSourceFile: SourceFileSyntax = """ +private let foundationEssentialsSourceFile: SourceFileSyntax = """ public protocol DataProtocol {} public struct Data: DataProtocol { @@ -96,3 +114,9 @@ private let foundationSourceFile: SourceFileSyntax = """ public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } """ + +private var foundationSourceFile: SourceFileSyntax { + // On platforms other than Darwin, imports such as FoundationEssentials, FoundationNetworking, etc. are used, + // so this file should be created by combining the files of the aforementioned modules. + foundationEssentialsSourceFile +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 11ff25c4..4a0cb9e8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -15,6 +15,7 @@ import SwiftSyntax enum SwiftKnownTypeDeclKind: String, Hashable { + // Swift case bool = "Swift.Bool" case int = "Swift.Int" case uint = "Swift.UInt" @@ -39,9 +40,13 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case optional = "Swift.Optional" case void = "Swift.Void" case string = "Swift.String" + case array = "Swift.Array" - case dataProtocol = "Foundation.DataProtocol" - case data = "Foundation.Data" + // Foundation + case foundationDataProtocol = "Foundation.DataProtocol" + case essentialsDataProtocol = "FoundationEssentials.DataProtocol" + case foundationData = "Foundation.Data" + case essentialsData = "FoundationEssentials.Data" var moduleAndName: (module: String, name: String) { let qualified = self.rawValue diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index 8c70f7e2..25b1135a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,8 +35,10 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } - var dataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.dataProtocol])) } - var data: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.data])) } + 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])) } func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( @@ -78,7 +80,8 @@ struct SwiftKnownTypes { /// given protocol kind. E.g. `String` for `StringProtocol` func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { switch knownProtocol { - case .dataProtocol: return self.data + case .foundationDataProtocol: return self.foundationData + case .essentialsDataProtocol: return self.essentialsData default: return nil } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift index f1bbaa12..2db19928 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift @@ -19,6 +19,12 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { /// The name of this module. let moduleName: String + /// The name of module required to be imported and checked via canImport statement. + let requiredAvailablityOfModuleWithName: String? + + /// Data about alternative modules which provides desired symbos e.g. FoundationEssentials is non-Darwin platform alternative for Foundation + let alternativeModules: AlternativeModuleNamesData? + /// The top-level nominal types, found by name. var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] @@ -36,4 +42,18 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { nestedTypes[parent]?[name] } + + func isAlternative(for moduleName: String) -> Bool { + alternativeModules.flatMap { $0.moduleNames.contains(moduleName) } ?? false + } +} + +extension SwiftModuleSymbolTable { + struct AlternativeModuleNamesData { + /// Flag indicating module should be used as source of symbols to avoid duplication of symbols. + let isMainSourceOfSymbols: Bool + + /// Names of modules which are alternative for currently checked module. + let moduleNames: Set + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 763a5da2..0b8b5651 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -19,20 +19,37 @@ import SwiftSyntax public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax package class SwiftTypeDeclaration { + + // The short path from module root to the file in which this nominal was originally declared. + // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. + let sourceFilePath: String + /// The module in which this nominal type is defined. If this is a nested type, the /// module might be different from that of the parent type, if this nominal type /// is defined in an extension within another module. let moduleName: String - /// The name of this nominal type, e.g., 'MyCollection'. + /// The name of this nominal type, e.g. 'MyCollection'. let name: String - init(moduleName: String, name: String) { + init(sourceFilePath: String, moduleName: String, name: String) { + self.sourceFilePath = sourceFilePath self.moduleName = moduleName self.name = name } } +/// A syntax node paired with a simple file path +package struct SwiftJavaInputFile { + let syntax: SourceFileSyntax + /// Simple file path of the file from which the syntax node was parsed. + let path: String + package init(syntax: SourceFileSyntax, path: String) { + self.syntax = syntax + self.path = path + } +} + /// Describes a nominal type declaration, which can be of any kind (class, struct, etc.) /// and has a name, parent type (if nested), and owning module. package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { @@ -66,6 +83,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { /// Create a nominal type declaration from the syntax node for a nominal type /// declaration. init( + sourceFilePath: String, moduleName: String, parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode @@ -82,7 +100,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } - super.init(moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } lazy var firstInheritanceType: TypeSyntax? = { @@ -145,11 +163,12 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { let syntax: GenericParameterSyntax init( + sourceFilePath: String, moduleName: String, node: GenericParameterSyntax ) { self.syntax = node - super.init(moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 9b8ef236..c5586410 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -26,9 +26,18 @@ struct SwiftParsedModuleSymbolTableBuilder { /// Extension decls their extended type hasn't been resolved. var unresolvedExtensions: [ExtensionDeclSyntax] - init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable], log: Logger? = nil) { + init( + moduleName: String, + requiredAvailablityOfModuleWithName: String? = nil, + alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil, + importedModules: [String: SwiftModuleSymbolTable], + log: Logger? = nil + ) { self.log = log - self.symbolTable = .init(moduleName: moduleName) + self.symbolTable = .init( + moduleName: moduleName, + requiredAvailablityOfModuleWithName: requiredAvailablityOfModuleWithName, + alternativeModules: alternativeModules) self.importedModules = importedModules self.unresolvedExtensions = [] } @@ -41,7 +50,8 @@ struct SwiftParsedModuleSymbolTableBuilder { extension SwiftParsedModuleSymbolTableBuilder { mutating func handle( - sourceFile: SourceFileSyntax + sourceFile: SourceFileSyntax, + sourceFilePath: String ) { // Find top-level type declarations. for statement in sourceFile.statements { @@ -51,10 +61,10 @@ extension SwiftParsedModuleSymbolTableBuilder { } if let nominalTypeNode = decl.asNominal { - self.handle(nominalTypeDecl: nominalTypeNode, parent: nil) + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalTypeNode, parent: nil) } if let extensionNode = decl.as(ExtensionDeclSyntax.self) { - self.handle(extensionDecl: extensionNode) + self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath) } } } @@ -62,6 +72,7 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add a nominal type declaration and all of the nested types within it to the symbol /// table. mutating func handle( + sourceFilePath: String, nominalTypeDecl node: NominalTypeDeclSyntaxNode, parent: SwiftNominalTypeDeclaration? ) { @@ -74,6 +85,7 @@ extension SwiftParsedModuleSymbolTableBuilder { // Otherwise, create the nominal type declaration. let nominalTypeDecl = SwiftNominalTypeDeclaration( + sourceFilePath: sourceFilePath, moduleName: moduleName, parent: parent, node: node @@ -87,26 +99,28 @@ extension SwiftParsedModuleSymbolTableBuilder { symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl } - self.handle(memberBlock: node.memberBlock, parent: nominalTypeDecl) + self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: nominalTypeDecl) } mutating func handle( + sourceFilePath: String, memberBlock node: MemberBlockSyntax, parent: SwiftNominalTypeDeclaration ) { for member in node.members { // Find any nested types within this nominal type and add them. if let nominalMember = member.decl.asNominal { - self.handle(nominalTypeDecl: nominalMember, parent: parent) + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalMember, parent: parent) } } } mutating func handle( - extensionDecl node: ExtensionDeclSyntax + extensionDecl node: ExtensionDeclSyntax, + sourceFilePath: String ) { - if !self.tryHandle(extension: node) { + if !self.tryHandle(extension: node, sourceFilePath: sourceFilePath) { self.unresolvedExtensions.append(node) } } @@ -114,7 +128,8 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add any nested types within the given extension to the symbol table. /// If the extended nominal type can't be resolved, returns false. mutating func tryHandle( - extension node: ExtensionDeclSyntax + extension node: ExtensionDeclSyntax, + sourceFilePath: String ) -> Bool { // Try to resolve the type referenced by this extension declaration. // If it fails, we'll try again later. @@ -132,7 +147,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } // Find any nested types within this extension and add them. - self.handle(memberBlock: node.memberBlock, parent: extendedNominal) + self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: extendedNominal) return true } @@ -149,7 +164,7 @@ extension SwiftParsedModuleSymbolTableBuilder { while !unresolvedExtensions.isEmpty { var extensions = self.unresolvedExtensions extensions.removeAll(where: { - self.tryHandle(extension: $0) + self.tryHandle(extension: $0, sourceFilePath: "FIXME_MISSING_FILEPATH.swift") // FIXME: missing filepath here in finalize }) // If we didn't resolve anything, we're done. diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 239d38c4..ef299bab 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -39,9 +39,12 @@ extension SwiftSymbolTableProtocol { package class SwiftSymbolTable { let importedModules: [String: SwiftModuleSymbolTable] - let parsedModule:SwiftModuleSymbolTable + let parsedModule: SwiftModuleSymbolTable private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] + private var prioritySortedImportedModules: [SwiftModuleSymbolTable] { + importedModules.values.sorted(by: { ($0.alternativeModules?.isMainSourceOfSymbols ?? false) && $0.moduleName < $1.moduleName }) + } init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { self.parsedModule = parsedModule @@ -52,24 +55,29 @@ package class SwiftSymbolTable { extension SwiftSymbolTable { package static func setup( moduleName: String, - _ sourceFiles: some Collection, + _ inputFiles: some Collection, log: Logger ) -> SwiftSymbolTable { // Prepare imported modules. // FIXME: Support arbitrary dependencies. - var moduleNames: Set = [] - for sourceFile in sourceFiles { - moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile)) + var modules: Set = [] + for inputFile in inputFiles { + let importedModules = importingModules(sourceFile: inputFile.syntax) + modules.formUnion(importedModules) } var importedModules: [String: SwiftModuleSymbolTable] = [:] importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable - for moduleName in moduleNames.sorted() { + for module in modules { + // We don't need duplicates of symbols, first known definition is enough to parse module + // e.g Data from FoundationEssentials and Foundation collide and lead to different results due to random order of keys in Swift's Dictionary + // guard module.isMainSourceOfSymbols || !importedModules.contains(where: { $0.value.isAlternative(for: String)}) else { continue } + if - importedModules[moduleName] == nil, - let knownModule = SwiftKnownModule(rawValue: moduleName) + importedModules[module.name] == nil, + let knownModule = SwiftKnownModule(rawValue: module.name) { - importedModules[moduleName] = knownModule.symbolTable + importedModules[module.name] = knownModule.symbolTable } } @@ -77,8 +85,8 @@ extension SwiftSymbolTable { var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: moduleName, importedModules: importedModules, log: log) // First, register top-level and nested nominal types to the symbol table. - for sourceFile in sourceFiles { - builder.handle(sourceFile: sourceFile) + for sourceFile in inputFiles { + builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) } let parsedModule = builder.finalize() return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) @@ -95,7 +103,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules.values { + for importedModule in prioritySortedImportedModules { if let result = importedModule.lookupTopLevelNominalType(name) { return result } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 81afe637..b2f8d6ea 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -40,6 +40,9 @@ enum SwiftType: Equatable { /// `type1` & `type2` indirect case composite([SwiftType]) + /// `[type]` + indirect case array(SwiftType) + static var void: Self { return .tuple([]) } @@ -48,7 +51,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite, .array: nil } } @@ -91,7 +94,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .genericParameter, .optional, .tuple, .existential, .opaque, .composite: + case .genericParameter, .optional, .tuple, .existential, .opaque, .composite, .array: return false } } @@ -127,7 +130,7 @@ extension SwiftType: CustomStringConvertible { private var postfixRequiresParentheses: Bool { switch self { case .function, .existential, .opaque, .composite: true - case .genericParameter, .metatype, .nominal, .optional, .tuple: false + case .genericParameter, .metatype, .nominal, .optional, .tuple, .array: false } } @@ -152,6 +155,8 @@ extension SwiftType: CustomStringConvertible { return "some \(constraintType)" case .composite(let types): return types.map(\.description).joined(separator: " & ") + case .array(let type): + return "[\(type)]" } } } @@ -213,9 +218,9 @@ extension SwiftNominalType { extension SwiftType { init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { - case .arrayType, .classRestrictionType, + case .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .suppressedType: + .packElementType, .packExpansionType, .suppressedType, .inlineArrayType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): @@ -323,6 +328,10 @@ extension SwiftType { } self = .composite(types) + + case .arrayType(let arrayType): + let elementType = try SwiftType(arrayType.element, lookupContext: lookupContext) + self = .array(elementType) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 9ede2b1b..33759a2c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -42,24 +42,19 @@ class SwiftTypeLookupContext { return typeDeclaration(for: names) } - case .fromFileScope(_, let names): - if !names.isEmpty { - return typeDeclaration(for: names) - } - - case .lookInMembers(let scopeNode): - if let nominalDecl = try typeDeclaration(for: scopeNode) { + case .lookForMembers(let scopeNode): + if let nominalDecl = try typeDeclaration(for: scopeNode, sourceFilePath: "FIXME.swift") { // FIXME: no path here // implement some node -> file if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { return found } } - case .lookInGenericParametersOfExtendedType(let extensionNode): + case .lookForGenericParameters(let extensionNode): // TODO: Implement _ = extensionNode break - case .mightIntroduceDollarIdentifiers: + case .lookForImplicitClosureParameters: // Dollar identifier can't be a type, ignore. break } @@ -74,15 +69,16 @@ class SwiftTypeLookupContext { for name in names { switch name { case .identifier(let identifiableSyntax, _): - return try? typeDeclaration(for: identifiableSyntax) + return try? typeDeclaration(for: identifiableSyntax, sourceFilePath: "FIXME_NO_PATH.swift") // FIXME: how to get path here? case .declaration(let namedDeclSyntax): - return try? typeDeclaration(for: namedDeclSyntax) + return try? typeDeclaration(for: namedDeclSyntax, sourceFilePath: "FIXME_NO_PATH.swift") // FIXME: how to get path here? case .implicit(let implicitDecl): // TODO: Implement _ = implicitDecl break - case .dollarIdentifier: - break + case .equivalentNames(let equivalentNames): + // TODO: Implement + _ = equivalentNames } } return nil @@ -90,7 +86,7 @@ class SwiftTypeLookupContext { /// Returns the type declaration object associated with the `Syntax` node. /// If there's no declaration created, create an instance on demand, and cache it. - func typeDeclaration(for node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + func typeDeclaration(for node: some SyntaxProtocol, sourceFilePath: String) throws -> SwiftTypeDeclaration? { if let found = typeDecls[node.id] { return found } @@ -98,17 +94,17 @@ class SwiftTypeLookupContext { let typeDecl: SwiftTypeDeclaration switch Syntax(node).as(SyntaxEnum.self) { case .genericParameter(let node): - typeDecl = SwiftGenericParameterDeclaration(moduleName: symbolTable.moduleName, node: node) + typeDecl = SwiftGenericParameterDeclaration(sourceFilePath: sourceFilePath, moduleName: symbolTable.moduleName, node: node) case .classDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .actorDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .structDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .enumDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .protocolDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .typeAliasDecl: fatalError("typealias not implemented") case .associatedTypeDecl: @@ -122,8 +118,17 @@ class SwiftTypeLookupContext { } /// Create a nominal type declaration instance for the specified syntax node. - private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode) throws -> SwiftNominalTypeDeclaration { - SwiftNominalTypeDeclaration( + private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration { + + if let symbolTableDeclaration = self.symbolTable.lookupType( + node.name.text, + parent: try parentTypeDecl(for: node) + ) { + return symbolTableDeclaration + } + + return SwiftNominalTypeDeclaration( + sourceFilePath: sourceFilePath, moduleName: self.symbolTable.moduleName, parent: try parentTypeDecl(for: node), node: node @@ -136,7 +141,7 @@ class SwiftTypeLookupContext { while let parentDecl = node.ancestorDecl { switch parentDecl.as(DeclSyntaxEnum.self) { case .structDecl, .classDecl, .actorDecl, .enumDecl, .protocolDecl: - return (try typeDeclaration(for: parentDecl) as! SwiftNominalTypeDeclaration) + return (try typeDeclaration(for: parentDecl, sourceFilePath: "FIXME_NO_SOURCE_FILE.swift") as! SwiftNominalTypeDeclaration) // FIXME: need to get the source file of the parent default: node = parentDecl continue diff --git a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift index 71ee864c..ecc11b50 100644 --- a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift @@ -13,23 +13,41 @@ //===----------------------------------------------------------------------===// extension Method { + /// Whether this is a 'public' method. public var isPublic: Bool { - return (getModifiers() & 1) != 0 + return (getModifiers() & 0x00000001) != 0 + } + + /// Whether this is a 'private' method. + public var isPrivate: Bool { + return (getModifiers() & 0x00000002) != 0 } /// Whether this is a 'protected' method. public var isProtected: Bool { - return (getModifiers() & 4) != 0 + return (getModifiers() & 0x00000004) != 0 + } + + /// Whether this is a 'package' method. + /// + /// The "default" access level in Java is 'package', it is signified by lack of a different access modifier. + public var isPackage: Bool { + return !isPublic && !isPrivate && !isProtected } /// Whether this is a 'static' method. public var isStatic: Bool { - return (getModifiers() & 0x08) != 0 + return (getModifiers() & 0x00000008) != 0 } /// Whether this is a 'native' method. public var isNative: Bool { - return (getModifiers() & 256) != 0 + return (getModifiers() & 0x00000100) != 0 + } + + /// Whether this is a 'final' method. + public var isFinal: Bool { + return (getModifiers() & 0x00000010) != 0 } } diff --git a/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift new file mode 100644 index 00000000..157ae353 --- /dev/null +++ b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI +import SwiftJava + +// FIXME: all interfaces should ahve these https://github.com/swiftlang/swift-java/issues/430 +extension TypeVariable { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 + +} + +// FIXME: All Java objects are Hashable, we should handle that accordingly. +extension TypeVariable: Hashable { + + public func hash(into hasher: inout Hasher) { + guard let pojo = self.as(JavaObject.self) else { + return + } + + hasher.combine(pojo.hashCode()) + } + + public static func == (lhs: TypeVariable, rhs: TypeVariable) -> Bool { + guard let lhpojo: JavaObject = lhs.as(JavaObject.self) else { + return false + } + guard let rhpojo: JavaObject = rhs.as(JavaObject.self) else { + return false + } + + return lhpojo.equals(rhpojo) + } + +} + +extension TypeVariable { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 3a6df8ea..af9931d3 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -11,9 +11,6 @@ open class Executable: AccessibleObject { @JavaMethod open func getModifiers() -> Int32 - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] - @JavaMethod open func getParameterTypes() -> [JavaClass?] diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index 5e29ee05..984c2b16 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -16,3 +16,24 @@ public struct ParameterizedType { @JavaMethod public func getTypeName() -> String } + +extension ParameterizedType { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 +} + +extension ParameterizedType: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index ff52b41a..2e85c384 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -6,4 +6,13 @@ import CSwiftJavaJNI public struct Type { @JavaMethod public func getTypeName() -> String + + @JavaMethod + public func toString() -> String +} + +extension Type: CustomStringConvertible { + public var description: String { + "JavaLangReflect.Type(\(self.toString()))" + } } diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index 736fcfde..7bf8f7ba 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -3,10 +3,10 @@ import SwiftJava import CSwiftJavaJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) -public struct TypeVariable { +public struct TypeVariable: CustomStringConvertible { @JavaMethod public func getGenericDeclaration() -> GenericDeclaration! - + @JavaMethod public func getAnnotatedBounds() -> [AnnotatedType?] diff --git a/Sources/JavaStdlib/JavaNet/URL+Extensions.swift b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift new file mode 100644 index 00000000..2b220d1c --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava +import CSwiftJavaJNI + +import Foundation +public typealias SwiftJavaFoundationURL = Foundation.URL + +extension SwiftJavaFoundationURL { + public static func fromJava(_ url: URL) throws -> SwiftJavaFoundationURL { + guard let converted = SwiftJavaFoundationURL(string: try url.toURI().toString()) else { + throw SwiftJavaConversionError("Failed to convert \(URL.self) to \(SwiftJavaFoundationURL.self)") + } + return converted + } +} + +extension URL { + public static func fromSwift(_ url: SwiftJavaFoundationURL) throws -> URL { + return try URL(url.absoluteString) + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift new file mode 100644 index 00000000..4c55f6af --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.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 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import SwiftJava + +// FIXME: workaround until importing properly would make UCL inherit from CL https://github.com/swiftlang/swift-java/issues/423 +extension URLClassLoader /* workaround for missing inherits from ClassLoader */ { + @JavaMethod + public func loadClass(_ name: String) throws -> JavaClass? +} diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 08361bac..b9b00ccb 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -34,9 +34,9 @@ extension JavaType { case .array(.float): "jfloatArray?" case .array(.double): "jdoubleArray?" case .array: "jobjectArray?" - case .class(package: "java.lang", name: "String"): "jstring?" - case .class(package: "java.lang", name: "Class"): "jclass?" - case .class(package: "java.lang", name: "Throwable"): "jthrowable?" + case .class(package: "java.lang", name: "String", _): "jstring?" + case .class(package: "java.lang", name: "Class", _): "jclass?" + case .class(package: "java.lang", name: "Throwable", _): "jthrowable?" case .class: "jobject?" } } diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index f9e2e0cd..f81f0c6d 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -51,9 +51,13 @@ extension JavaType: CustomStringConvertible { case .double: "double" case .void: "void" case .array(let elementType): "\(elementType.description)[]" - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, let typeParameters): if let package { - "\(package).\(name)" + if !typeParameters.isEmpty { + "\(package).\(name)<\(typeParameters.map(\.description).joined(separator: ", "))>" + } else { + "\(package).\(name)" + } } else { name } @@ -64,7 +68,7 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var className: String? { switch self { - case .class(_, let name): + case .class(_, let name, _): return name default: return nil @@ -75,9 +79,9 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var fullyQualifiedClassName: String? { switch self { - case .class(.some(let package), let name): + case .class(.some(let package), let name, _): return "\(package).\(name)" - case .class(nil, let name): + case .class(nil, let name, _): return name default: return nil diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 20de73fc..7015ef91 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -25,7 +25,7 @@ extension JavaType { .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return !stringIsValueType case .class: @@ -38,7 +38,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "Runnable"): + case .class(package: "java.lang", name: "Runnable", _): return true case .class: return false @@ -57,7 +57,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return true case .class: return false diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 2a2d901f..ce7191ba 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -28,7 +28,7 @@ public enum JavaType: Equatable, Hashable { /// A Java class separated into its package (e.g., "java.lang") and class name /// (e.g., "Object") - case `class`(package: String?, name: String) + case `class`(package: String?, name: String, typeParameters: [JavaType] = []) /// A Java array. indirect case array(JavaType) diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index f0dbd484..1311b717 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -35,7 +35,7 @@ extension JavaType { case .short: "S" case .void: "V" case .array(let elementType): "[" + elementType.mangledName - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, _): "L\(package!).\(name.replacingPeriodsWithDollars());".replacingPeriodsWithSlashes() } } diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index e514d3e6..bf749820 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -52,7 +52,7 @@ public protocol AnyJavaObject { /// Protocol that allows Swift types to specify a custom Java class loader on /// initialization. This is useful for platforms (e.g. Android) where the default /// class loader does not make all application classes visible. -public protocol CustomJavaClassLoader: AnyJavaObject { +public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject { static func getJavaClassLoader(in environment: JNIEnvironment) throws -> JavaClassLoader! } @@ -118,8 +118,8 @@ extension AnyJavaObject { in environment: JNIEnvironment, _ body: (jclass) throws -> Result ) throws -> Result { - if let customJavaClassLoader = self as? CustomJavaClassLoader.Type, - let customClassLoader = try customJavaClassLoader.getJavaClassLoader(in: environment) { + if let AnyJavaObjectWithCustomClassLoader = self as? AnyJavaObjectWithCustomClassLoader.Type, + let customClassLoader = try AnyJavaObjectWithCustomClassLoader.getJavaClassLoader(in: environment) { try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body) } else { try _withJNIClassFromDefaultClassLoader(in: environment, body) diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift similarity index 95% rename from Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift rename to Sources/SwiftJava/JVM/JavaVirtualMachine.swift index edbddbb7..1c7936a3 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -25,6 +25,14 @@ typealias JNIEnvPointer = UnsafeMutablePointer typealias JNIEnvPointer = UnsafeMutableRawPointer #endif +extension FileManager { +#if os(Windows) + static let pathSeparator = ";" +#else + static let pathSeparator = ":" +#endif +} + public final class JavaVirtualMachine: @unchecked Sendable { /// The JNI version that we depend on. static let jniVersion = JNI_VERSION_1_6 @@ -81,8 +89,8 @@ public final class JavaVirtualMachine: @unchecked Sendable { print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr } } - let colonSeparatedClassPath = classpath.joined(separator: ":") - allVMOptions.append("-Djava.class.path=\(colonSeparatedClassPath)") + let pathSeparatedClassPath = classpath.joined(separator: FileManager.pathSeparator) + allVMOptions.append("-Djava.class.path=\(pathSeparatedClassPath)") } allVMOptions.append(contentsOf: vmOptions) @@ -182,8 +190,8 @@ extension JavaVirtualMachine { // If we failed to attach, report that. if let attachError = VMError(fromJNIError: attachResult) { + // throw attachError fatalError("JVM Error: \(attachError)") - throw attachError } JavaVirtualMachine.destroyTLS.set(jniEnv!) @@ -237,7 +245,7 @@ extension JavaVirtualMachine { ignoreUnrecognized: Bool = false, replace: Bool = false ) throws -> JavaVirtualMachine { - precondition(!classpath.contains(where: { $0.contains(":") }), "Classpath element must not contain `:`! Split the path into elements! Was: \(classpath)") + precondition(!classpath.contains(where: { $0.contains(FileManager.pathSeparator) }), "Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)") return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in // If we already have a JavaVirtualMachine instance, return it. @@ -342,4 +350,4 @@ extension JavaVirtualMachine { enum JavaKitError: Error { case classpathEntryNotFound(entry: String, classpath: [String]) } -} +} \ No newline at end of file diff --git a/Sources/SwiftJava/JavaKitVM/LockedState.swift b/Sources/SwiftJava/JVM/LockedState.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/LockedState.swift rename to Sources/SwiftJava/JVM/LockedState.swift diff --git a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JVM/ThreadLocalStorage.swift similarity index 98% rename from Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift rename to Sources/SwiftJava/JVM/ThreadLocalStorage.swift index 7ea0b50a..037e328b 100644 --- a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift +++ b/Sources/SwiftJava/JVM/ThreadLocalStorage.swift @@ -56,7 +56,7 @@ package struct ThreadLocalStorage: ~Copyable { _key = 0 pthread_key_create(&_key, onThreadExit) #elseif canImport(WinSDK) - key = FlsAlloc(onThreadExit) + _key = FlsAlloc(onThreadExit) #endif } diff --git a/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift new file mode 100644 index 00000000..e7eb2510 --- /dev/null +++ b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension JavaClass: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 319a09e8..5930da59 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -17,8 +17,8 @@ import CSwiftJavaJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object /// while this instance is live. -public class JavaObjectHolder { - public private(set) var object: jobject? +public final class JavaObjectHolder { + public private(set) var object: jobject? // TODO: thread-safety public let environment: JNIEnvironment /// Take a reference to a Java object and promote it to a global reference diff --git a/Sources/SwiftJavaTool/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift similarity index 82% rename from Sources/SwiftJavaTool/String+Extensions.swift rename to Sources/SwiftJava/String+Extensions.swift index 304e217d..94fb1928 100644 --- a/Sources/SwiftJavaTool/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -13,17 +13,15 @@ //===----------------------------------------------------------------------===// import Foundation -import ArgumentParser -import SwiftJavaToolLib -import SwiftJava -import JavaUtilJar -import SwiftJavaToolLib -import SwiftJavaConfigurationShared +// import SwiftJavaToolLib +// import SwiftJava +// import JavaUtilJar +// import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" /// part. - var defaultSwiftNameForJavaClass: String { + package var defaultSwiftNameForJavaClass: String { if let dotLoc = lastIndex(of: ".") { let afterDot = index(after: dotLoc) return String(self[afterDot...]).javaClassNameToCanonicalName.adjustedSwiftTypeName @@ -36,12 +34,12 @@ extension String { extension String { /// Replace all of the $'s for nested names with "." to turn a Java class /// name into a Java canonical class name, - var javaClassNameToCanonicalName: String { + package var javaClassNameToCanonicalName: String { return replacing("$", with: ".") } /// Whether this is the name of an anonymous class. - var isLocalJavaClass: Bool { + package var isLocalJavaClass: Bool { for segment in split(separator: "$") { if let firstChar = segment.first, firstChar.isNumber { return true @@ -52,7 +50,7 @@ extension String { } /// Adjust type name for "bad" type names that don't work well in Swift. - var adjustedSwiftTypeName: String { + package var adjustedSwiftTypeName: String { switch self { case "Type": return "JavaType" default: return self diff --git a/Sources/SwiftJava/SwiftJavaConversionError.swift b/Sources/SwiftJava/SwiftJavaConversionError.swift new file mode 100644 index 00000000..5b29741c --- /dev/null +++ b/Sources/SwiftJava/SwiftJavaConversionError.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 +// +//===----------------------------------------------------------------------===// + +/// Used to indicate Swift/Java conversion failures. +public struct SwiftJavaConversionError: Error { + public let message: String + + public init(_ message: String) { + self.message = message + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift index eadc509e..ee5dca36 100644 --- a/Sources/SwiftJava/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -9,6 +9,9 @@ public struct CharSequence { @JavaMethod public func toString() -> String + @JavaMethod + public func getChars(_ arg0: Int32, _ arg1: Int32, _ arg2: [UInt16], _ arg3: Int32) + @JavaMethod public func charAt(_ arg0: Int32) -> UInt16 diff --git a/Sources/SwiftJava/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift index 406b45ee..f79742a3 100644 --- a/Sources/SwiftJava/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1557,985 +1557,985 @@ extension JavaCharacter { if let COMMON = classObj.COMMON { self.init(javaHolder: COMMON.javaHolder) } else { - fatalError("Enum value COMMON was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COMMON was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LATIN: if let LATIN = classObj.LATIN { self.init(javaHolder: LATIN.javaHolder) } else { - fatalError("Enum value LATIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LATIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GREEK: if let GREEK = classObj.GREEK { self.init(javaHolder: GREEK.javaHolder) } else { - fatalError("Enum value GREEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GREEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYRILLIC: if let CYRILLIC = classObj.CYRILLIC { self.init(javaHolder: CYRILLIC.javaHolder) } else { - fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARMENIAN: if let ARMENIAN = classObj.ARMENIAN { self.init(javaHolder: ARMENIAN.javaHolder) } else { - fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HEBREW: if let HEBREW = classObj.HEBREW { self.init(javaHolder: HEBREW.javaHolder) } else { - fatalError("Enum value HEBREW was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HEBREW was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARABIC: if let ARABIC = classObj.ARABIC { self.init(javaHolder: ARABIC.javaHolder) } else { - fatalError("Enum value ARABIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARABIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYRIAC: if let SYRIAC = classObj.SYRIAC { self.init(javaHolder: SYRIAC.javaHolder) } else { - fatalError("Enum value SYRIAC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYRIAC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAANA: if let THAANA = classObj.THAANA { self.init(javaHolder: THAANA.javaHolder) } else { - fatalError("Enum value THAANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DEVANAGARI: if let DEVANAGARI = classObj.DEVANAGARI { self.init(javaHolder: DEVANAGARI.javaHolder) } else { - fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BENGALI: if let BENGALI = classObj.BENGALI { self.init(javaHolder: BENGALI.javaHolder) } else { - fatalError("Enum value BENGALI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BENGALI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GURMUKHI: if let GURMUKHI = classObj.GURMUKHI { self.init(javaHolder: GURMUKHI.javaHolder) } else { - fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUJARATI: if let GUJARATI = classObj.GUJARATI { self.init(javaHolder: GUJARATI.javaHolder) } else { - fatalError("Enum value GUJARATI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUJARATI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ORIYA: if let ORIYA = classObj.ORIYA { self.init(javaHolder: ORIYA.javaHolder) } else { - fatalError("Enum value ORIYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ORIYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAMIL: if let TAMIL = classObj.TAMIL { self.init(javaHolder: TAMIL.javaHolder) } else { - fatalError("Enum value TAMIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAMIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TELUGU: if let TELUGU = classObj.TELUGU { self.init(javaHolder: TELUGU.javaHolder) } else { - fatalError("Enum value TELUGU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TELUGU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KANNADA: if let KANNADA = classObj.KANNADA { self.init(javaHolder: KANNADA.javaHolder) } else { - fatalError("Enum value KANNADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KANNADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MALAYALAM: if let MALAYALAM = classObj.MALAYALAM { self.init(javaHolder: MALAYALAM.javaHolder) } else { - fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SINHALA: if let SINHALA = classObj.SINHALA { self.init(javaHolder: SINHALA.javaHolder) } else { - fatalError("Enum value SINHALA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SINHALA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAI: if let THAI = classObj.THAI { self.init(javaHolder: THAI.javaHolder) } else { - fatalError("Enum value THAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LAO: if let LAO = classObj.LAO { self.init(javaHolder: LAO.javaHolder) } else { - fatalError("Enum value LAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIBETAN: if let TIBETAN = classObj.TIBETAN { self.init(javaHolder: TIBETAN.javaHolder) } else { - fatalError("Enum value TIBETAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIBETAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MYANMAR: if let MYANMAR = classObj.MYANMAR { self.init(javaHolder: MYANMAR.javaHolder) } else { - fatalError("Enum value MYANMAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MYANMAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GEORGIAN: if let GEORGIAN = classObj.GEORGIAN { self.init(javaHolder: GEORGIAN.javaHolder) } else { - fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANGUL: if let HANGUL = classObj.HANGUL { self.init(javaHolder: HANGUL.javaHolder) } else { - fatalError("Enum value HANGUL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANGUL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ETHIOPIC: if let ETHIOPIC = classObj.ETHIOPIC { self.init(javaHolder: ETHIOPIC.javaHolder) } else { - fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHEROKEE: if let CHEROKEE = classObj.CHEROKEE { self.init(javaHolder: CHEROKEE.javaHolder) } else { - fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CANADIAN_ABORIGINAL: if let CANADIAN_ABORIGINAL = classObj.CANADIAN_ABORIGINAL { self.init(javaHolder: CANADIAN_ABORIGINAL.javaHolder) } else { - fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OGHAM: if let OGHAM = classObj.OGHAM { self.init(javaHolder: OGHAM.javaHolder) } else { - fatalError("Enum value OGHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OGHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .RUNIC: if let RUNIC = classObj.RUNIC { self.init(javaHolder: RUNIC.javaHolder) } else { - fatalError("Enum value RUNIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value RUNIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHMER: if let KHMER = classObj.KHMER { self.init(javaHolder: KHMER.javaHolder) } else { - fatalError("Enum value KHMER was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHMER was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MONGOLIAN: if let MONGOLIAN = classObj.MONGOLIAN { self.init(javaHolder: MONGOLIAN.javaHolder) } else { - fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HIRAGANA: if let HIRAGANA = classObj.HIRAGANA { self.init(javaHolder: HIRAGANA.javaHolder) } else { - fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KATAKANA: if let KATAKANA = classObj.KATAKANA { self.init(javaHolder: KATAKANA.javaHolder) } else { - fatalError("Enum value KATAKANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KATAKANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BOPOMOFO: if let BOPOMOFO = classObj.BOPOMOFO { self.init(javaHolder: BOPOMOFO.javaHolder) } else { - fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HAN: if let HAN = classObj.HAN { self.init(javaHolder: HAN.javaHolder) } else { - fatalError("Enum value HAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YI: if let YI = classObj.YI { self.init(javaHolder: YI.javaHolder) } else { - fatalError("Enum value YI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_ITALIC: if let OLD_ITALIC = classObj.OLD_ITALIC { self.init(javaHolder: OLD_ITALIC.javaHolder) } else { - fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GOTHIC: if let GOTHIC = classObj.GOTHIC { self.init(javaHolder: GOTHIC.javaHolder) } else { - fatalError("Enum value GOTHIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GOTHIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DESERET: if let DESERET = classObj.DESERET { self.init(javaHolder: DESERET.javaHolder) } else { - fatalError("Enum value DESERET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DESERET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INHERITED: if let INHERITED = classObj.INHERITED { self.init(javaHolder: INHERITED.javaHolder) } else { - fatalError("Enum value INHERITED was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INHERITED was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGALOG: if let TAGALOG = classObj.TAGALOG { self.init(javaHolder: TAGALOG.javaHolder) } else { - fatalError("Enum value TAGALOG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGALOG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANUNOO: if let HANUNOO = classObj.HANUNOO { self.init(javaHolder: HANUNOO.javaHolder) } else { - fatalError("Enum value HANUNOO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANUNOO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUHID: if let BUHID = classObj.BUHID { self.init(javaHolder: BUHID.javaHolder) } else { - fatalError("Enum value BUHID was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUHID was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGBANWA: if let TAGBANWA = classObj.TAGBANWA { self.init(javaHolder: TAGBANWA.javaHolder) } else { - fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LIMBU: if let LIMBU = classObj.LIMBU { self.init(javaHolder: LIMBU.javaHolder) } else { - fatalError("Enum value LIMBU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LIMBU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_LE: if let TAI_LE = classObj.TAI_LE { self.init(javaHolder: TAI_LE.javaHolder) } else { - fatalError("Enum value TAI_LE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_LE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_B: if let LINEAR_B = classObj.LINEAR_B { self.init(javaHolder: LINEAR_B.javaHolder) } else { - fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UGARITIC: if let UGARITIC = classObj.UGARITIC { self.init(javaHolder: UGARITIC.javaHolder) } else { - fatalError("Enum value UGARITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UGARITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHAVIAN: if let SHAVIAN = classObj.SHAVIAN { self.init(javaHolder: SHAVIAN.javaHolder) } else { - fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSMANYA: if let OSMANYA = classObj.OSMANYA { self.init(javaHolder: OSMANYA.javaHolder) } else { - fatalError("Enum value OSMANYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSMANYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRIOT: if let CYPRIOT = classObj.CYPRIOT { self.init(javaHolder: CYPRIOT.javaHolder) } else { - fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAILLE: if let BRAILLE = classObj.BRAILLE { self.init(javaHolder: BRAILLE.javaHolder) } else { - fatalError("Enum value BRAILLE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAILLE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUGINESE: if let BUGINESE = classObj.BUGINESE { self.init(javaHolder: BUGINESE.javaHolder) } else { - fatalError("Enum value BUGINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUGINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .COPTIC: if let COPTIC = classObj.COPTIC { self.init(javaHolder: COPTIC.javaHolder) } else { - fatalError("Enum value COPTIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COPTIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEW_TAI_LUE: if let NEW_TAI_LUE = classObj.NEW_TAI_LUE { self.init(javaHolder: NEW_TAI_LUE.javaHolder) } else { - fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GLAGOLITIC: if let GLAGOLITIC = classObj.GLAGOLITIC { self.init(javaHolder: GLAGOLITIC.javaHolder) } else { - fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIFINAGH: if let TIFINAGH = classObj.TIFINAGH { self.init(javaHolder: TIFINAGH.javaHolder) } else { - fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYLOTI_NAGRI: if let SYLOTI_NAGRI = classObj.SYLOTI_NAGRI { self.init(javaHolder: SYLOTI_NAGRI.javaHolder) } else { - fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERSIAN: if let OLD_PERSIAN = classObj.OLD_PERSIAN { self.init(javaHolder: OLD_PERSIAN.javaHolder) } else { - fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHAROSHTHI: if let KHAROSHTHI = classObj.KHAROSHTHI { self.init(javaHolder: KHAROSHTHI.javaHolder) } else { - fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BALINESE: if let BALINESE = classObj.BALINESE { self.init(javaHolder: BALINESE.javaHolder) } else { - fatalError("Enum value BALINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BALINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CUNEIFORM: if let CUNEIFORM = classObj.CUNEIFORM { self.init(javaHolder: CUNEIFORM.javaHolder) } else { - fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHOENICIAN: if let PHOENICIAN = classObj.PHOENICIAN { self.init(javaHolder: PHOENICIAN.javaHolder) } else { - fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHAGS_PA: if let PHAGS_PA = classObj.PHAGS_PA { self.init(javaHolder: PHAGS_PA.javaHolder) } else { - fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NKO: if let NKO = classObj.NKO { self.init(javaHolder: NKO.javaHolder) } else { - fatalError("Enum value NKO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NKO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SUNDANESE: if let SUNDANESE = classObj.SUNDANESE { self.init(javaHolder: SUNDANESE.javaHolder) } else { - fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BATAK: if let BATAK = classObj.BATAK { self.init(javaHolder: BATAK.javaHolder) } else { - fatalError("Enum value BATAK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BATAK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LEPCHA: if let LEPCHA = classObj.LEPCHA { self.init(javaHolder: LEPCHA.javaHolder) } else { - fatalError("Enum value LEPCHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LEPCHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OL_CHIKI: if let OL_CHIKI = classObj.OL_CHIKI { self.init(javaHolder: OL_CHIKI.javaHolder) } else { - fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VAI: if let VAI = classObj.VAI { self.init(javaHolder: VAI.javaHolder) } else { - fatalError("Enum value VAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAURASHTRA: if let SAURASHTRA = classObj.SAURASHTRA { self.init(javaHolder: SAURASHTRA.javaHolder) } else { - fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAYAH_LI: if let KAYAH_LI = classObj.KAYAH_LI { self.init(javaHolder: KAYAH_LI.javaHolder) } else { - fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .REJANG: if let REJANG = classObj.REJANG { self.init(javaHolder: REJANG.javaHolder) } else { - fatalError("Enum value REJANG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value REJANG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYCIAN: if let LYCIAN = classObj.LYCIAN { self.init(javaHolder: LYCIAN.javaHolder) } else { - fatalError("Enum value LYCIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYCIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CARIAN: if let CARIAN = classObj.CARIAN { self.init(javaHolder: CARIAN.javaHolder) } else { - fatalError("Enum value CARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYDIAN: if let LYDIAN = classObj.LYDIAN { self.init(javaHolder: LYDIAN.javaHolder) } else { - fatalError("Enum value LYDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAM: if let CHAM = classObj.CHAM { self.init(javaHolder: CHAM.javaHolder) } else { - fatalError("Enum value CHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_THAM: if let TAI_THAM = classObj.TAI_THAM { self.init(javaHolder: TAI_THAM.javaHolder) } else { - fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_VIET: if let TAI_VIET = classObj.TAI_VIET { self.init(javaHolder: TAI_VIET.javaHolder) } else { - fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AVESTAN: if let AVESTAN = classObj.AVESTAN { self.init(javaHolder: AVESTAN.javaHolder) } else { - fatalError("Enum value AVESTAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AVESTAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .EGYPTIAN_HIEROGLYPHS: if let EGYPTIAN_HIEROGLYPHS = classObj.EGYPTIAN_HIEROGLYPHS { self.init(javaHolder: EGYPTIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAMARITAN: if let SAMARITAN = classObj.SAMARITAN { self.init(javaHolder: SAMARITAN.javaHolder) } else { - fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANDAIC: if let MANDAIC = classObj.MANDAIC { self.init(javaHolder: MANDAIC.javaHolder) } else { - fatalError("Enum value MANDAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANDAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LISU: if let LISU = classObj.LISU { self.init(javaHolder: LISU.javaHolder) } else { - fatalError("Enum value LISU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LISU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BAMUM: if let BAMUM = classObj.BAMUM { self.init(javaHolder: BAMUM.javaHolder) } else { - fatalError("Enum value BAMUM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BAMUM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .JAVANESE: if let JAVANESE = classObj.JAVANESE { self.init(javaHolder: JAVANESE.javaHolder) } else { - fatalError("Enum value JAVANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value JAVANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEETEI_MAYEK: if let MEETEI_MAYEK = classObj.MEETEI_MAYEK { self.init(javaHolder: MEETEI_MAYEK.javaHolder) } else { - fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .IMPERIAL_ARAMAIC: if let IMPERIAL_ARAMAIC = classObj.IMPERIAL_ARAMAIC { self.init(javaHolder: IMPERIAL_ARAMAIC.javaHolder) } else { - fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOUTH_ARABIAN: if let OLD_SOUTH_ARABIAN = classObj.OLD_SOUTH_ARABIAN { self.init(javaHolder: OLD_SOUTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PARTHIAN: if let INSCRIPTIONAL_PARTHIAN = classObj.INSCRIPTIONAL_PARTHIAN { self.init(javaHolder: INSCRIPTIONAL_PARTHIAN.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PAHLAVI: if let INSCRIPTIONAL_PAHLAVI = classObj.INSCRIPTIONAL_PAHLAVI { self.init(javaHolder: INSCRIPTIONAL_PAHLAVI.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_TURKIC: if let OLD_TURKIC = classObj.OLD_TURKIC { self.init(javaHolder: OLD_TURKIC.javaHolder) } else { - fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAHMI: if let BRAHMI = classObj.BRAHMI { self.init(javaHolder: BRAHMI.javaHolder) } else { - fatalError("Enum value BRAHMI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAHMI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAITHI: if let KAITHI = classObj.KAITHI { self.init(javaHolder: KAITHI.javaHolder) } else { - fatalError("Enum value KAITHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAITHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_HIEROGLYPHS: if let MEROITIC_HIEROGLYPHS = classObj.MEROITIC_HIEROGLYPHS { self.init(javaHolder: MEROITIC_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_CURSIVE: if let MEROITIC_CURSIVE = classObj.MEROITIC_CURSIVE { self.init(javaHolder: MEROITIC_CURSIVE.javaHolder) } else { - fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SORA_SOMPENG: if let SORA_SOMPENG = classObj.SORA_SOMPENG { self.init(javaHolder: SORA_SOMPENG.javaHolder) } else { - fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAKMA: if let CHAKMA = classObj.CHAKMA { self.init(javaHolder: CHAKMA.javaHolder) } else { - fatalError("Enum value CHAKMA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAKMA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHARADA: if let SHARADA = classObj.SHARADA { self.init(javaHolder: SHARADA.javaHolder) } else { - fatalError("Enum value SHARADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHARADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAKRI: if let TAKRI = classObj.TAKRI { self.init(javaHolder: TAKRI.javaHolder) } else { - fatalError("Enum value TAKRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAKRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MIAO: if let MIAO = classObj.MIAO { self.init(javaHolder: MIAO.javaHolder) } else { - fatalError("Enum value MIAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MIAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CAUCASIAN_ALBANIAN: if let CAUCASIAN_ALBANIAN = classObj.CAUCASIAN_ALBANIAN { self.init(javaHolder: CAUCASIAN_ALBANIAN.javaHolder) } else { - fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BASSA_VAH: if let BASSA_VAH = classObj.BASSA_VAH { self.init(javaHolder: BASSA_VAH.javaHolder) } else { - fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DUPLOYAN: if let DUPLOYAN = classObj.DUPLOYAN { self.init(javaHolder: DUPLOYAN.javaHolder) } else { - fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELBASAN: if let ELBASAN = classObj.ELBASAN { self.init(javaHolder: ELBASAN.javaHolder) } else { - fatalError("Enum value ELBASAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELBASAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GRANTHA: if let GRANTHA = classObj.GRANTHA { self.init(javaHolder: GRANTHA.javaHolder) } else { - fatalError("Enum value GRANTHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GRANTHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAHAWH_HMONG: if let PAHAWH_HMONG = classObj.PAHAWH_HMONG { self.init(javaHolder: PAHAWH_HMONG.javaHolder) } else { - fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHOJKI: if let KHOJKI = classObj.KHOJKI { self.init(javaHolder: KHOJKI.javaHolder) } else { - fatalError("Enum value KHOJKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHOJKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_A: if let LINEAR_A = classObj.LINEAR_A { self.init(javaHolder: LINEAR_A.javaHolder) } else { - fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAHAJANI: if let MAHAJANI = classObj.MAHAJANI { self.init(javaHolder: MAHAJANI.javaHolder) } else { - fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANICHAEAN: if let MANICHAEAN = classObj.MANICHAEAN { self.init(javaHolder: MANICHAEAN.javaHolder) } else { - fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MENDE_KIKAKUI: if let MENDE_KIKAKUI = classObj.MENDE_KIKAKUI { self.init(javaHolder: MENDE_KIKAKUI.javaHolder) } else { - fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MODI: if let MODI = classObj.MODI { self.init(javaHolder: MODI.javaHolder) } else { - fatalError("Enum value MODI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MODI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MRO: if let MRO = classObj.MRO { self.init(javaHolder: MRO.javaHolder) } else { - fatalError("Enum value MRO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MRO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_NORTH_ARABIAN: if let OLD_NORTH_ARABIAN = classObj.OLD_NORTH_ARABIAN { self.init(javaHolder: OLD_NORTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NABATAEAN: if let NABATAEAN = classObj.NABATAEAN { self.init(javaHolder: NABATAEAN.javaHolder) } else { - fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PALMYRENE: if let PALMYRENE = classObj.PALMYRENE { self.init(javaHolder: PALMYRENE.javaHolder) } else { - fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAU_CIN_HAU: if let PAU_CIN_HAU = classObj.PAU_CIN_HAU { self.init(javaHolder: PAU_CIN_HAU.javaHolder) } else { - fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERMIC: if let OLD_PERMIC = classObj.OLD_PERMIC { self.init(javaHolder: OLD_PERMIC.javaHolder) } else { - fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PSALTER_PAHLAVI: if let PSALTER_PAHLAVI = classObj.PSALTER_PAHLAVI { self.init(javaHolder: PSALTER_PAHLAVI.javaHolder) } else { - fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIDDHAM: if let SIDDHAM = classObj.SIDDHAM { self.init(javaHolder: SIDDHAM.javaHolder) } else { - fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHUDAWADI: if let KHUDAWADI = classObj.KHUDAWADI { self.init(javaHolder: KHUDAWADI.javaHolder) } else { - fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIRHUTA: if let TIRHUTA = classObj.TIRHUTA { self.init(javaHolder: TIRHUTA.javaHolder) } else { - fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WARANG_CITI: if let WARANG_CITI = classObj.WARANG_CITI { self.init(javaHolder: WARANG_CITI.javaHolder) } else { - fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AHOM: if let AHOM = classObj.AHOM { self.init(javaHolder: AHOM.javaHolder) } else { - fatalError("Enum value AHOM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AHOM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ANATOLIAN_HIEROGLYPHS: if let ANATOLIAN_HIEROGLYPHS = classObj.ANATOLIAN_HIEROGLYPHS { self.init(javaHolder: ANATOLIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HATRAN: if let HATRAN = classObj.HATRAN { self.init(javaHolder: HATRAN.javaHolder) } else { - fatalError("Enum value HATRAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HATRAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MULTANI: if let MULTANI = classObj.MULTANI { self.init(javaHolder: MULTANI.javaHolder) } else { - fatalError("Enum value MULTANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MULTANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_HUNGARIAN: if let OLD_HUNGARIAN = classObj.OLD_HUNGARIAN { self.init(javaHolder: OLD_HUNGARIAN.javaHolder) } else { - fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIGNWRITING: if let SIGNWRITING = classObj.SIGNWRITING { self.init(javaHolder: SIGNWRITING.javaHolder) } else { - fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ADLAM: if let ADLAM = classObj.ADLAM { self.init(javaHolder: ADLAM.javaHolder) } else { - fatalError("Enum value ADLAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ADLAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BHAIKSUKI: if let BHAIKSUKI = classObj.BHAIKSUKI { self.init(javaHolder: BHAIKSUKI.javaHolder) } else { - fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MARCHEN: if let MARCHEN = classObj.MARCHEN { self.init(javaHolder: MARCHEN.javaHolder) } else { - fatalError("Enum value MARCHEN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MARCHEN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEWA: if let NEWA = classObj.NEWA { self.init(javaHolder: NEWA.javaHolder) } else { - fatalError("Enum value NEWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSAGE: if let OSAGE = classObj.OSAGE { self.init(javaHolder: OSAGE.javaHolder) } else { - fatalError("Enum value OSAGE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSAGE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGUT: if let TANGUT = classObj.TANGUT { self.init(javaHolder: TANGUT.javaHolder) } else { - fatalError("Enum value TANGUT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGUT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MASARAM_GONDI: if let MASARAM_GONDI = classObj.MASARAM_GONDI { self.init(javaHolder: MASARAM_GONDI.javaHolder) } else { - fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NUSHU: if let NUSHU = classObj.NUSHU { self.init(javaHolder: NUSHU.javaHolder) } else { - fatalError("Enum value NUSHU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NUSHU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOYOMBO: if let SOYOMBO = classObj.SOYOMBO { self.init(javaHolder: SOYOMBO.javaHolder) } else { - fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ZANABAZAR_SQUARE: if let ZANABAZAR_SQUARE = classObj.ZANABAZAR_SQUARE { self.init(javaHolder: ZANABAZAR_SQUARE.javaHolder) } else { - fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANIFI_ROHINGYA: if let HANIFI_ROHINGYA = classObj.HANIFI_ROHINGYA { self.init(javaHolder: HANIFI_ROHINGYA.javaHolder) } else { - fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOGDIAN: if let OLD_SOGDIAN = classObj.OLD_SOGDIAN { self.init(javaHolder: OLD_SOGDIAN.javaHolder) } else { - fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOGDIAN: if let SOGDIAN = classObj.SOGDIAN { self.init(javaHolder: SOGDIAN.javaHolder) } else { - fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DOGRA: if let DOGRA = classObj.DOGRA { self.init(javaHolder: DOGRA.javaHolder) } else { - fatalError("Enum value DOGRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DOGRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUNJALA_GONDI: if let GUNJALA_GONDI = classObj.GUNJALA_GONDI { self.init(javaHolder: GUNJALA_GONDI.javaHolder) } else { - fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAKASAR: if let MAKASAR = classObj.MAKASAR { self.init(javaHolder: MAKASAR.javaHolder) } else { - fatalError("Enum value MAKASAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAKASAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEDEFAIDRIN: if let MEDEFAIDRIN = classObj.MEDEFAIDRIN { self.init(javaHolder: MEDEFAIDRIN.javaHolder) } else { - fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELYMAIC: if let ELYMAIC = classObj.ELYMAIC { self.init(javaHolder: ELYMAIC.javaHolder) } else { - fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NANDINAGARI: if let NANDINAGARI = classObj.NANDINAGARI { self.init(javaHolder: NANDINAGARI.javaHolder) } else { - fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NYIAKENG_PUACHUE_HMONG: if let NYIAKENG_PUACHUE_HMONG = classObj.NYIAKENG_PUACHUE_HMONG { self.init(javaHolder: NYIAKENG_PUACHUE_HMONG.javaHolder) } else { - fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WANCHO: if let WANCHO = classObj.WANCHO { self.init(javaHolder: WANCHO.javaHolder) } else { - fatalError("Enum value WANCHO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WANCHO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YEZIDI: if let YEZIDI = classObj.YEZIDI { self.init(javaHolder: YEZIDI.javaHolder) } else { - fatalError("Enum value YEZIDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YEZIDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHORASMIAN: if let CHORASMIAN = classObj.CHORASMIAN { self.init(javaHolder: CHORASMIAN.javaHolder) } else { - fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DIVES_AKURU: if let DIVES_AKURU = classObj.DIVES_AKURU { self.init(javaHolder: DIVES_AKURU.javaHolder) } else { - fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHITAN_SMALL_SCRIPT: if let KHITAN_SMALL_SCRIPT = classObj.KHITAN_SMALL_SCRIPT { self.init(javaHolder: KHITAN_SMALL_SCRIPT.javaHolder) } else { - fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VITHKUQI: if let VITHKUQI = classObj.VITHKUQI { self.init(javaHolder: VITHKUQI.javaHolder) } else { - fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_UYGHUR: if let OLD_UYGHUR = classObj.OLD_UYGHUR { self.init(javaHolder: OLD_UYGHUR.javaHolder) } else { - fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRO_MINOAN: if let CYPRO_MINOAN = classObj.CYPRO_MINOAN { self.init(javaHolder: CYPRO_MINOAN.javaHolder) } else { - fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGSA: if let TANGSA = classObj.TANGSA { self.init(javaHolder: TANGSA.javaHolder) } else { - fatalError("Enum value TANGSA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGSA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TOTO: if let TOTO = classObj.TOTO { self.init(javaHolder: TOTO.javaHolder) } else { - fatalError("Enum value TOTO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TOTO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAWI: if let KAWI = classObj.KAWI { self.init(javaHolder: KAWI.javaHolder) } else { - fatalError("Enum value KAWI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAWI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NAG_MUNDARI: if let NAG_MUNDARI = classObj.NAG_MUNDARI { self.init(javaHolder: NAG_MUNDARI.javaHolder) } else { - fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UNKNOWN: if let UNKNOWN = classObj.UNKNOWN { self.init(javaHolder: UNKNOWN.javaHolder) } else { - fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run swift-java on the most updated Java class") } } } diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 0f1af1cd..a1147e3c 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import CSwiftJavaJNI -@JavaClass("java.lang.Class") +@JavaClass("java.lang.Class", implements: JavaReflectType.self) open class JavaClass: JavaObject { @JavaMethod open func getName() -> String @@ -52,13 +52,16 @@ open class JavaClass: JavaObject { open func isRecord() -> Bool @JavaMethod - open func getClassLoader() -> JavaClassLoader! + open func isSealed() -> Bool @JavaMethod - open func newInstance() throws -> JavaObject! + open func getInterfaces() -> [JavaClass?] @JavaMethod - open func getInterfaces() -> [JavaClass?] + open func getClassLoader() -> JavaClassLoader! + + @JavaMethod + open func newInstance() throws -> T! @JavaMethod open func isMemberClass() -> Bool @@ -70,7 +73,7 @@ open class JavaClass: JavaObject { open func isAnonymousClass() -> Bool @JavaMethod - open func getEnclosingClass() throws -> JavaClass! + open func getEnclosingClass() -> JavaClass! @JavaMethod open func arrayType() -> JavaClass! @@ -81,6 +84,9 @@ open class JavaClass: JavaObject { @JavaMethod open func getCanonicalName() -> String + @JavaMethod + open func getDeclaredClasses() -> [JavaClass?] + @JavaMethod open func getPackageName() -> String @@ -102,11 +108,17 @@ open class JavaClass: JavaObject { @JavaMethod open func isSynthetic() -> Bool + @JavaMethod + open func getGenericSuperclass() -> JavaReflectType! + + @JavaMethod + open func getGenericInterfaces() -> [JavaReflectType?] + @JavaMethod open func getSigners() -> [JavaObject?] @JavaMethod - open func getDeclaringClass() throws -> JavaClass! + open func getDeclaringClass() -> JavaClass! @JavaMethod open func getTypeName() -> String @@ -114,9 +126,6 @@ open class JavaClass: JavaObject { @JavaMethod open func getClasses() -> [JavaClass?] - @JavaMethod - open func getDeclaredClasses() throws -> [JavaClass?] - @JavaMethod open func getEnumConstants() -> [JavaObject?] @@ -128,16 +137,13 @@ open class JavaClass: JavaObject { @JavaMethod open func getNestMembers() -> [JavaClass?] - - @JavaMethod - open func isSealed() -> Bool } extension JavaClass { @JavaStaticMethod - public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod - public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod public func forPrimitiveName(_ arg0: String) -> JavaClass! where ObjectType == JavaClass diff --git a/Sources/SwiftJava/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift index 349cba8d..0cd64aa1 100644 --- a/Sources/SwiftJava/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -7,10 +7,10 @@ open class JavaClassLoader: JavaObject { open func getName() -> String @JavaMethod - open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! + open func loadClass(_ arg0: String) throws -> JavaClass! @JavaMethod - open func loadClass(_ arg0: String) throws -> JavaClass! + open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! @JavaMethod open func setSigners(_ arg0: JavaClass?, _ arg1: [JavaObject?]) @@ -22,10 +22,10 @@ open class JavaClassLoader: JavaObject { open func findLoadedClass(_ arg0: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String) throws -> JavaClass! + open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! + open func findClass(_ arg0: String) throws -> JavaClass! @JavaMethod open func resolveClass(_ arg0: JavaClass?) diff --git a/Sources/SwiftJava/generated/JavaEnum.swift b/Sources/SwiftJava/generated/JavaEnum.swift new file mode 100644 index 00000000..2b8e102c --- /dev/null +++ b/Sources/SwiftJava/generated/JavaEnum.swift @@ -0,0 +1,11 @@ +// // Auto-generated by Java-to-Swift wrapper generator. +// import CSwiftJavaJNI + +// @JavaClass("java.lang.Enum") +// open class JavaEnum: JavaObject { +// @JavaMethod +// public func name() -> String + +// @JavaMethod +// public func ordinal() -> Int32 +// } diff --git a/Sources/SwiftJava/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift index 94800037..df57ba66 100644 --- a/Sources/SwiftJava/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -3,7 +3,6 @@ import CSwiftJavaJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { - @JavaMethod @_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil) @@ -121,10 +120,10 @@ extension JavaClass { public func valueOf(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! + public func valueOf(_ arg0: Int32) -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: Int32) -> JavaInteger! + public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! @JavaStaticMethod public func toHexString(_ arg0: Int32) -> String @@ -133,31 +132,37 @@ extension JavaClass { public func decode(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func parseInt(_ arg0: String) throws -> Int32 + public func parseInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func parseInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func toUnsignedLong(_ arg0: Int32) -> Int64 + public func parseInt(_ arg0: String) throws -> Int32 @JavaStaticMethod - public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 + public func highestOneBit(_ arg0: Int32) -> Int32 @JavaStaticMethod - public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String + public func toUnsignedLong(_ arg0: Int32) -> Int64 + + @JavaStaticMethod + public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 @JavaStaticMethod public func toUnsignedString(_ arg0: Int32) -> String @JavaStaticMethod - public func parseUnsignedInt(_ arg0: String) throws -> Int32 + public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String @JavaStaticMethod public func parseUnsignedInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! + public func parseUnsignedInt(_ arg0: String) throws -> Int32 + + @JavaStaticMethod + public func parseUnsignedInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func getInteger(_ arg0: String, _ arg1: Int32) -> JavaInteger! @@ -166,13 +171,13 @@ extension JavaClass { public func getInteger(_ arg0: String) -> JavaInteger! @JavaStaticMethod - public func toOctalString(_ arg0: Int32) -> String + public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! @JavaStaticMethod - public func toBinaryString(_ arg0: Int32) -> String + public func toOctalString(_ arg0: Int32) -> String @JavaStaticMethod - public func highestOneBit(_ arg0: Int32) -> Int32 + public func toBinaryString(_ arg0: Int32) -> String @JavaStaticMethod public func lowestOneBit(_ arg0: Int32) -> Int32 diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index a986e9ef..7ea8fc09 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -126,10 +126,10 @@ extension JavaClass { public func compare(_ arg0: Int64, _ arg1: Int64) -> Int32 @JavaStaticMethod - public func valueOf(_ arg0: String) throws -> JavaLong! + public func valueOf(_ arg0: Int64) -> JavaLong! @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> JavaLong! + public func valueOf(_ arg0: String) throws -> JavaLong! @JavaStaticMethod public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaLong! @@ -140,6 +140,9 @@ extension JavaClass { @JavaStaticMethod public func decode(_ arg0: String) throws -> JavaLong! + @JavaStaticMethod + public func highestOneBit(_ arg0: Int64) -> Int64 + @JavaStaticMethod public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64 @@ -155,9 +158,6 @@ extension JavaClass { @JavaStaticMethod public func toBinaryString(_ arg0: Int64) -> String - @JavaStaticMethod - public func highestOneBit(_ arg0: Int64) -> Int64 - @JavaStaticMethod public func lowestOneBit(_ arg0: Int64) -> Int64 @@ -168,20 +168,20 @@ extension JavaClass { public func rotateRight(_ arg0: Int64, _ arg1: Int32) -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: String) throws -> Int64 + public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseUnsignedLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseUnsignedLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: String) throws -> Int64 + public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 } diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 08cc764a..5f10005f 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -4,7 +4,7 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { @JavaMethod - open func get() -> JavaObject! + open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439 @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool diff --git a/Sources/SwiftJava/generated/JavaReflectArray.swift b/Sources/SwiftJava/generated/JavaReflectArray.swift new file mode 100644 index 00000000..4cae1202 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectArray.swift @@ -0,0 +1,71 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.reflect.Array") +open class JavaReflectArray: JavaObject { + +} +extension JavaClass { + @JavaStaticMethod + public func get(_ arg0: JavaObject?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func getLength(_ arg0: JavaObject?) throws -> Int32 + + @JavaStaticMethod + public func getBoolean(_ arg0: JavaObject?, _ arg1: Int32) throws -> Bool + + @JavaStaticMethod + public func getByte(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int8 + + @JavaStaticMethod + public func getShort(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int16 + + @JavaStaticMethod + public func getChar(_ arg0: JavaObject?, _ arg1: Int32) throws -> UInt16 + + @JavaStaticMethod + public func getInt(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int32 + + @JavaStaticMethod + public func getLong(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int64 + + @JavaStaticMethod + public func getFloat(_ arg0: JavaObject?, _ arg1: Int32) throws -> Float + + @JavaStaticMethod + public func getDouble(_ arg0: JavaObject?, _ arg1: Int32) throws -> Double + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: [Int32]) throws -> JavaObject! + + @JavaStaticMethod + public func set(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: JavaObject?) throws + + @JavaStaticMethod + public func setBoolean(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Bool) throws + + @JavaStaticMethod + public func setByte(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int8) throws + + @JavaStaticMethod + public func setChar(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: UInt16) throws + + @JavaStaticMethod + public func setShort(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int16) throws + + @JavaStaticMethod + public func setInt(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int32) throws + + @JavaStaticMethod + public func setLong(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int64) throws + + @JavaStaticMethod + public func setFloat(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Float) throws + + @JavaStaticMethod + public func setDouble(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Double) throws +} diff --git a/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift new file mode 100644 index 00000000..a0801644 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift @@ -0,0 +1,17 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.ParameterizedType", extends: JavaReflectType.self) +public struct JavaReflectParameterizedType { + @JavaMethod + public func getOwnerType() -> JavaReflectType! + + @JavaMethod + public func getRawType() -> JavaReflectType! + + @JavaMethod + public func getActualTypeArguments() -> [JavaReflectType?] + + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/generated/JavaReflectType.swift b/Sources/SwiftJava/generated/JavaReflectType.swift new file mode 100644 index 00000000..fdf3b572 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectType.swift @@ -0,0 +1,8 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.Type") +public struct JavaReflectType { + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index b45671a7..d07ff162 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -5,6 +5,8 @@ "java.lang.Byte" : "JavaByte", "java.lang.Character" : "JavaCharacter", "java.lang.Class" : "JavaClass", + "java.lang.reflect.Type" : "JavaReflectType", + "java.lang.reflect.ParameterizedType" : "JavaReflectParameterizedType", "java.lang.ClassLoader" : "JavaClassLoader", "java.lang.Double" : "JavaDouble", "java.lang.Error" : "JavaError", diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 2d9b4311..0fb5494f 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -39,6 +39,9 @@ public struct Configuration: Codable { public var outputJavaDirectory: String? public var mode: JExtractGenerationMode? + public var effectiveMode: JExtractGenerationMode { + mode ?? .default + } public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable @@ -46,6 +49,7 @@ public struct Configuration: Codable { public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { unsignedNumbersMode ?? .default } + public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { minimumInputAccessLevelMode ?? .default @@ -56,7 +60,12 @@ public struct Configuration: Codable { memoryManagementMode ?? .default } - // ==== java 2 swift --------------------------------------------------------- + public var asyncFuncMode: JExtractAsyncFuncMode? + public var effectiveAsyncFuncMode: JExtractAsyncFuncMode { + asyncFuncMode ?? .default + } + + // ==== wrap-java --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. public var classpath: String? = nil @@ -76,6 +85,12 @@ public struct Configuration: Codable { // Generate class files suitable for the specified Java SE release. public var targetCompatibility: JavaVersion? + /// Filter input Java types by their package prefix if set. + public var filterInclude: [String]? + + /// Exclude input Java types by their package prefix or exact match. + public var filterExclude: [String]? + // ==== dependencies --------------------------------------------------------- // Java dependencies we need to fetch for this target. diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift new file mode 100644 index 00000000..221649c5 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Configures how Swift `async` functions should be extracted by jextract. +public enum JExtractAsyncFuncMode: String, Codable { + /// Extract Swift `async` APIs as Java functions that return `CompletableFuture`s. + case completableFuture + + /// Extract Swift `async` APIs as Java functions that return `Future`s. + /// + /// This mode is useful for platforms that do not have `CompletableFuture` support, such as + /// Android 23 and below. + /// + /// - Note: Prefer using the `completableFuture` mode instead, if possible. +// case future +} + +extension JExtractAsyncFuncMode { + public static var `default`: Self { + .completableFuture + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift new file mode 100644 index 00000000..1ad331da --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. +public enum JExtractGenerationMode: String, Codable { + /// Foreign Value and Memory API + case ffm + + /// Java Native Interface + case jni + + public static var `default`: JExtractGenerationMode { + .ffm + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift new file mode 100644 index 00000000..be77d27d --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Configures how memory should be managed by the user +public enum JExtractMemoryManagementMode: String, Codable { + /// Force users to provide an explicit `SwiftArena` to all calls that require them. + case explicit + + /// Provide both explicit `SwiftArena` support + /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. + case allowGlobalAutomatic + + public static var `default`: Self { + .explicit + } + + public var requiresGlobalArena: Bool { + switch self { + case .explicit: false + case .allowGlobalAutomatic: true + } + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift new file mode 100644 index 00000000..22fead57 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// The minimum access level which +public enum JExtractMinimumAccessLevelMode: String, Codable { + case `public` + case `package` + case `internal` +} + +extension JExtractMinimumAccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift similarity index 68% rename from Sources/SwiftJavaConfigurationShared/GenerationMode.swift rename to Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index 22fdd5f5..b53a2c6b 100644 --- a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -11,16 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - -/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. -public enum JExtractGenerationMode: String, Codable { - /// Foreign Value and Memory API - case ffm - - /// Java Native Interface - case jni -} - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, @@ -51,6 +41,7 @@ public enum JExtractUnsignedIntegerMode: String, Codable { // case widenOrAnnotate } + extension JExtractUnsignedIntegerMode { public var needsConversion: Bool { switch self { @@ -63,38 +54,3 @@ extension JExtractUnsignedIntegerMode { .annotate } } - -/// The minimum access level which -public enum JExtractMinimumAccessLevelMode: String, Codable { - case `public` - case `package` - case `internal` -} - -extension JExtractMinimumAccessLevelMode { - public static var `default`: Self { - .public - } -} - - -/// Configures how memory should be managed by the user -public enum JExtractMemoryManagementMode: String, Codable { - /// Force users to provide an explicit `SwiftArena` to all calls that require them. - case explicit - - /// Provide both explicit `SwiftArena` support - /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. - case allowGlobalAutomatic - - public static var `default`: Self { - .explicit - } - - public var requiresGlobalArena: Bool { - switch self { - case .explicit: false - case .allowGlobalAutomatic: true - } - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 997e680e..7e8c66d3 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -47,7 +47,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Swift Feature | FFM | JNI | |--------------------------------------------------------------------------------------|----------|-----| | Initializers: `class`, `struct` | ✅ | ✅ | -| Optional Initializers / Throwing Initializers | ❌ | ❌ | +| Optional Initializers / Throwing Initializers | ❌ | ✅ | | Deinitializers: `class`, `struct` | ✅ | ✅ | | `enum` | ❌ | ✅ | | `actor` | ❌ | ❌ | @@ -57,8 +57,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Typed throws: `func x() throws(E)` | ❌ | ❌ | | Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | | Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | -| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | -| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | +| Arrays: `[UInt8]`, `[MyType]`, `Array` etc | ❌ | ✅ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | | Generic return values in functions: `func f() -> T` | ❌ | ❌ | @@ -86,7 +86,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Subscripts: `subscript()` | ❌ | ❌ | | Equatable | ❌ | ❌ | | Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | -| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | +| Nested types: `struct Hello { struct World {} }` | ❌ | ✅ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | @@ -97,7 +97,6 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | | Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | | | | | | | | | @@ -136,10 +135,10 @@ on the Java side. | `Float` | `float` | | `Double` | `double` | -#### Unsigned numbers mode: wrap-guava +#### Unsigned numbers mode: wrapGuava You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations -as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers-mode wrapGuava` command line option, or set the corresponding configuration value in `swift-java.config` (TODO). This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every @@ -163,7 +162,7 @@ you are expected to add a Guava dependency to your Java project. | `Float` | `float` | | `Double` | `double` | -> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. +> Note: The `wrapGuava` mode is currently only available in FFM mode of jextract. ### Enums @@ -328,3 +327,21 @@ which conform a to a given Swift protocol. #### Returning protocol types Protocols are not yet supported as return types. + +### `async` functions + +> Note: Importing `async` functions is currently only available in the JNI mode of jextract. + +Asynchronous functions in Swift can be extraced using different modes, which are explained below. + +#### Async function mode: completable-future (default) + +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.CompletableFuture`. +This mode gives the most flexibility and should be prefered if your platform supports `CompletableFuture`. + +#### Async mode: future + +This is a mode for legacy platforms, where `CompletableFuture` is not available, such as Android 23 and below. +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.Future`. +To enable this mode pass the `--async-func-mode future` command line option, +or set the `asyncFuncMode` configuration value in `swift-java.config` diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift new file mode 100644 index 00000000..1c3079bc --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +extension _JNIMethodIDCache { + public enum CompletableFuture { + private static let completeMethod = Method( + name: "complete", + signature: "(Ljava/lang/Object;)Z" + ) + + private static let completeExceptionallyMethod = Method( + name: "completeExceptionally", + signature: "(Ljava/lang/Throwable;)Z" + ) + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/util/concurrent/CompletableFuture", + methods: [completeMethod, completeExceptionallyMethod] + ) + + public static var `class`: jclass { + cache.javaClass + } + + /// CompletableFuture.complete(T) + public static var complete: jmethodID { + cache.methods[completeMethod]! + } + + /// CompletableFuture.completeExceptionally(Throwable) + public static var completeExceptionally: jmethodID { + cache.methods[completeExceptionallyMethod]! + } + } + + public enum Exception { + private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Exception", + methods: [messageConstructor] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var constructWithMessage: jmethodID { + cache.methods[messageConstructor]! + } + } +} diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift new file mode 100644 index 00000000..68d98ffc --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI +import SwiftJava + +public enum _JNIBoxedConversions { + private static let booleanMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(Z)Ljava/lang/Boolean;", isStatic: true) + private static let byteMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(B)Ljava/lang/Byte;", isStatic: true) + private static let charMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(C)Ljava/lang/Character;", isStatic: true) + private static let shortMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(S)Ljava/lang/Short;", isStatic: true) + private static let intMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(I)Ljava/lang/Integer;", isStatic: true) + private static let longMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(J)Ljava/lang/Long;", isStatic: true) + private static let floatMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(F)Ljava/lang/Float;", isStatic: true) + private static let doubleMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(D)Ljava/lang/Double;", isStatic: true) + + private static let booleanCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Boolean", + methods: [booleanMethod] + ) + + private static let byteCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Byte", + methods: [byteMethod] + ) + private static let charCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Character", + methods: [charMethod] + ) + + private static let shortCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Short", + methods: [shortMethod] + ) + private static let intCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Integer", + methods: [intMethod] + ) + + private static let longCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Long", + methods: [longMethod] + ) + + private static let floatCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Float", + methods: [floatMethod] + ) + + private static let doubleCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Double", + methods: [doubleMethod] + ) + + public static func box(_ value: jboolean, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, booleanCache.javaClass, booleanCache.methods[booleanMethod]!, [jvalue(z: value)])! + } + + public static func box(_ value: jbyte, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, byteCache.javaClass, byteCache.methods[byteMethod]!, [jvalue(b: value)])! + } + + public static func box(_ value: jchar, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, charCache.javaClass, charCache.methods[charMethod]!, [jvalue(c: value)])! + } + + public static func box(_ value: jshort, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, shortCache.javaClass, shortCache.methods[shortMethod]!, [jvalue(s: value)])! + } + + public static func box(_ value: jint, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, intCache.javaClass, intCache.methods[intMethod]!, [jvalue(i: value)])! + } + + public static func box(_ value: jlong, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, longCache.javaClass, longCache.methods[longMethod]!, [jvalue(j: value)])! + } + + public static func box(_ value: jfloat, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, floatCache.javaClass, floatCache.methods[floatMethod]!, [jvalue(f: value)])! + } + + public static func box(_ value: jdouble, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, doubleCache.javaClass, doubleCache.methods[doubleMethod]!, [jvalue(d: value)])! + } +} diff --git a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift similarity index 67% rename from Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift rename to Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index a67d225f..dd7eb5d1 100644 --- a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -12,6 +12,9 @@ // //===----------------------------------------------------------------------===// +import CSwiftJavaJNI +import SwiftJava + /// A cache used to hold references for JNI method and classes. /// /// This type is used internally in by the outputted JExtract wrappers @@ -20,10 +23,12 @@ public final class _JNIMethodIDCache: Sendable { public struct Method: Hashable { public let name: String public let signature: String + public let isStatic: Bool - public init(name: String, signature: String) { + public init(name: String, signature: String, isStatic: Bool = false) { self.name = name self.signature = signature + self.isStatic = isStatic } } @@ -40,10 +45,18 @@ public final class _JNIMethodIDCache: Sendable { } self._class = environment.interface.NewGlobalRef(environment, clazz)! self.methods = methods.reduce(into: [:]) { (result, method) in - if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { - result[method] = methodID + if method.isStatic { + if let methodID = environment.interface.GetStaticMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Static method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } else { - fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } } } diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 3bce6c18..837d5e2f 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import Logging import ArgumentParser import Foundation import SwiftJavaToolLib @@ -27,6 +28,9 @@ import SwiftJavaShared extension SwiftJava { struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + + static let log: Logging.Logger = Logger(label: "swift-java:\(configuration.commandName!)") + static let configuration = CommandConfiguration( commandName: "configure", abstract: "Configure and emit a swift-java.config file based on an input dependency or jar file") @@ -63,12 +67,12 @@ extension SwiftJava { extension SwiftJava.ConfigureCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { let classpathEntries = self.configureCommandJVMClasspath( - searchDirs: [self.effectiveSwiftModuleURL], config: config) + searchDirs: [self.effectiveSwiftModuleURL], config: config, log: Self.log) let jvm = try self.makeJVM(classpathEntries: classpathEntries) - try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment()) + try emitConfiguration(classpathEntries: classpathEntries, environment: jvm.environment()) } /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. @@ -93,32 +97,41 @@ extension SwiftJava.ConfigureCommand { // TODO: make this perhaps "emit type mappings" mutating func emitConfiguration( - classpath: [String], + classpathEntries: [String], environment: JNIEnvironment ) throws { - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage { - print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") + var log = Self.log + log.logLevel = .init(rawValue: self.logLevel.rawValue)! + + log.info("Run: emit configuration...") + var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() + + if !self.commonOptions.filterInclude.isEmpty { + log.debug("Generate Java->Swift type mappings. Active include filters: \(self.commonOptions.filterInclude)") + } else if let filters = configuration.filterInclude, !filters.isEmpty { + // take the package filter from the configuration file + self.commonOptions.filterInclude = filters + } else { + log.debug("Generate Java->Swift type mappings. No package include filter applied.") } - print("[java-swift][debug] Classpath: \(classpath)") + log.debug("Classpath: \(classpathEntries)") - if classpath.isEmpty { - print("[java-swift][warning] Classpath is empty!") + if classpathEntries.isEmpty { + log.warning("Classpath is empty!") } // Get a fresh or existing configuration we'll amend - var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() if amendExistingConfig { - print("[swift-java] Amend existing swift-java.config file...") + log.info("Amend existing swift-java.config file...") } - configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct? + configuration.classpath = classpathEntries.joined(separator: ":") // TODO: is this correct? // Import types from all the classpath entries; // Note that we use the package level filtering, so users have some control over what gets imported. - let classpathEntries = classpath.split(separator: ":").map(String.init) for entry in classpathEntries { guard fileOrDirectoryExists(at: entry) else { // We only log specific jars missing, as paths may be empty directories that won't hurt not existing. - print("[debug][swift-java] Classpath entry does not exist: \(entry)") + log.debug("Classpath entry does not exist: \(entry)") continue } @@ -131,9 +144,9 @@ extension SwiftJava.ConfigureCommand { environment: environment ) } else if FileManager.default.fileExists(atPath: entry) { - print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") + log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") } else { - print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)") + log.warning("Classpath entry does not exist, skipping: \(entry)") } } @@ -154,6 +167,8 @@ extension SwiftJava.ConfigureCommand { forJar jarFile: JarFile, environment: JNIEnvironment ) throws { + let log = Self.log + for entry in jarFile.entries()! { // We only look at class files in the Jar file. guard entry.getName().hasSuffix(".class") else { @@ -179,9 +194,8 @@ extension SwiftJava.ConfigureCommand { let javaCanonicalName = String(entry.getName().replacing("/", with: ".") .dropLast(".class".count)) - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage, - !javaCanonicalName.hasPrefix(filterJavaPackage) { - // Skip classes which don't match our expected prefix + guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else { + log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters") continue } @@ -191,7 +205,17 @@ extension SwiftJava.ConfigureCommand { continue } - configuration.classes?[javaCanonicalName] = + if configuration.classes == nil { + configuration.classes = [:] + } + + if let configuredSwiftName = configuration.classes![javaCanonicalName] { + log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.") + } else { + log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.") + } + + configuration.classes![javaCanonicalName] = javaCanonicalName.defaultSwiftNameForJavaClass } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index ec7c0aeb..b5c3a7bb 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -40,7 +40,7 @@ extension SwiftJava { @OptionGroup var commonOptions: SwiftJava.CommonOptions @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") - var mode: JExtractGenerationMode = .ffm + var mode: JExtractGenerationMode? @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") var swiftModule: String @@ -61,14 +61,14 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false - @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") - var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrapGuava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") + var unsignedNumbersMode: JExtractUnsignedIntegerMode? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? - @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") - var memoryManagementMode: JExtractMemoryManagementMode = .default + @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allowGlobalAutomatic`, user can omit this parameter and a global GC-based arena will be used.") + var memoryManagementMode: JExtractMemoryManagementMode? @Option( help: """ @@ -78,23 +78,30 @@ extension SwiftJava { """ ) var dependsOn: [String] = [] + + @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") + var asyncFuncMode: JExtractAsyncFuncMode? } } extension SwiftJava.JExtractCommand { func runSwiftJavaCommand(config: inout Configuration) async throws { - if let javaPackage { - config.javaPackage = javaPackage - } + configure(&config.javaPackage, overrideWith: self.javaPackage) + configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift - config.writeEmptyFiles = writeEmptyFiles - config.unsignedNumbersMode = unsignedNumbers - config.minimumInputAccessLevelMode = minimumInputAccessLevel - config.memoryManagementMode = memoryManagementMode - try checkModeCompatibility() + // @Flag does not support optional, so we check ourself if it is passed + let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil + configure(&config.writeEmptyFiles, overrideWith: writeEmptyFiles) + + configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) + configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) + configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) + configure(&config.asyncFuncMode, overrideWith: self.asyncFuncMode) + + try checkModeCompatibility(config: config) if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -103,7 +110,7 @@ extension SwiftJava.JExtractCommand { config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" } - print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.mode ?? .ffm)".bold) + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.effectiveMode)".bold) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) @@ -113,16 +120,16 @@ extension SwiftJava.JExtractCommand { } /// Check if the configured modes are compatible, and fail if not - func checkModeCompatibility() throws { - if self.mode == .jni { - switch self.unsignedNumbers { + func checkModeCompatibility(config: Configuration) throws { + if config.effectiveMode == .jni { + switch config.effectiveUnsignedNumbersMode { case .annotate: () // OK case .wrapGuava: throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") } - } else if self.mode == .ffm { - guard self.memoryManagementMode == .explicit else { + } else if config.effectiveMode == .ffm { + guard config.effectiveMemoryManagementMode == .explicit else { throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") } } @@ -157,3 +164,4 @@ extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} extension JExtractMemoryManagementMode: ExpressibleByArgument {} +extension JExtractAsyncFuncMode: ExpressibleByArgument {} diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 58c64690..7cc4c477 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -104,7 +104,10 @@ extension SwiftJava.ResolveCommand { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") - let dependenciesClasspath = await resolveDependencies(dependencies: dependencies) + let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + .appendingPathComponent(".build") + + let dependenciesClasspath = await resolveDependencies(workDir: workDir, dependencies: dependencies) let classpathEntries = dependenciesClasspath.split(separator: ":") print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "") @@ -122,10 +125,15 @@ extension SwiftJava.ResolveCommand { /// /// - Parameter dependencies: maven-style dependencies to resolve /// - Returns: Colon-separated classpath - func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { - let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .appendingPathComponent(".build") - let resolverDir = try! createTemporaryDirectory(in: workDir) + func resolveDependencies(workDir: URL, dependencies: [JavaDependencyDescriptor]) async -> String { + print("Create directory: \(workDir.absoluteString)") + + let resolverDir: URL + do { + resolverDir = try createTemporaryDirectory(in: workDir) + } catch { + fatalError("Unable to create temp directory at: \(workDir.absoluteString)! \(error)") + } defer { try? FileManager.default.removeItem(at: resolverDir) } @@ -162,7 +170,9 @@ extension SwiftJava.ResolveCommand { } else { let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "")>>>") + "Command was: \(CommandLine.arguments.joined(separator: " ").bold)\n" + + "Output was: <<<\(outString)>>>;\n" + + "Err was: <<<\(errString)>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 69ad3ebe..ae985e77 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -14,15 +14,18 @@ import Foundation import ArgumentParser +import Logging import SwiftJavaToolLib import SwiftJava import JavaUtilJar -import SwiftJavaToolLib import SwiftJavaConfigurationShared extension SwiftJava { struct WrapJavaCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + + static let log: Logging.Logger = .init(label: "swift-java:\(configuration.commandName!)") + static let configuration = CommandConfiguration( commandName: "wrap-java", abstract: "Wrap Java classes with corresponding Swift bindings.") @@ -63,6 +66,9 @@ extension SwiftJava { extension SwiftJava.WrapJavaCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + configure(&config.filterInclude, append: self.commonOptions.filterInclude) + configure(&config.filterExclude, append: self.commonOptions.filterExclude) + // Get base classpath configuration for this target and configuration var classpathSearchDirs = [self.effectiveSwiftModuleURL] if let cacheDir = self.cacheDirectory { @@ -74,7 +80,7 @@ extension SwiftJava.WrapJavaCommand { print("[trace][swift-java] INPUT: \(input)") var classpathEntries = self.configureCommandJVMClasspath( - searchDirs: classpathSearchDirs, config: config) + searchDirs: classpathSearchDirs, config: config, log: Self.log) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn).map { moduleName, config in @@ -110,16 +116,19 @@ extension SwiftJava.WrapJavaCommand { extension SwiftJava.WrapJavaCommand { mutating func generateWrappers( config: Configuration, - // classpathEntries: [String], dependentConfigs: [(String, Configuration)], environment: JNIEnvironment ) throws { let translator = JavaTranslator( + config: config, swiftModuleName: effectiveSwiftModule, environment: environment, translateAsClass: true ) + log.info("Active include filters: \(config.filterInclude ?? [])") + log.info("Active exclude filters: \(config.filterExclude ?? [])") + // Keep track of all of the Java classes that will have // Swift-native implementations. translator.swiftNativeImplementations = Set(swiftNativeImplementation) @@ -136,12 +145,30 @@ extension SwiftJava.WrapJavaCommand { translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule) // Load all of the explicitly-requested classes. - let classLoader = try JavaClass(environment: environment) + let classLoader = try! JavaClass(environment: environment) .getSystemClassLoader()! var javaClasses: [JavaClass] = [] - for (javaClassName, _) in config.classes ?? [:] { + eachClass: for (javaClassName, _) in config.classes ?? [:] { + + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + continue + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + continue eachClass + } + } + + log.info("Wrapping java type: \(javaClassName)") + guard let javaClass = try classLoader.loadClass(javaClassName) else { - print("warning: could not find Java class '\(javaClassName)'") + log.warning("Could not load Java class '\(javaClassName)', skipping.") continue } @@ -175,8 +202,23 @@ extension SwiftJava.WrapJavaCommand { return nil } + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + return nil + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + return nil + } + } + // If this class has been explicitly mentioned, we're done. - if translator.translatedClasses[javaClassName] != nil { + guard translator.translatedClasses[javaClassName] == nil else { return nil } @@ -184,9 +226,8 @@ extension SwiftJava.WrapJavaCommand { let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName .defaultSwiftNameForJavaClass - let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)" - translator.translatedClasses[javaClassName] = (swiftName, nil) + translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName) return nestedClass } diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index c88ecbc8..ebdfdbea 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -20,6 +20,7 @@ import SwiftJava import JavaUtilJar import JavaNet import SwiftSyntax +import Logging import SwiftJavaConfigurationShared import SwiftJavaShared @@ -29,6 +30,18 @@ protocol HasCommonOptions { var commonOptions: SwiftJava.CommonOptions { get set } } extension HasCommonOptions { + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } + + func configure(_ setting: inout [T]?, append value: [T]?) { + if let value { + setting?.append(contentsOf: value) + } + } + var outputDirectory: String? { self.commonOptions.outputDirectory } @@ -43,7 +56,13 @@ extension SwiftJava { var inputSwift: String? = nil @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") - var logLevel: Logger.Level = .info + var logLevel: JExtractSwiftLib.Logger.Level = .info + + @Option(name: .long, help: "While scanning a classpath, inspect ONLY types included in these packages") + var filterInclude: [String] = [] + + @Option(name: .long, help: "While scanning a classpath, skip types which match the filter prefix") + var filterExclude: [String] = [] } struct CommonJVMOptions: ParsableArguments { @@ -52,9 +71,6 @@ extension SwiftJava { help: "Class search path of directories and zip/jar files from which Java classes can be loaded." ) var classpath: [String] = [] - - @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package") - var filterJavaPackage: String? = nil } } @@ -78,46 +94,46 @@ extension HasCommonJVMOptions { /// swift-java.classpath files as configured. /// Parameters: /// - searchDirs: search directories where we can find swift.java.classpath files to include in the configuration - func configureCommandJVMClasspath(searchDirs: [Foundation.URL], config: Configuration) -> [String] { + func configureCommandJVMClasspath(searchDirs: [Foundation.URL], config: Configuration, log: Logging.Logger) -> [String] { // Form a class path from all of our input sources: // * Command-line option --classpath let classpathOptionEntries: [String] = self.classpathEntries let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? [] - print("[debug][swift-java] Base classpath from CLASSPATH environment: \(classpathFromEnv)") + log.debug("Base classpath from CLASSPATH environment: \(classpathFromEnv)") let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? [] - print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)") + log.debug("Base classpath from config: \(classpathFromConfig)") var classpathEntries: [String] = classpathFromConfig for searchDir in searchDirs { let classPathFilesSearchDirectory = searchDir.path - print("[debug][swift-java] Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)") + log.debug("Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)") let foundSwiftJavaClasspath = findSwiftJavaClasspaths(in: classPathFilesSearchDirectory) - print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") + log.debug("Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") classpathEntries += foundSwiftJavaClasspath } if !classpathOptionEntries.isEmpty { - print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") + log.debug("Classpath from options: \(classpathOptionEntries)") classpathEntries += classpathOptionEntries } else { // * Base classpath from CLASSPATH env variable - print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)") + log.debug("Classpath from environment: \(classpathFromEnv)") classpathEntries += classpathFromEnv } let extraClasspath = self.commonJVMOptions.classpath let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init) - print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)") + log.debug("Extra classpath: \(extraClasspathEntries)") classpathEntries += extraClasspathEntries // Bring up the Java VM when necessary - // if logLevel >= .debug { + if log.logLevel >= .debug { let classpathString = classpathEntries.joined(separator: ":") - print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)") - // } + log.debug("Initialize JVM with classpath: \(classpathString)") + } return classpathEntries } diff --git a/Sources/SwiftJavaTool/ExcludedJDKTypes.swift b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift new file mode 100644 index 00000000..1d24022f --- /dev/null +++ b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension SwiftJava { + /// Some types we cannot handle importing, so we hardcode skipping them. + public static let ExcludedJDKTypes: Set = [ + "java.lang.Enum", + "java.lang.Enum$EnumDesc", + ] + + static func shouldImport(javaCanonicalName: String, commonOptions: SwiftJava.CommonOptions) -> Bool { + if SwiftJava.ExcludedJDKTypes.contains(javaCanonicalName) { + return false + } + + for include in commonOptions.filterInclude { + guard javaCanonicalName.hasPrefix(include) else { + // Skip classes which don't match our expected prefix + return false + } + } + + for exclude in commonOptions.filterExclude { + if javaCanonicalName.hasPrefix(exclude) { + return false + } + } + + return true + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index d465a206..27650885 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -17,6 +17,7 @@ import SwiftJavaShared import CSwiftJavaJNI import SwiftJava +// FIXME: do we need this here or can we rely on the generated one? @JavaClass("java.lang.ClassLoader") public struct ClassLoader { @JavaMethod diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 25b162e1..9763b4b3 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -24,9 +24,13 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftJavaConfigurationShared import SwiftJavaShared +import Logging protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { - var logLevel: Logger.Level { get set } + + var log: Logging.Logger { get } + + var logLevel: JExtractSwiftLib.Logger.Level { get set } /// Must be implemented with an `@OptionGroup` in Command implementations var commonOptions: SwiftJava.CommonOptions { get set } @@ -45,8 +49,8 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { public mutating func run() async { - print("[info][swift-java] Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") - print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: ".").path)") + self.log.info("Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") + self.log.info("Current work directory: \(URL(fileURLWithPath: ".").path)") do { var config = try readInitialConfiguration(command: self) @@ -54,12 +58,12 @@ extension SwiftJavaBaseAsyncParsableCommand { } catch { // We fail like this since throwing out of the run often ends up hiding the failure reason when it is executed as SwiftPM plugin (!) let message = "Failed with error: \(error)" - print("[error][java-swift] \(message)") + self.log.error("\(message)") fatalError(message) } // Just for debugging so it is clear which command has finished - print("[debug][swift-java] " + "Done: ".green + CommandLine.arguments.joined(separator: " ").green) + self.log.debug("\("Done: ".green) \(CommandLine.arguments.joined(separator: " ").green)") } } @@ -95,7 +99,11 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { - var logLevel: Logger.Level { + var log: Logging.Logger { // FIXME: replace with stored property inside specific commands + .init(label: "swift-java") + } + + var logLevel: JExtractSwiftLib.Logger.Level { get { self.commonOptions.logLevel } @@ -167,4 +175,4 @@ extension SwiftJavaBaseAsyncParsableCommand { config.logLevel = command.logLevel return config } -} \ No newline at end of file +} diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index a7c4d76f..795639e5 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -15,6 +15,8 @@ import SwiftJava import JavaLangReflect import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging /// Utility type that translates a single Java class into its corresponding /// Swift type and any additional helper types or functions. @@ -23,6 +25,10 @@ struct JavaClassTranslator { /// needed for translation. let translator: JavaTranslator + var log: Logger { + translator.log + } + /// The Java class (or interface) being translated. let javaClass: JavaClass @@ -47,7 +53,7 @@ struct JavaClassTranslator { let effectiveJavaSuperclass: JavaClass? /// The Swift name of the superclass. - let swiftSuperclass: String? + let swiftSuperclass: SwiftJavaParameterizedType? /// The Swift names of the interfaces that this class implements. let swiftInterfaces: [String] @@ -98,16 +104,16 @@ struct JavaClassTranslator { } /// The generic parameter clause for the Swift version of the Java class. - var genericParameterClause: String { + var genericParameters: [String] { if javaTypeParameters.isEmpty { - return "" + return [] } let genericParameters = javaTypeParameters.map { param in "\(param.getName()): AnyJavaObject" } - return "<\(genericParameters.joined(separator: ", "))>" + return genericParameters } /// Prepare translation for the given Java class (or interface). @@ -126,23 +132,38 @@ struct JavaClassTranslator { self.javaTypeParameters = javaClass.getTypeParameters().compactMap { $0 } self.nestedClasses = translator.nestedClasses[fullName] ?? [] - // Superclass. + // Superclass, incl parameter types (if any) if !javaClass.isInterface() { var javaSuperclass = javaClass.getSuperclass() - var swiftSuperclass: String? = nil + var javaGenericSuperclass: JavaReflectType? = javaClass.getGenericSuperclass() + var swiftSuperclassName: String? = nil + var swiftSuperclassTypeArgs: [String] = [] while let javaSuperclassNonOpt = javaSuperclass { do { - swiftSuperclass = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + swiftSuperclassName = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + if let javaGenericSuperclass = javaGenericSuperclass?.as(JavaReflectParameterizedType.self) { + for typeArg in javaGenericSuperclass.getActualTypeArguments() { + let javaTypeArgName = typeArg?.getTypeName() ?? "" + if let swiftTypeArgName = self.translator.translatedClasses[javaTypeArgName] { + swiftSuperclassTypeArgs.append(swiftTypeArgName.qualifiedName) + } else { + swiftSuperclassTypeArgs.append("/* MISSING MAPPING FOR */ \(javaTypeArgName)") + } + } + } break } catch { translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") } javaSuperclass = javaSuperclassNonOpt.getSuperclass() + javaGenericSuperclass = javaClass.getGenericSuperclass() } self.effectiveJavaSuperclass = javaSuperclass - self.swiftSuperclass = swiftSuperclass + self.swiftSuperclass = SwiftJavaParameterizedType( + name: swiftSuperclassName, + typeArguments: swiftSuperclassTypeArgs) } else { self.effectiveJavaSuperclass = nil self.swiftSuperclass = nil @@ -192,8 +213,9 @@ struct JavaClassTranslator { for method in methods { guard let method else { continue } - // Only look at public and protected methods here. - guard method.isPublic || method.isProtected else { continue } + guard shouldExtract(method: method) else { + continue + } // Skip any methods that are expected to be implemented in Swift. We will // visit them in the second pass, over the *declared* methods, because @@ -226,6 +248,20 @@ struct JavaClassTranslator { /// MARK: Collection of Java class members. extension JavaClassTranslator { + + /// Determines whether a method should be extracted for translation. + /// Only look at public and protected methods here. + private func shouldExtract(method: Method) -> Bool { + switch self.translator.config.effectiveMinimumInputAccessLevelMode { + case .internal: + return method.isPublic || method.isProtected || method.isPackage + case .package: + return method.isPublic || method.isProtected || method.isPackage + case .public: + return method.isPublic || method.isProtected + } + } + /// Add a field to the appropriate lists(s) for later translation. private mutating func addField(_ field: Field) { // Static fields go into a separate list. @@ -325,13 +361,25 @@ extension JavaClassTranslator { // Compute the "extends" clause for the superclass (of the struct // formulation) or the inheritance clause (for the class // formulation). - let extends: String + let extendsClause: String let inheritanceClause: String if translateAsClass { - extends = "" - inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? "" + extendsClause = "" + inheritanceClause = + if let swiftSuperclass, swiftSuperclass.typeArguments.isEmpty { + ": \(swiftSuperclass.name)" + } else if let swiftSuperclass { + ": \(swiftSuperclass.name)<\(swiftSuperclass.typeArguments.joined(separator: ", "))>" + } else { + "" + } } else { - extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + extendsClause = + if let swiftSuperclass { + ", extends: \(swiftSuperclass.render()).self" + } else { + "" + } inheritanceClause = "" } @@ -344,12 +392,19 @@ extension JavaClassTranslator { interfacesStr = ", \(prefix): \(swiftInterfaces.map { "\($0).self" }.joined(separator: ", "))" } + let genericParameterClause = + if genericParameters.isEmpty { + "" + } else { + "<\(genericParameters.joined(separator: ", "))>" + } + // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; let introducer = translateAsClass ? "open class" : "public struct" var classDecl: DeclSyntax = """ - @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) + @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extendsClause)\(raw: interfacesStr)) \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } @@ -396,7 +451,7 @@ extension JavaClassTranslator { } let genericArgumentClause = "<\(genericParameterNames.joined(separator: ", "))>" - staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" + staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" // FIXME: move the 'where ...' part into the render bit } else { staticMemberWhereClause = "" } @@ -418,7 +473,7 @@ extension JavaClassTranslator { do { return try renderMethod( method, implementedInSwift: /*FIXME:*/false, - genericParameterClause: genericParameterClause, + genericParameters: genericParameters, whereClause: staticMemberWhereClause ) } catch { @@ -435,7 +490,7 @@ extension JavaClassTranslator { // Specify the specialization arguments when needed. let extSpecialization: String - if genericParameterClause.isEmpty { + if genericParameters.isEmpty { extSpecialization = "<\(swiftTypeName)>" } else { extSpecialization = "" @@ -523,31 +578,76 @@ extension JavaClassTranslator { let accessModifier = javaConstructor.isPublic ? "public " : "" let convenienceModifier = translateAsClass ? "convenience " : "" let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" + + // FIXME: handle generics in constructors return """ @JavaMethod \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } + func genericParameterIsUsedInSignature(_ typeParam: TypeVariable, in method: Method) -> Bool { + // --- Return type + // Is the return type exactly the type param + // FIXME: make this equals based? + if method.getGenericReturnType().getTypeName() == typeParam.getTypeName() { + return true + } + + if let parameterizedReturnType = method.getGenericReturnType().as(ParameterizedType.self) { + for actualTypeParam in parameterizedReturnType.getActualTypeArguments() { + guard let actualTypeParam else { continue } + if actualTypeParam.isEqualTo(typeParam.as(Type.self)) { + return true + } + } + } + + return false + } + /// Translates the given Java method into a Swift declaration. package func renderMethod( _ javaMethod: Method, implementedInSwift: Bool, - genericParameterClause: String = "", + genericParameters: [String] = [], whereClause: String = "" ) throws -> DeclSyntax { - // Map the parameters. - let parameters = try translateJavaParameters(javaMethod.getParameters()) + // Map the generic params on the method. + var allGenericParameters = genericParameters + let typeParameters = javaMethod.getTypeParameters() + if typeParameters.contains(where: {$0 != nil }) { + allGenericParameters += typeParameters.compactMap { typeParam in + guard let typeParam else { return nil } + guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else { + return nil + } + return "\(typeParam.getTypeName()): AnyJavaObject" + } + } + let genericParameterClauseStr = + if allGenericParameters.isEmpty { + "" + } else { + "<\(allGenericParameters.joined(separator: ", "))>" + } + // Map the parameters. + let parameters = try translateJavaParameters(javaMethod) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") // Map the result type. let resultTypeStr: String - let resultType = try translator.getSwiftTypeNameAsString( - javaMethod.getGenericReturnType()!, - preferValueTypes: true, + let resultType = try translator.getSwiftReturnTypeNameAsString( + method: javaMethod, + preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) + // let resultType = try translator.getSwiftTypeNameAsString( + // javaMethod.getGenericReturnType()!, + // preferValueTypes: true, + // outerOptional: .implicitlyUnwrappedOptional + // ) // FIXME: cleanup the checking here if resultType != "Void" && resultType != "Swift.Void" { @@ -556,6 +656,7 @@ extension JavaClassTranslator { resultTypeStr = "" } + // --- Handle other effects let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" let swiftMethodName = javaMethod.getName().escapedSwiftName let methodAttribute: AttributeSyntax = implementedInSwift @@ -581,23 +682,24 @@ extension JavaClassTranslator { let resultOptional: String = resultType.optionalWrappedType() ?? resultType let baseBody: ExprSyntax = "\(raw: javaMethod.throwsCheckedException ? "try " : "")\(raw: swiftMethodName)(\(raw: parameters.map(\.passedArg).joined(separator: ", ")))" - let body: ExprSyntax = if let optionalType = resultType.optionalWrappedType() { - "Optional(javaOptional: \(baseBody))" - } else { - baseBody - } + let body: ExprSyntax = + if resultType.optionalWrappedType() != nil { + "Optional(javaOptional: \(baseBody))" + } else { + baseBody + } return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) - \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClause)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { + \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClauseStr)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { \(body) } """ } else { return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } } @@ -688,7 +790,7 @@ extension JavaClassTranslator { ? "self.init(javaHolder: \($0.getName()).javaHolder)" : "self = \($0.getName())") } else { - fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run swift-java on the most updated Java class") } """ }.joined(separator: "\n")) @@ -700,7 +802,30 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters( + _ javaMethod: JavaLangReflect.Method + ) throws -> [FunctionParameterSyntax] { + let parameters: [Parameter?] = javaMethod.getParameters() + + return try parameters.compactMap { javaParameter in + guard let javaParameter else { return nil } + + let typeName = try translator.getSwiftTypeNameAsString( + method: javaMethod, + javaParameter.getParameterizedType()!, + preferValueTypes: true, + outerOptional: .optional + ) + let paramName = javaParameter.getName() + return "_ \(raw: paramName): \(raw: typeName)" + } + } + + // Translate a Java parameter list into Swift parameters. + @available(*, deprecated, message: "Prefer the method based version") // FIXME: constructors are not well handled + private func translateJavaParameters( + _ parameters: [Parameter?] + ) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } @@ -778,9 +903,7 @@ extension JavaClassTranslator { continue } - // Ignore non-public, non-protected methods because they would not - // have been render into the Swift superclass. - if !overriddenMethod.isPublic && !overriddenMethod.isProtected { + guard shouldExtract(method: overriddenMethod) else { continue } @@ -791,6 +914,7 @@ extension JavaClassTranslator { return true } } catch { + // FIXME: logging } } @@ -815,114 +939,3 @@ extension [Type?] { } } -extension Type { - /// Adjust the given type to use its bounds, mirroring what we do in - /// mapping Java types into Swift. - func adjustToJavaBounds(adjusted: inout Bool) -> Type { - if let typeVariable = self.as(TypeVariable.self), - typeVariable.getBounds().count == 1, - let bound = typeVariable.getBounds()[0] { - adjusted = true - return bound - } - - if let wildcardType = self.as(WildcardType.self), - wildcardType.getUpperBounds().count == 1, - let bound = wildcardType.getUpperBounds()[0] { - adjusted = true - return bound - } - - return self - } - - /// Determine whether this type is equivalent to or a subtype of the other - /// type. - func isEqualTo(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualTo(adjustedOther) - } - - // If both are classes, check for equivalence. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - return selfClass.equals(otherClass.as(JavaObject.self)) - } - - // If both are arrays, check that their component types are equivalent. - if let selfArray = self.as(GenericArrayType.self), - let otherArray = other.as(GenericArrayType.self) { - return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) - } - - // If both are parameterized types, check their raw type and type - // arguments for equivalence. - if let selfParameterizedType = self.as(ParameterizedType.self), - let otherParameterizedType = other.as(ParameterizedType.self) { - if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { - return false - } - - return selfParameterizedType.getActualTypeArguments() - .allTypesEqual(otherParameterizedType.getActualTypeArguments()) - } - - // If both are type variables, compare their bounds. - // FIXME: This is a hack. - if let selfTypeVariable = self.as(TypeVariable.self), - let otherTypeVariable = other.as(TypeVariable.self) { - return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) - } - - // If both are wildcards, compare their upper and lower bounds. - if let selfWildcard = self.as(WildcardType.self), - let otherWildcard = other.as(WildcardType.self) { - return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) - && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) - } - - return false - } - - /// Determine whether this type is equivalent to or a subtype of the - /// other type. - func isEqualToOrSubtypeOf(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) - } - - if isEqualTo(other) { - return true - } - - // If both are classes, check for subclassing. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - // If either is a Java array, then this cannot be a subtype relationship - // in Swift. - if selfClass.isArray() || otherClass.isArray() { - return false - } - - return selfClass.isSubclass(of: otherClass) - } - - // Anything object-like is a subclass of java.lang.Object - if let otherClass = other.as(JavaClass.self), - otherClass.getName() == "java.lang.Object" { - if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || - self.is(WildcardType.self) || self.is(TypeVariable.self) { - return true - } - } - return false - } -} diff --git a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift new file mode 100644 index 00000000..7c781962 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaLangReflect +import JavaTypes +import SwiftBasicFormat +import SwiftJava +import SwiftJavaConfigurationShared +import SwiftSyntax +import SwiftSyntaxBuilder + +struct GenericJavaTypeOriginInfo { + enum GenericSource { + /// The source of the generic + case `class`([Type]) + case method + } + + var source: GenericSource + var type: Type +} + +/// if the type (that is used by the Method) is generic, return if the use originates from the method, or a surrounding class. +func getGenericJavaTypeOriginInfo(_ type: Type?, from method: Method) -> [GenericJavaTypeOriginInfo] { + guard let type else { + return [] + } + + guard isGenericJavaType(type) else { + return [] // it's not a generic type, no "origin" of the use to detect + } + + var methodTypeVars = method.getTypeParameters() + + // TODO: also handle nested classes here... + var classTypeVars = method.getDeclaringClass().getTypeParameters() + + var usedTypeVars: [TypeVariable] = [] + + return [] +} + +func isGenericJavaType(_ type: Type?) -> Bool { + guard let type else { + return false + } + + // Check if it's a type variable (e.g., T, E, etc.) + if type.as(TypeVariable.self) != nil { + return true + } + + // Check if it's a parameterized type (e.g., List, Map) + if let paramType = type.as(ParameterizedType.self) { + let typeArgs: [Type?] = paramType.getActualTypeArguments() + + // Check if any of the type arguments are generic + for typeArg in typeArgs { + guard let typeArg else { continue } + if isGenericJavaType(typeArg) { + return true + } + } + } + + // Check if it's a generic array type (e.g., T[], List[]) + if let arrayType = type.as(GenericArrayType.self) { + let componentType = arrayType.getGenericComponentType() + return isGenericJavaType(componentType) + } + + // Check if it's a wildcard type (e.g., ? extends Number, ? super String) + if type.as(WildcardType.self) != nil { + return true + } + + return false +} diff --git a/Sources/SwiftJavaToolLib/JavaHomeSupport.swift b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift new file mode 100644 index 00000000..cd18f7a8 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation + +/// Detected JAVA_HOME for this process. +package let javaHome: String = findJavaHome() + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +public func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty + { + return home + } + + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" + && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil + { + // Just ignore that we're missing a JAVA_HOME when building in Swift Package Index during general processing where no Java is needed. However, do _not_ suppress the error during SPI's compatibility build stage where Java is required. + return "" + } + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} + +/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. +public func getJavaHomeFromLibexecJavaHome() -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") + + // Check if the executable exists before trying to run it + guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { + print("/usr/libexec/java_home does not exist") + return nil + } + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe // Redirect standard error to the same pipe for simplicity + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters( + in: .whitespacesAndNewlines) + + if task.terminationStatus == 0 { + return output + } else { + print("java_home terminated with status: \(task.terminationStatus)") + // Optionally, log the error output for debugging + if let errorOutput = String( + data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { + print("Error output: \(errorOutput)") + } + return nil + } + } catch { + print("Error running java_home: \(error)") + return nil + } +} diff --git a/Sources/SwiftJavaToolLib/JavaParameterizedType.swift b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift new file mode 100644 index 00000000..a1b2c6b6 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// E.g. `Another` +struct SwiftJavaParameterizedType { + let name: String + let typeArguments: [String] + + init?(name: String?, typeArguments: [String]) { + guard let name else { + return nil + } + + self.name = name + self.typeArguments = typeArguments + } + + func render() -> String { + if typeArguments.isEmpty { + name + } else { + "\(name)<\(typeArguments.joined(separator: ", "))>" + } + } + + +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index 4fc9feb0..655c6765 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -16,11 +16,6 @@ import Foundation import SwiftJavaConfigurationShared extension JavaTranslator { -// /// Read a configuration file from the given URL. -// package static func readConfiguration(from url: URL) throws -> Configuration { -// let contents = try Data(contentsOf: url) -// return try JSONDecoder().decode(Configuration.self, from: contents) -// } /// Load the configuration file with the given name to populate the known set of /// translated Java classes. @@ -30,10 +25,7 @@ extension JavaTranslator { } for (javaClassName, swiftName) in classes { - translatedClasses[javaClassName] = ( - swiftType: swiftName, - swiftModule: swiftModule - ) + translatedClasses[javaClassName] = SwiftTypeName(module: swiftModule, name: swiftName) } } } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index 9d4d00ca..3a6468d5 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -12,24 +12,45 @@ // //===----------------------------------------------------------------------===// -package extension JavaTranslator { - struct SwiftTypeName: Hashable { - let swiftType: String - let swiftModule: String? +public typealias JavaFullyQualifiedTypeName = String - package init(swiftType: String, swiftModule: String?) { - self.swiftType = swiftType - self.swiftModule = swiftModule +package struct SwiftTypeName: Hashable, CustomStringConvertible { + package let swiftModule: String? + package let swiftType: String + + package init(module: String?, name: String) { + self.swiftModule = module + self.swiftType = name + } + + package var qualifiedName: String { + if let swiftModule { + "\(swiftModule).\(swiftType)" + } else { + "\(swiftType)" + } + } + + package var description: String { + if let swiftModule { + "`\(swiftModule)/\(swiftType)`" + } else { + "`\(swiftType)`" } } +} + +package extension JavaTranslator { struct SwiftToJavaMapping: Equatable { let swiftType: SwiftTypeName - let javaTypes: [String] + let javaTypes: [JavaFullyQualifiedTypeName] - package init(swiftType: SwiftTypeName, javaTypes: [String]) { + package init(swiftType: SwiftTypeName, javaTypes: [JavaFullyQualifiedTypeName]) { self.swiftType = swiftType self.javaTypes = javaTypes + precondition(!javaTypes.contains("com.google.protobuf.AbstractMessage$Builder"), + "\(swiftType) mapped as \(javaTypes)\n\(CommandLine.arguments.joined(separator: " "))") // XXX } } @@ -48,15 +69,23 @@ package extension JavaTranslator { private func mappingDescription(mapping: SwiftToJavaMapping) -> String { let javaTypes = mapping.javaTypes.map { "'\($0)'" }.joined(separator: ", ") - return "Swift Type: '\(mapping.swiftType.swiftModule ?? "")'.'\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" + return "Swift module: '\(mapping.swiftType.swiftModule ?? "")', type: '\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" } } func validateClassConfiguration() throws(ValidationError) { + // for a in translatedClasses { + // print("MAPPING = \(a.key) -> \(a.value.swiftModule?.escapedSwiftName ?? "").\(a.value.swiftType.escapedSwiftName)") + // } + // Group all classes by swift name - let groupedDictionary: [SwiftTypeName: [(String, (String, String?))]] = Dictionary(grouping: translatedClasses, by: { SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) }) + let groupedDictionary: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = Dictionary(grouping: translatedClasses, by: { + // SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) + $0.value + }) // Find all that are mapped to multiple names - let multipleClassesMappedToSameName: [SwiftTypeName: [(String, (String, String?))]] = groupedDictionary.filter { (key: SwiftTypeName, value: [(String, (String, String?))]) in + let multipleClassesMappedToSameName: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = groupedDictionary.filter { + (key: SwiftTypeName, value: [(JavaFullyQualifiedTypeName, SwiftTypeName)]) in value.count > 1 } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 7cb8a7ee..71e01179 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -19,10 +19,16 @@ import SwiftBasicFormat import SwiftSyntax import SwiftJavaConfigurationShared import SwiftSyntaxBuilder +import Foundation +import Logging /// Utility that translates Java classes into Swift source code to access /// those Java classes. package class JavaTranslator { + let config: Configuration + + let log: Logger + /// The name of the Swift module that we are translating into. let swiftModuleName: String @@ -35,7 +41,10 @@ package class JavaTranslator { /// A mapping from the name of each known Java class to the corresponding /// Swift type name and its Swift module. - package var translatedClasses: [String: (swiftType: String, swiftModule: String?)] = [:] + package var translatedClasses: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "byte[]": SwiftTypeName(module: nil, name: "[UInt8]") + ] /// A mapping from the name of each known Java class with the Swift value type /// (and its module) to which it is mapped. @@ -44,8 +53,8 @@ package class JavaTranslator { /// `translatedClasses` should map to a representation of the Java class (i.e., /// an AnyJavaObject-conforming type) whereas the entry here should map to /// a value type. - package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [ - "java.lang.String": ("String", "SwiftJava"), + package let translatedToValueTypes: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.String": SwiftTypeName(module: "SwiftJava", name: "String"), ] /// The set of Swift modules that need to be imported to make the generated @@ -64,15 +73,21 @@ package class JavaTranslator { package var nestedClasses: [String: [JavaClass]] = [:] package init( + config: Configuration, swiftModuleName: String, environment: JNIEnvironment, translateAsClass: Bool = false, format: BasicFormat = JavaTranslator.defaultFormat ) { + self.config = config self.swiftModuleName = swiftModuleName self.environment = environment self.translateAsClass = translateAsClass self.format = format + + var l = Logger(label: "swift-java") + l.logLevel = .init(rawValue: (config.logLevel ?? .info).rawValue)! + self.log = l } /// Clear out any per-file state when we want to start a new file. @@ -112,8 +127,28 @@ extension JavaTranslator { // MARK: Type translation extension JavaTranslator { + + func getSwiftReturnTypeNameAsString( + method: JavaLangReflect.Method, + preferValueTypes: Bool, + outerOptional: OptionalKind + ) throws -> String { + // let returnType = method.getReturnType() + let genericReturnType = method.getGenericReturnType() + + // Special handle the case when the return type is the generic type of the method: ` T foo()` + + // if isGenericJavaType(genericReturnType) { + // print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") + // getGenericJavaTypeOriginInfo(genericReturnType, from: method) + // } + + return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) + } + /// Turn a Java type into a string. func getSwiftTypeNameAsString( + method: JavaLangReflect.Method? = nil, _ javaType: Type, preferValueTypes: Bool, outerOptional: OptionalKind @@ -123,11 +158,7 @@ extension JavaTranslator { typeVariable.getBounds().count == 1, let bound = typeVariable.getBounds()[0] { - return try getSwiftTypeNameAsString( - bound, - preferValueTypes: preferValueTypes, - outerOptional: outerOptional - ) + return outerOptional.adjustTypeName(typeVariable.getName()) } // Replace wildcards with their upper bound. @@ -164,30 +195,42 @@ extension JavaTranslator { // Handle parameterized types by recursing on the raw type and the type // arguments. - if let parameterizedType = javaType.as(ParameterizedType.self), - let rawJavaType = parameterizedType.getRawType() - { - var rawSwiftType = try getSwiftTypeNameAsString( - rawJavaType, - preferValueTypes: false, - outerOptional: outerOptional - ) + if let parameterizedType = javaType.as(ParameterizedType.self) { + if let rawJavaType = parameterizedType.getRawType() { + var rawSwiftType = try getSwiftTypeNameAsString( + rawJavaType, + preferValueTypes: false, + outerOptional: outerOptional + ) - let optionalSuffix: String - if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { - optionalSuffix = "\(lastChar)" - rawSwiftType.removeLast() - } else { - optionalSuffix = "" - } + let optionalSuffix: String + if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { + optionalSuffix = "\(lastChar)" + rawSwiftType.removeLast() + } else { + optionalSuffix = "" + } - let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in - try typeArg.map { typeArg in - try getSwiftTypeNameAsString(typeArg, preferValueTypes: false, outerOptional: .nonoptional) + let typeArguments: [String] = try parameterizedType.getActualTypeArguments().compactMap { typeArg in + guard let typeArg else { return nil } + + let mappedSwiftName = try getSwiftTypeNameAsString(method: method, typeArg, preferValueTypes: false, outerOptional: .nonoptional) + + // FIXME: improve the get instead... + if mappedSwiftName == nil || mappedSwiftName == "JavaObject" { + // Try to salvage it, is it perhaps a type parameter? + if let method { + if method.getTypeParameters().contains(where: { $0?.getTypeName() == typeArg.getTypeName() }) { + return typeArg.getTypeName() + } + } + } + + return mappedSwiftName } - } - return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + } } // Handle direct references to Java classes. @@ -196,10 +239,12 @@ extension JavaTranslator { } let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes) - var resultString = swiftName - if isOptional { - resultString = outerOptional.adjustTypeName(resultString) - } + let resultString = + if isOptional { + outerOptional.adjustTypeName(swiftName) + } else { + swiftName + } return resultString } @@ -233,7 +278,10 @@ extension JavaTranslator { if preferValueTypes, let translatedValueType = translatedToValueTypes[name] { // Note that we need to import this Swift module. if translatedValueType.swiftModule != swiftModuleName { - importedSwiftModules.insert(translatedValueType.swiftModule) + guard let module = translatedValueType.swiftModule else { + preconditionFailure("Translated value type must have Swift module, but was nil! Type: \(translatedValueType)") + } + importedSwiftModules.insert(module) } return translatedValueType.swiftType diff --git a/Sources/SwiftJavaToolLib/JavaType+Equality.swift b/Sources/SwiftJavaToolLib/JavaType+Equality.swift new file mode 100644 index 00000000..b3d4a37a --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaType+Equality.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava +import JavaLangReflect +import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging + +extension Type { + /// Adjust the given type to use its bounds, mirroring what we do in + /// mapping Java types into Swift. + func adjustToJavaBounds(adjusted: inout Bool) -> Type { + if let typeVariable = self.as(TypeVariable.self), + typeVariable.getBounds().count == 1, + let bound = typeVariable.getBounds()[0] { + adjusted = true + return bound + } + + if let wildcardType = self.as(WildcardType.self), + wildcardType.getUpperBounds().count == 1, + let bound = wildcardType.getUpperBounds()[0] { + adjusted = true + return bound + } + + return self + } + + /// Determine whether this type is equivalent to or a subtype of the other + /// type. + func isEqualTo(_ other: Type, file: String = #file, line: Int = #line, function: String = #function) -> Bool { + if self.javaHolder.object == other.javaHolder.object { + return true + } + + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualTo(adjustedOther) + } + + // If both are classes, check for equivalence. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + return selfClass.equals(otherClass.as(JavaObject.self)) + } + + // If both are arrays, check that their component types are equivalent. + if let selfArray = self.as(GenericArrayType.self), + let otherArray = other.as(GenericArrayType.self) { + return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) + } + + // If both are parameterized types, check their raw type and type + // arguments for equivalence. + if let selfParameterizedType = self.as(ParameterizedType.self), + let otherParameterizedType = other.as(ParameterizedType.self) { + if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { + return false + } + + return selfParameterizedType.getActualTypeArguments() + .allTypesEqual(otherParameterizedType.getActualTypeArguments()) + } + + // If both are type variables, compare their bounds. + // FIXME: This is a hack. + if let selfTypeVariable = self.as(TypeVariable.self), + let otherTypeVariable = other.as(TypeVariable.self) { + return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) + } + + // If both are wildcards, compare their upper and lower bounds. + if let selfWildcard = self.as(WildcardType.self), + let otherWildcard = other.as(WildcardType.self) { + return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) + && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) + } + + return false + } + + /// Determine whether this type is equivalent to or a subtype of the + /// other type. + func isEqualToOrSubtypeOf(_ other: Type) -> Bool { + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) + } + + if isEqualTo(other) { + return true + } + + // If both are classes, check for subclassing. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + // If either is a Java array, then this cannot be a subtype relationship + // in Swift. + if selfClass.isArray() || otherClass.isArray() { + return false + } + + return selfClass.isSubclass(of: otherClass) + } + + // Anything object-like is a subclass of java.lang.Object + if let otherClass = other.as(JavaClass.self), + otherClass.getName() == "java.lang.Object" { + if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || + self.is(WildcardType.self) || self.is(TypeVariable.self) { + return true + } + } + return false + } +} diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift similarity index 100% rename from Sources/SwiftKitSwift/SwiftKit.swift rename to Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift diff --git a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift index aaf53ea0..e84d1414 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift @@ -1213,8 +1213,7 @@ extension UInt8 { extension OutputProtocol { internal func output(from data: [UInt8]) throws -> OutputType { return try data.withUnsafeBytes { - let span = RawSpan(_unsafeBytes: $0) - return try self.output(from: span) + return try self.output(from: $0) } } } diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 82874e20..bc6bd279 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -43,14 +43,17 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(25)) } - // Support Android 6+ (Java 7) - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType(JavaCompile).configureEach { + // Support Java 11 + options.release.set(11) } dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } @@ -70,10 +73,10 @@ tasks.test { } } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -81,7 +84,8 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 4383a6fe..3514d9c1 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -16,6 +16,8 @@ import java.util.LinkedList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; public class ConfinedSwiftMemorySession implements ClosableSwiftArena { @@ -23,21 +25,17 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final static int CLOSED = 0; final static int ACTIVE = 1; - final Thread owner; final AtomicInteger state; final ConfinedResourceList resources; - public ConfinedSwiftMemorySession(Thread owner) { - this.owner = owner; + public ConfinedSwiftMemorySession() { this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); } void checkValid() throws RuntimeException { - if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); - } else if (this.state.get() < ACTIVE) { + if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } } @@ -61,8 +59,7 @@ public void register(SwiftInstance instance) { } static final class ConfinedResourceList implements SwiftResourceList { - // TODO: Could use intrusive linked list to avoid one indirection here - final List resourceCleanups = new LinkedList<>(); + final Queue resourceCleanups = new ConcurrentLinkedQueue<>(); void add(SwiftInstanceCleanup cleanup) { resourceCleanups.add(cleanup); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index 3b6c4626..96353c37 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -31,7 +31,7 @@ public interface SwiftArena { void register(SwiftInstance instance); static ClosableSwiftArena ofConfined() { - return new ConfinedSwiftMemorySession(Thread.currentThread()); + return new ConfinedSwiftMemorySession(); } static SwiftArena ofAuto() { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index a093cc50..7eccde74 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -24,10 +24,10 @@ public final class SwiftLibraries { - // Library names of core Swift and SwiftKit + // Library names of core Swift and SwiftRuntimeFunctions public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; - public static final String LIB_NAME_SWIFTKITSWIFT = "SwiftKitSwift"; + public static final String LIB_NAME_SWIFT_RUNTIME_FUNCTIONS = "SwiftRuntimeFunctions"; /** * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. @@ -42,10 +42,10 @@ public final class SwiftLibraries { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); - if (loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_CORE); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); } return true; } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index d818586d..08a36a3e 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -42,13 +42,14 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } dependencies { implementation(project(':SwiftKitCore')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } @@ -74,10 +75,10 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs.add("-Xlint:preview") } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -85,7 +86,8 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java index 08fad15b..b72818a3 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java @@ -24,7 +24,7 @@ public interface AllocatingSwiftArena extends SwiftArena, SegmentAllocator { MemorySegment allocate(long byteSize, long byteAlignment); static ClosableAllocatingSwiftArena ofConfined() { - return new FFMConfinedSwiftMemorySession(Thread.currentThread()); + return new FFMConfinedSwiftMemorySession(); } static AllocatingSwiftArena ofAuto() { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java index 424e54b6..05daebd7 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java @@ -22,8 +22,8 @@ final class FFMConfinedSwiftMemorySession extends ConfinedSwiftMemorySession implements AllocatingSwiftArena, ClosableAllocatingSwiftArena { final Arena arena; - public FFMConfinedSwiftMemorySession(Thread owner) { - super(owner); + public FFMConfinedSwiftMemorySession() { + super(); this.arena = Arena.ofConfined(); } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index e03b9bfe..74a270c0 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -33,7 +33,7 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final String SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME = "SwiftRuntimeFunctions"; private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -42,10 +42,10 @@ public class SwiftRuntime { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { System.loadLibrary(STDLIB_DYLIB_NAME); - if (loadSwiftKit) { - System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME); } return true; } @@ -487,4 +487,4 @@ boolean isEnabled() { } throw new IllegalArgumentException("Not handled log group: " + this); } -} \ No newline at end of file +} diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 66563fee..67671548 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -42,7 +42,7 @@ func assertOutput( let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) - try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) + try! translator.analyze(path: "/fake/Fake.swiftinterface", text: input) let output: String var printer: CodePrinter = CodePrinter(mode: .accumulateAll) @@ -179,7 +179,9 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(n): \(e)".yellow(if: diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n))) + let isMismatch = diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n) + let marker = isMismatch ? " // <<<<<<<<<<< mismatch" : "" + print("\(n): \(e)\(marker)".yellow(if: isMismatch)) } } @@ -188,15 +190,7 @@ func assertOutput( let printFromLineNo = matchingOutputOffset for (n, g) in gotLines.enumerated() where n >= printFromLineNo { let baseLine = "\(n): \(g)" - var line = baseLine - if diffLineNumbers.contains(n) { - line += "\n" - let leadingCount = "\(n): ".count - let message = "\(String(repeating: " ", count: leadingCount))\(String(repeating: "^", count: 8)) EXPECTED MATCH OR SEARCHING FROM HERE " - line += "\(message)\(String(repeating: "^", count: max(0, line.count - message.count)))" - line = line.red - } - print(line) + print(baseLine) } print("==== ---------------------------------------------------------------\n") } @@ -248,14 +242,18 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(e)".yellow(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? " // <<<<<<<< error: mismatch" : "" + print("\(e)\(marker)".yellow(if: isMismatch)) } } print("==== ---------------------------------------------------------------") print("Got output:") for (n, g) in gotLines.enumerated() { - print("\(g)".red(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? "// <<<<<<<< error: mismatch" : "" + print("\(g)\(marker)".red(if: isMismatch)) } print("==== ---------------------------------------------------------------\n") } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index cf4ed387..bad4174d 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -16,7 +16,14 @@ import JExtractSwiftLib import Testing final class DataImportTests { - let data_interfaceFile = + private static let ifConfigImport = """ + #if canImport(FoundationEssentials) + import FoundationEssentials + #else + import Foundation + #endif + """ + private static let foundationData_interfaceFile = """ import Foundation @@ -24,23 +31,53 @@ final class DataImportTests { public func returnData() -> Data """ - let dataProtocol_interfaceFile = + private static let foundationDataProtocol_interfaceFile = """ import Foundation public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ + private static let essentialsData_interfaceFile = + """ + import FoundationEssentials + + public func receiveData(dat: Data) + public func returnData() -> Data + """ - @Test("Import Data: Swift thunks") - func data_swiftThunk() throws { + private static let essentialsDataProtocol_interfaceFile = + """ + import FoundationEssentials + + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) + """ + private static let ifConfigData_interfaceFile = + """ + \(ifConfigImport) + + public func receiveData(dat: Data) + public func returnData() -> Data + """ + + private static let ifConfigDataProtocol_interfaceFile = + """ + \(ifConfigImport) + + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) + """ + + @Test("Import Data: Swift thunks", arguments: zip( + [Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) + func data_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( - input: data_interfaceFile, .ffm, .swift, + input: fileContent, .ffm, .swift, + detectChunkByInitialLines: 10, expectedChunks: [ - """ - import Foundation - """, + expectedImportChunk, """ @_cdecl("swiftjava_SwiftModule_receiveData_dat") public func swiftjava_SwiftModule_receiveData_dat(_ dat: UnsafeRawPointer) { @@ -87,11 +124,12 @@ final class DataImportTests { ) } - @Test("Import Data: JavaBindings") - func data_javaBindings() throws { - + @Test("Import Data: JavaBindings", arguments: [ + Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile + ]) + func data_javaBindings(fileContent: String) throws { try assertOutput( - input: data_interfaceFile, .ffm, .java, + input: fileContent, .ffm, .java, expectedChunks: [ """ /** @@ -333,14 +371,15 @@ final class DataImportTests { ) } - @Test("Import DataProtocol: Swift thunks") - func dataProtocol_swiftThunk() throws { + @Test("Import DataProtocol: Swift thunks", arguments: zip( + [Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) + func dataProtocol_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( - input: dataProtocol_interfaceFile, .ffm, .swift, + input: fileContent, .ffm, .swift, expectedChunks: [ - """ - import Foundation - """, + expectedImportChunk, """ @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2") public func swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(_ dat: UnsafeRawPointer, _ dat2: UnsafeRawPointer?) { @@ -356,11 +395,13 @@ final class DataImportTests { ) } - @Test("Import DataProtocol: JavaBindings") - func dataProtocol_javaBindings() throws { + @Test("Import DataProtocol: JavaBindings", arguments: [ + Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile + ]) + func dataProtocol_javaBindings(fileContent: String) throws { try assertOutput( - input: dataProtocol_interfaceFile, .ffm, .java, + input: fileContent, .ffm, .java, expectedChunks: [ """ /** diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift new file mode 100644 index 00000000..5bb422ea --- /dev/null +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwiftLib +import SwiftJavaConfigurationShared +import Testing + +final class FFMNestedTypesTests { + let class_interfaceFile = + """ + public enum MyNamespace { } + + extension MyNamespace { + public struct MyNestedStruct { + public func test() {} + } + } + """ + + @Test("Import: Nested type in extension MyNamespace { struct MyName {} }") + func test_nested_in_extension() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + guard let ty = st.importedTypes["MyNamespace.MyNestedStruct"] else { + fatalError("Didn't import nested type!") + } + + + + } + +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 82747ec9..79b51c19 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -42,7 +42,7 @@ final class FuncCallbackImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! @@ -131,7 +131,7 @@ final class FuncCallbackImportTests { config.swiftModule = "__FakeModule" let st = Swift2JavaTranslator(config: config) - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! @@ -247,7 +247,7 @@ final class FuncCallbackImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index ff19f4a2..b6ae6f6c 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -239,7 +239,7 @@ extension FunctionDescriptorTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel - try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == methodIdentifier @@ -273,7 +273,7 @@ extension FunctionDescriptorTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel - try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, diff --git a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift new file mode 100644 index 00000000..ebe3f807 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// 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 Testing + +@Suite +struct JNIArrayTest { + + @Test("Import: (Array) -> Array (Java)") + func uint8Array_explicitType_java() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static byte[] f(@Unsigned byte[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native byte[] $f(byte[] array); + """, + ] + ) + } + + @Test("Import: (Array) -> Array (Swift)") + func uint8Array_explicitType_swift() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3B") + func Java_com_example_swift_SwiftModule__00024f___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) -> jbyteArray? { + return SwiftModule.f(array: [UInt8](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([UInt8]) -> [UInt8] (Java)") + func uint8Array_syntaxSugar_java() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static byte[] f(@Unsigned byte[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native byte[] $f(byte[] array); + """, + ] + ) + } + + @Test("Import: ([UInt8]) -> [UInt8] (Swift)") + func uint8Array_syntaxSugar_swift() throws { + try assertOutput( + input: "public func f(array: [UInt8]) -> [UInt8] {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3B") + func Java_com_example_swift_SwiftModule__00024f___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) -> jbyteArray? { + return SwiftModule.f(array: [UInt8](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([Int64]) -> [Int64] (Java)") + func int64Array_syntaxSugar_java() throws { + try assertOutput( + input: "public func f(array: [Int64]) -> [Int64] {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static long[] f(long[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native long[] $f(long[] array); + """, + ] + ) + } + + @Test("Import: ([Int64]) -> [Int64] (Swift)") + func int64Array_syntaxSugar_swift() throws { + try assertOutput( + input: "public func f(array: [Int64]) -> [Int64] {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3J") + func Java_com_example_swift_SwiftModule__00024f___3J(environment: UnsafeMutablePointer!, thisClass: jclass, array: jlongArray?) -> jlongArray? { + return SwiftModule.f(array: [Int64](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([MySwiftClass]) -> [MySwiftClass] (Java)") + func swiftClassArray_syntaxSugar_java() throws { + try assertOutput( + input: """ + public class MySwiftClass {} + public func f(array: [MySwiftClass]) -> [MySwiftClass] {} + """, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MySwiftClass[] f(MySwiftClass[] array, SwiftArena swiftArena$) { + return Arrays.stream(SwiftModule.$f(Arrays.stream(Objects.requireNonNull(array, "array must not be null")).mapToLong(MySwiftClass::$memoryAddress).toArray())).mapToObj((pointer) -> { + return MySwiftClass.wrapMemoryAddressUnsafe(pointer, swiftArena$); + } + ).toArray(MySwiftClass[]::new); + } + """, + """ + private static native long[] $f(long[] array); + """, + ] + ) + } + + @Test("Import: ([MySwiftClass]) -> [MySwiftClass] (Swift)") + func swiftClassArray_syntaxSugar_swift() throws { + try assertOutput( + input: """ + public class MySwiftClass {} + public func f(array: [MySwiftClass]) -> [MySwiftClass] {} + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3J") + func Java_com_example_swift_SwiftModule__00024f___3J(environment: UnsafeMutablePointer!, thisClass: jclass, array: jlongArray?) -> jlongArray? { + return SwiftModule.f(array: [Int64](fromJNI: array, in: environment).map( { (pointer$) in + assert(pointer$ != 0, "pointer$ memory address was null") + let pointer$Bits$ = Int(pointer$) + let pointer$$ = UnsafeMutablePointer(bitPattern: pointer$Bits$) + guard let pointer$$ else { + fatalError("pointer$ memory address was null in call to \\(#function)!") + } + return pointer$$.pointee + } + )).map( { (object$) in + let object$$ = UnsafeMutablePointer.allocate(capacity: 1) + object$$.initialize(to: object$) + let object$Bits$ = Int64(Int(bitPattern: object$$)) + return object$Bits$ + } + ).getJNIValue(in: environment) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift new file mode 100644 index 00000000..1930e601 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -0,0 +1,411 @@ +//===----------------------------------------------------------------------===// +// +// 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 Testing + +@Suite +struct JNIAsyncTests { + + @Test("Import: async -> Void (Java, CompletableFuture)") + func completableFuture_asyncVoid_java() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func asyncVoid() async + * } + */ + public static java.util.concurrent.CompletableFuture asyncVoid() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$asyncVoid($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $asyncVoid(java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: async -> Void (Swift, CompletableFuture)") + func completableFuture_asyncVoid_swift() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + } + #endif + if task == nil { + task = Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + } + } + """ + ] + ) + } + + @Test("Import: async throws -> Void (Java, CompletableFuture)") + func completableFuture_asyncThrowsVoid_java() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async() async throws + * } + */ + public static java.util.concurrent.CompletableFuture async() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: async throws -> Void (Swift, CompletableFuture)") + func completableFuture_asyncThrowsVoid_swift() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } + } + } + #endif + if task == nil { + task = Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } + } + } + } + """ + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Java, CompletableFuture)") + func completableFuture_asyncIntToInt_java() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(i: Int64) async -> Int64 + * } + */ + public static java.util.concurrent.CompletableFuture async(long i) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(i, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(long i, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Swift, CompletableFuture)") + func completableFuture_asyncIntToInt_swift() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try! JavaVirtualMachine.shared().environment() + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) + } + } + #endif // end of swift(>=6.2) + if task == nil { + task = Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try! JavaVirtualMachine.shared().environment() + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) + } + } + return + } + """ + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Java, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_java() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(c: MyClass) async -> MyClass + * } + */ + public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(c.$memoryAddress(), $future); + return $future.thenApply((futureResult$) -> { + return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena$); + } + ); + } + """, + """ + private static native void $async(long c, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Swift, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_swift() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong, result_future: jobject?) { + assert(c != 0, "c memory address was null") + let cBits$ = Int(Int64(fromJNI: c, in: environment)) + let c$ = UnsafeMutablePointer(bitPattern: cBits$) + guard let c$ else { + fatalError("c memory address was null in call to \\(#function)!") + } + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try! JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) + } + } + #endif + if task == nil { + task = Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try! JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) + } + } + return + } + """ + ] + ) + } + + @Test("Import: (String) async -> String (Java, CompletableFuture)") + func completableFuture_asyncStringToString_java() throws { + try assertOutput( + input: """ + public func async(s: String) async -> String + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static java.util.concurrent.CompletableFuture async(java.lang.String s) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(s, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(java.lang.String s, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (String) async -> String (Swift, CompletableFuture)") + func completableFuture_asyncStringToString_swift() throws { + try assertOutput( + input: """ + public func async(s: String) async -> String + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, s: jstring?, result_future: jobject?) { + let s = environment.interface.NewGlobalRef(environment, s) + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + ... + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, s) + } + ... + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))]) + ... + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index a7527aa8..c3102a62 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -221,9 +221,9 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024init__JJ") func Java_com_example_swift_MyClass__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @@ -232,7 +232,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyClass.init()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, ] @@ -303,12 +303,12 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] @@ -352,7 +352,7 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024copy__J") func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") @@ -360,7 +360,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: self$.pointee.copy()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -404,18 +404,18 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024isEqual__JJ") func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") - let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) + let otherBits$ = Int(Int64(fromJNI: other, in: environment)) let other$ = UnsafeMutablePointer(bitPattern: otherBits$) guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) + return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index b374d24e..b54749c1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -63,10 +63,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "()V")! let arguments$: [jvalue] = [] - environment!.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) } ) } @@ -115,10 +115,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! - let arguments$: [jvalue] = [_0.getJValue(in: environment!), _1.getJValue(in: environment!)] - return Int64(fromJNI: environment!.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment!) + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! + let arguments$: [jvalue] = [_0.getJValue(in: environment), _1.getJValue(in: environment)] + return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index c6aaf923..38ef8789 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -224,25 +224,25 @@ struct JNIEnumTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyEnum.first) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2") func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jstring?) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment!))) + result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment!), y: Int32(fromJNI: y, in: environment!))) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ]) @@ -302,9 +302,8 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") let constructorID$ = cache$[method$] - return withVaList([_0.getJNIValue(in: environment!) ?? 0]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [jvalue(l: _0.getJNIValue(in: environment) ?? nil)] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """, """ @@ -318,9 +317,8 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") let constructorID$ = cache$[method$] - return withVaList([x.getJNIValue(in: environment!), y.getJNIValue(in: environment!)]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNIValue(in: environment)), jvalue(i: y.getJNIValue(in: environment))] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """ ]) diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index ac6b8384..69d77b73 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -71,7 +71,7 @@ struct JNIJavaKitTests { guard let javaInteger_unwrapped$ = javaInteger else { fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") } - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment), int: Int64(fromJNI: int, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 27b0cdea..dddf1147 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -130,13 +130,13 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ") func Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment), i2: Int16(fromJNI: i2, in: environment), i3: Int32(fromJNI: i3, in: environment), i4: Int64(fromJNI: i4, in: environment)).getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD") func Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { - SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) + SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment), f: Float(fromJNI: f, in: environment), d: Double(fromJNI: d, in: environment)) } """ ] @@ -179,7 +179,7 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { - return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.copy(String(fromJNI: string, in: environment)).getJNIValue(in: environment) } """, ] @@ -247,7 +247,7 @@ struct JNIModuleTests { @_cdecl("Java_com_example_swift_SwiftModule__00024methodB__") func Java_com_example_swift_SwiftModule__00024methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { - return try SwiftModule.methodB().getJNIValue(in: environment!) + return try SwiftModule.methodB().getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift new file mode 100644 index 00000000..67d966e3 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// 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 Testing + +@Suite +struct JNINestedTypesTests { + let source1 = """ + public class A { + public class B { + public func g(c: C) {} + + public struct C { + public func h(b: B) {} + } + } + } + + public func f(a: A, b: A.B, c: A.B.C) {} + """ + + @Test("Import: class and struct A.B.C (Java)") + func nestedClassesAndStructs_java() throws { + try assertOutput( + input: source1, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class A implements JNISwiftInstance { + ... + public static final class B implements JNISwiftInstance { + ... + public static final class C implements JNISwiftInstance { + ... + public void h(A.B b) { + ... + } + ... + public void g(A.B.C c) { + ... + } + ... + } + """, + """ + public static void f(A a, A.B b, A.B.C c) { + ... + } + ... + """ + ] + ) + } + + @Test("Import: class and struct A.B.C (Swift)") + func nestedClassesAndStructs_swift() throws { + try assertOutput( + input: source1, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_A__00024destroy__J") + func Java_com_example_swift_A__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B__00024destroy__J") + func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B__00024destroy__J") + func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B_00024C__00024h__JJ") + func Java_com_example_swift_A_00024B_00024C__00024h__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, b: jlong, self: jlong) { + ... + } + """ + ] + ) + } + + @Test("Import: nested in enum") + func nestedEnums_java() throws { + try assertOutput( + input: """ + public enum MyError { + case text(TextMessage) + + public struct TextMessage {} + } + + public func f(text: MyError.TextMessage) {} + """, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class MyError implements JNISwiftInstance { + ... + public static final class TextMessage implements JNISwiftInstance { + ... + } + ... + public static MyError text(MyError.TextMessage arg0, SwiftArena swiftArena$) { + ... + } + """, + """ + public static void f(MyError.TextMessage text) { + ... + } + """ + ] + ) + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index be2e0f6a..6c931d7b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -72,10 +72,10 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { - let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment) : nil).map { Int64($0) << 32 | Int64(1) } ?? 0 - return result_value$.getJNIValue(in: environment!) + return result_value$.getJNIValue(in: environment) } """ ] @@ -123,8 +123,8 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { let result$: jstring? - if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { - result$ = innerResult$.getJNIValue(in: environment!) + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment) : nil) { + result$ = innerResult$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -180,14 +180,14 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { - let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let argBits$ = Int(Int64(fromJNI: arg, in: environment)) let arg$ = UnsafeMutablePointer(bitPattern: argBits$) let result$: jlong if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { let _result$ = UnsafeMutablePointer.allocate(capacity: 1) _result$.initialize(to: innerResult$) let _resultBits$ = Int64(Int(bitPattern: _result$)) - result$ = _resultBits$.getJNIValue(in: environment!) + result$ = _resultBits$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -242,7 +242,7 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { - return JavaLong(javaThis: $0, environment: environment!) + return JavaLong(javaThis: $0, environment: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index b5a0fcdb..c5302ca6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -107,11 +107,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -122,11 +122,11 @@ struct JNIProtocolTests { } let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) #endif - guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment!))) else { + guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment))) else { fatalError("y_typeMetadataAddress memory address was null") } let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) - guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment!))) else { + guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment))) else { fatalError("y memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -172,11 +172,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { - guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment!))) else { + guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment))) else { fatalError("s_typeMetadataAddress memory address was null") } let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) - guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment!))) else { + guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment))) else { fatalError("s memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -222,11 +222,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a7c689aa..f8830b64 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -142,9 +142,9 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024init__JJ") func Java_com_example_swift_MyStruct__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -214,12 +214,12 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index 61a94062..0833b21f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -47,7 +47,6 @@ final class JNIUnsignedNumberTests { func jni_unsignedInt_annotate() throws { var config = Configuration() config.unsignedNumbersMode = .annotate - config.logLevel = .trace try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index c9d313a5..363c117d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -70,7 +70,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.constant.getJNIValue(in: environment!) + return self$.pointee.constant.getJNIValue(in: environment) } """ ] @@ -135,7 +135,7 @@ struct JNIVariablesTests { fatalError("self memory address was null in call to \\(#function)!") } ... - return self$.pointee.mutable.getJNIValue(in: environment!) + return self$.pointee.mutable.getJNIValue(in: environment) } """, """ @@ -143,7 +143,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") ... - self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment) } """ ] @@ -188,7 +188,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.computed.getJNIValue(in: environment!) + return self$.pointee.computed.getJNIValue(in: environment) } """, ] @@ -234,7 +234,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... do { - return try self$.pointee.computedThrowing.getJNIValue(in: environment!) + return try self$.pointee.computedThrowing.getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue @@ -297,14 +297,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.getterAndSetter.getJNIValue(in: environment!) + return self$.pointee.getterAndSetter.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { ... - self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment) } """ ] @@ -363,14 +363,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.someBoolean.getJNIValue(in: environment!) + return self$.pointee.someBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment) } """ ] @@ -429,14 +429,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.isBoolean.getJNIValue(in: environment!) + return self$.pointee.isBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment) } """ ] diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 938b5e7f..6ba93016 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -70,7 +70,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, @@ -110,7 +110,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalTakeInt" @@ -152,7 +152,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalTakeIntLongString" @@ -196,7 +196,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalReturnClass" @@ -240,7 +240,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "swapRawBufferPointer" @@ -287,7 +287,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.name == "helloMemberFunction" @@ -330,7 +330,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.name == "makeInt" @@ -373,7 +373,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first { $0.name == "init" @@ -418,7 +418,7 @@ final class MethodImportTests { st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first { $0.name == "init" diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index fdbf2d5f..b437454c 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -34,7 +34,10 @@ struct SwiftSymbolTableSuite { """ let symbolTable = SwiftSymbolTable.setup( moduleName: "MyModule", - [sourceFile1, sourceFile2], + [ + .init(syntax: sourceFile1, path: "Fake.swift"), + .init(syntax: sourceFile2, path: "Fake2.swift") + ], log: Logger(label: "swift-java", logLevel: .critical) ) diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift new file mode 100644 index 00000000..68702b62 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -0,0 +1,174 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import JavaNet +import SwiftJavaShared +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import Foundation + +fileprivate func createTemporaryDirectory(in directory: Foundation.URL) throws -> Foundation.URL { + let uuid = UUID().uuidString + let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") + + try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + return resolverDirectoryURL +} + +/// Returns the directory that should be added to the classpath of the JVM to analyze the sources. +func compileJava(_ sourceText: String) async throws -> Foundation.URL { + let sourceFile = try TempFile.create(suffix: "java", sourceText) + + let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) + + let javacProcess = try await _Subprocess.run( + .path(.init("\(javaHome)" + "/bin/javac")), + arguments: [ + "-d", classesDirectory.path, // output directory for .class files + sourceFile.path + ], + output: .string(limit: Int.max, encoding: UTF8.self), + error: .string(limit: Int.max, encoding: UTF8.self) + ) + + // Check if compilation was successful + guard javacProcess.terminationStatus.isSuccess else { + let outString = javacProcess.standardOutput ?? "" + let errString = javacProcess.standardError ?? "" + fatalError("javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\n" + + "OUT: \(outString)\n" + + "ERROR: \(errString)") + } + + print("Compiled java sources to: \(classesDirectory)") + return classesDirectory +} + +func withJavaTranslator( + javaClassNames: [String], + classpath: [Foundation.URL], + body: (JavaTranslator) throws -> (), + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + print("New withJavaTranslator, for classpath: \(classpath)") + let jvm = try JavaVirtualMachine.shared( + classpath: classpath.map(\.path), + replace: true + ) + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + try body(translator) +} + +/// Translate a Java class and assert that the translated output contains +/// each of the expected "chunks" of text. +func assertWrapJavaOutput( + javaClassNames: [String], + classpath: [Foundation.URL], + assert assertBody: (JavaTranslator) throws -> Void = { _ in }, + expectedChunks: [String], + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + let jvm = try JavaVirtualMachine.shared( + //classpath: classpath.map(\.path), + replace: false + ) + // Do NOT destroy the jvm here, because the JavaClasses will need to deinit, + // and do so while the env is still valid... + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + let classpathJavaURLs = classpath.map({ try! URL.init("\($0)/") }) // we MUST have a trailing slash for JVM to consider it a search directory + let classLoader = URLClassLoader(classpathJavaURLs, environment: environment) + + // FIXME: deduplicate this + translator.startNewFile() + + var swiftCompleteOutputText = "" + + var javaClasses: [JavaClass] = [] + for javaClassName in javaClassNames { + guard let javaClass = try! classLoader.loadClass(javaClassName) else { + fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") + } + javaClasses.append(javaClass) + + // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!! + // TODO: especially because nested classes + // WrapJavaCommand(). + + let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName + .defaultSwiftNameForJavaClass + translator.translatedClasses[javaClassName] = + .init(module: nil, name: swiftUnqualifiedName) + + try translator.validateClassConfiguration() + + let swiftClassDecls = try translator.translateClass(javaClass) + let importDecls = translator.getImportDecls() + + let swiftFileText = + """ + // --------------------------------------------------------------------------- + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + \n + """ + swiftCompleteOutputText += swiftFileText + } + + // Run any additional user defined assertions: + try assertBody(translator) + + for expectedChunk in expectedChunks { + // We make the matching in-sensitive to whitespace: + let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "") + let checkAgainstExpectedChunk = expectedChunk.replacing(" ", with: "") + +let failureMessage = "Expected chunk: \n" + + "\(expectedChunk.yellow)" + + "\n" + + "not found in:\n" + + "\(swiftCompleteOutputText)" + XCTAssertTrue(checkAgainstText.contains(checkAgainstExpectedChunk), + "\(failureMessage)") + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index f251766a..e302fdc5 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -14,6 +14,7 @@ @_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared import SwiftJavaToolLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -48,12 +49,12 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObject { """, """ - @JavaMethod - public func toString() -> String + @JavaMethod + public func toString() -> String """, """ - @JavaMethod - public func wait() throws + @JavaMethod + public func wait() throws """ ] ) @@ -64,7 +65,7 @@ class Java2SwiftTests: XCTestCase { JavaClass.self, swiftTypeName: "MyJavaClass", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -73,8 +74,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaClass { """, """ - @JavaStaticMethod - public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass + @JavaStaticMethod + public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass """, ] ) @@ -100,12 +101,12 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self = APRIL } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -115,19 +116,19 @@ class Java2SwiftTests: XCTestCase { MyArrayList.self, swiftTypeName: "JavaArrayList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.lang.reflect.Array": ("JavaArray", nil), - "java.util.List": ("JavaList", nil), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.lang.reflect.Array": SwiftTypeName(module: nil, name: "JavaArray"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """, """ - @JavaMethod - public func toArray(_ arg0: MyJavaIntFunction?) -> [JavaObject?] + @JavaMethod + public func toArray(_ arg0: MyJavaIntFunction?) -> [T?] """ ] ) @@ -138,13 +139,13 @@ class Java2SwiftTests: XCTestCase { MyLinkedList.self, swiftTypeName: "JavaLinkedList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.util.List": ("JavaList", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """ ] ) @@ -155,9 +156,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.Redirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.Redirect.Type", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect.Type"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -166,8 +167,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.Redirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.Redirect! """, """ extension ProcessBuilder { @@ -183,8 +184,8 @@ class Java2SwiftTests: XCTestCase { public struct Type { """, """ - @JavaMethod - public func type() -> ProcessBuilder.Redirect.`Type`! + @JavaMethod + public func type() -> ProcessBuilder.Redirect.`Type`! """, ] ) @@ -195,9 +196,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.PBRedirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.PBRedirect.JavaType", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect.JavaType"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -206,8 +207,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.PBRedirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.PBRedirect! """, """ extension ProcessBuilder { @@ -223,8 +224,8 @@ class Java2SwiftTests: XCTestCase { public struct JavaType { """, """ - @JavaMethod - public func type() -> ProcessBuilder.PBRedirect.JavaType! + @JavaMethod + public func type() -> ProcessBuilder.PBRedirect.JavaType! """ ] ) @@ -248,9 +249,9 @@ class Java2SwiftTests: XCTestCase { MyObjects.self, swiftTypeName: "MyJavaObjects", translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.Supplier" : ("MySupplier", "JavaUtilFunction"), - "java.lang.String" : ("JavaString", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.Supplier" : SwiftTypeName(module: "JavaUtilFunction", name: "MySupplier"), + "java.lang.String" : SwiftTypeName(module: "SwiftJava", name: "JavaString"), ], expectedChunks: [ """ @@ -261,8 +262,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObjects { """, """ - @JavaStaticMethod - public func requireNonNull(_ arg0: JavaObject?, _ arg1: MySupplier?) -> JavaObject! + @JavaStaticMethod + public func requireNonNull(_ arg0: T?, _ arg1: MySupplier?) -> T """, ] ) @@ -280,20 +281,20 @@ class Java2SwiftTests: XCTestCase { open class JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open func toString() -> String + @JavaMethod + open func toString() -> String """, """ - @JavaMethod - open func wait() throws + @JavaMethod + open func wait() throws """, """ - @JavaMethod - open func clone() throws -> JavaObject! + @JavaMethod + open func clone() throws -> JavaObject! """, ] ) @@ -305,7 +306,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaString", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -314,24 +315,24 @@ class Java2SwiftTests: XCTestCase { open class JavaString: JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open override func toString() -> String + @JavaMethod + open override func toString() -> String """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, """ - @JavaMethod - open func intern() -> String + @JavaMethod + open func intern() -> String """, """ - @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> String + @JavaStaticMethod + public func valueOf(_ arg0: Int64) -> String """, ] ) @@ -361,12 +362,12 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self.init(javaHolder: APRIL.javaHolder) } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -380,9 +381,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.ClassLoader" : ("ClassLoader", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.ClassLoader" : SwiftTypeName(module: "SwiftJava", name: "ClassLoader"), + "java.net.URL" : SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -391,12 +392,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: ClassLoader { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open override func findResource(_ arg0: String) -> URL! + @JavaMethod + open override func findResource(_ arg0: String) -> URL! """, ] ) @@ -411,8 +412,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.net.URL": SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -421,12 +422,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: JavaObject { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open func findResource(_ arg0: String) -> URL! + @JavaMethod + open func findResource(_ arg0: String) -> URL! """, ] ) @@ -440,9 +441,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaByte", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Number" : ("JavaNumber", "SwiftJava"), - "java.lang.Byte" : ("JavaByte", "SwiftJava"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Number": SwiftTypeName(module: "SwiftJava", name: "JavaNumber"), + "java.lang.Byte": SwiftTypeName(module: "SwiftJava", name: "JavaByte"), ], expectedChunks: [ "import SwiftJava", @@ -451,8 +452,8 @@ class Java2SwiftTests: XCTestCase { open class JavaByte: JavaNumber { """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, ] ) @@ -464,8 +465,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "MyJavaIntFunction", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ "import SwiftJava", @@ -474,8 +475,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaIntFunction { """, """ - @JavaMethod - public func apply(_ arg0: Int32) -> JavaObject! + @JavaMethod + public func apply(_ arg0: Int32) -> R! """, ] ) @@ -487,11 +488,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Method", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -500,16 +501,16 @@ class Java2SwiftTests: XCTestCase { open class Method: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -521,11 +522,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Constructor", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -534,16 +535,16 @@ class Java2SwiftTests: XCTestCase { open class Constructor: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable>?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable>?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -555,10 +556,10 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "NIOByteBuffer", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.nio.Buffer": ("NIOBuffer", "JavaNio"), - "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaNio"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class": SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.nio.Buffer": SwiftTypeName(module: "JavaNio", name: "NIOBuffer"), + "java.nio.ByteBuffer": SwiftTypeName(module: "JavaNio", name: "NIOByteBuffer"), ], expectedChunks: [ "import JavaNio", @@ -580,61 +581,58 @@ class Java2SwiftTests: XCTestCase { } @JavaClass("java.lang.ClassLoader") -public struct ClassLoader { } +fileprivate struct ClassLoader { } @JavaClass("java.security.SecureClassLoader") -public struct SecureClassLoader { } +fileprivate struct SecureClassLoader { } @JavaClass("java.net.URLClassLoader") -public struct URLClassLoader { } - +fileprivate struct URLClassLoader { } @JavaClass("java.util.ArrayList") -public struct MyArrayList { +fileprivate struct MyArrayList { } @JavaClass("java.util.LinkedList") -public struct MyLinkedList { +fileprivate struct MyLinkedList { } @JavaClass("java.lang.String") -public struct MyJavaString { +fileprivate struct MyJavaString { } @JavaClass("java.util.Objects") -public struct MyObjects { } +fileprivate struct MyObjects { } @JavaInterface("java.util.function.Supplier") -public struct MySupplier { } +fileprivate struct MySupplier { } @JavaInterface("java.util.function.IntFunction") -public struct MyJavaIntFunction { +fileprivate struct MyJavaIntFunction { } @JavaClass("java.lang.reflect.Method", extends: Executable.self) -public struct Method { +fileprivate struct Method { } @JavaClass("java.lang.reflect.Constructor", extends: Executable.self) -public struct Constructor { +fileprivate struct Constructor { } @JavaClass("java.lang.reflect.Executable") -public struct Executable { +fileprivate struct Executable { } @JavaInterface("java.lang.reflect.TypeVariable") -public struct TypeVariable { +fileprivate struct TypeVariable { } @JavaClass("java.nio.Buffer") -open class NIOBuffer: JavaObject { - +fileprivate class NIOBuffer: JavaObject { } @JavaClass("java.nio.ByteBuffer") -open class NIOByteBuffer: NIOBuffer { - +fileprivate class NIOByteBuffer: NIOBuffer { } /// Translate a Java class and assert that the translated output contains @@ -643,9 +641,8 @@ func assertTranslatedClass( _ javaType: JavaClassType.Type, swiftTypeName: String, asClass: Bool = false, - translatedClasses: [ - String: (swiftType: String, swiftModule: String?) - ] = [:], + config: Configuration = Configuration(), + translatedClasses: [String: SwiftTypeName] = [:], nestedClasses: [String: [JavaClass]] = [:], expectedChunks: [String], file: StaticString = #filePath, @@ -653,13 +650,14 @@ func assertTranslatedClass( ) throws { let environment = try jvm.environment() let translator = JavaTranslator( + config: config, swiftModuleName: "SwiftModule", environment: environment, translateAsClass: asClass ) translator.translatedClasses = translatedClasses - translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil) + translator.translatedClasses[javaType.fullJavaClassName] = SwiftTypeName(module: nil, name: swiftTypeName) translator.nestedClasses = nestedClasses translator.startNewFile() @@ -677,12 +675,22 @@ func assertTranslatedClass( \(translatedDecls.map { $0.description }.joined(separator: "\n")) """ + func normalizeWhitespace(_ text: String) -> String { + return text.components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespaces) } + .joined(separator: "\n") + } + + let normalizedSwiftFileText = normalizeWhitespace(swiftFileText) + for expectedChunk in expectedChunks { - if swiftFileText.contains(expectedChunk) { + let normalizedExpectedChunk = normalizeWhitespace(expectedChunk) + + if normalizedSwiftFileText.contains(normalizedExpectedChunk) { continue } - XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line) + XCTFail("Expected chunk:\n---\n\(expectedChunk.yellow)\n---\nnot found in:\n===\n\(swiftFileText)\n===", file: file, line: line) } } } diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift new file mode 100644 index 00000000..9983813d --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaUtilJar +@_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared +import SwiftJavaShared +import SwiftJavaToolLib +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import _Subprocess + +class JavaTranslatorTests: XCTestCase { + + func translateGenericMethodParameters() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair( + String name, + Item key, + Item value + ) { return null; } + } + """) + + try withJavaTranslator( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass", + ], + classpath: [classpathURL], + ) { translator in + + } + } +} diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index 220f1c61..558add20 100644 --- a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -14,15 +14,16 @@ import SwiftJavaToolLib import XCTest +import SwiftJavaConfigurationShared final class JavaTranslatorValidationTests: XCTestCase { func testValidationError() throws { - let translator = try JavaTranslator(swiftModuleName: "SwiftModule", environment: jvm.environment()) + let translator = try JavaTranslator(config: Configuration(), swiftModuleName: "SwiftModule", environment: jvm.environment()) translator.translatedClasses = [ - "TestClass": ("Class1", "Module1"), - "TestClass2": ("Class1", "Module2"), - "TestClass3": ("Class1", "Module1"), - "TestClass4": ("Class1", nil) + "TestClass": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass2": SwiftTypeName(module: "Module2", name: "Class1"), + "TestClass3": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass4": SwiftTypeName(module: nil, name: "Class1") ] XCTAssertThrowsError(try translator.validateClassConfiguration()) { error in @@ -31,7 +32,7 @@ final class JavaTranslatorValidationTests: XCTestCase { switch validationError { case .multipleClassesMappedToSameName(let swiftToJavaMapping): XCTAssertEqual(swiftToJavaMapping, [ - JavaTranslator.SwiftToJavaMapping(swiftType: .init(swiftType: "Class1", swiftModule: "Module1"), + JavaTranslator.SwiftToJavaMapping(swiftType: .init(module: "Module1", name: "Class1"), javaTypes: ["TestClass", "TestClass3"]) ]) } diff --git a/Tests/SwiftJavaToolLibTests/TempFileTools.swift b/Tests/SwiftJavaToolLibTests/TempFileTools.swift new file mode 100644 index 00000000..5b133108 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/TempFileTools.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-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 Foundation + +/// Example demonstrating how to create a temporary file using Swift Foundation APIs +public class TempFile { + + public static func create( + suffix: String, + _ contents: String = "", + in tempDirectory: URL = FileManager.default.temporaryDirectory) throws -> URL { + let tempFileName = "tmp_\(UUID().uuidString).\(suffix)" + let tempFileURL = tempDirectory.appendingPathComponent(tempFileName) + + try contents.write(to: tempFileURL, atomically: true, encoding: .utf8) + + return tempFileURL + } + public static func delete(at fileURL: URL) throws { + try FileManager.default.removeItem(at: fileURL) + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift new file mode 100644 index 00000000..1c3d10d2 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -0,0 +1,273 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import SwiftJavaShared +import JavaNet +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 + +final class WrapJavaTests: XCTestCase { + + func testWrapJavaFromCompiledJavaSource() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class ExampleSimpleClass {} + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """ + ] + ) + } + + // @Test + func testWrapJavaGenericMethod_singleGeneric() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + KeyType getGeneric(Item key) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getGeneric(_ arg0: Item?) -> KeyType + """, + ] + ) + } + + // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params + func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + final class ExampleSimpleClass { + // use in return type + KeyType getGeneric() { + return null; + } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getGeneric() -> KeyType + """, + ] + ) + } + + func testWrapJavaGenericMethod_multipleGenerics() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair(String name, Item key, Item value) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Item") + open class Item: JavaObject { + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getPair(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> Pair! + """, + ] + ) + } + + func test_Java2Swift_returnType_generic() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class List {} + final class Map {} + + class GenericClass { + public T getClassGeneric() { return null; } + + public M getMethodGeneric() { return null; } + + public Map getMixedGeneric() { return null; } + + public String getNonGeneric() { return null; } + + public List getParameterizedClassGeneric() { return null; } + + public List getWildcard() { return null; } + + public T[] getGenericArray() { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.GenericClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getClassGeneric() -> T + """, + """ + @JavaMethod + open func getNonGeneric() -> String + """, + ] + ) + } + + func testGenericSuperclass() async throws { + return // FIXME: we need this + + let classpathURL = try await compileJava( + """ + package com.example; + + class ByteArray {} + class CompressingStore extends AbstractStore {} + abstract class AbstractStore {} // implements Store {} + // interface Store {} + + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ByteArray", + // TODO: what if we visit in other order, does the wrap-java handle it + // "com.example.Store", + "com.example.AbstractStore", + "com.example.CompressingStore", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ByteArray") + open class ByteArray: JavaObject { + """, + // """ + // @JavaInterface("com.example.Store") + // public struct Store { + // """, + """ + @JavaClass("com.example.CompressingStore") + open class CompressingStore: AbstractStore { + """ + ] + ) + } +} diff --git a/WIP.md b/WIP.md deleted file mode 100644 index 88671233..00000000 --- a/WIP.md +++ /dev/null @@ -1,25 +0,0 @@ -## Work In Progress - -This package is a work in progress, and many details are subject to change. - -Here is a long yet still very incomplete list of things we would like to do or -improve: - -- Expressivity gaps: - - [ ] Automatically turn get/set method pairs into Swift properties? - - [ ] Implement a global registry that lets us find the Swift type corresponding to a canonical Java class name (e.g., `java.net.URL` -> `JavaKitNetwork.URL`) - - [ ] Introduce overloads of `is` and `as` on the Swift projections so that conversion to any implemented interface or extended superclass returns non-optional. - - [ ] Figure out how to express the equivalent of `super.foo()` that calls the superclass's method from the subclass method. - - [ ] Recognize Java's enum classes and map them into Swift well - - [ ] Translate Java constants into Swift constants - - [ ] Support nested classes - - [ ] Figure out how to subclass a Java class from Swift -- Tooling - - [ ] Generate Swift projections for more common Java types into JavaKit libraries to make it easier to get started - - [ ] Teach `Java2Swift` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` -- Performance: - - [ ] Cache method/field IDs when we can - - [ ] Investigate noncopyable types to remove excess copies - - [ ] Investigate "unbridged" variants of String, Array, etc. - - [ ] Investigate the new [Foreign Function & Memory API](https://bugs.openjdk.org/browse/JDK-8312523) (aka Project Panama) for exposing Swift APIs to Java. - diff --git a/docker/Dockerfile b/docker/Dockerfile index c3568b54..ef428b87 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -20,11 +20,11 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency RUN curl -s "https://get.sdkman.io" | bash -RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 24.0.1-amzn" +RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 25.0.1-amzn" +ENV JAVA_HOME="$(sdk home java current)" RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ tar zxf swiftly-$(uname -m).tar.gz && \ ./swiftly init --quiet-shell-followup --assume-yes && \ . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ hash -r - diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8746e3ab..b69545f9 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -14,7 +14,7 @@ ##===----------------------------------------------------------------------===## set -euo pipefail -# We need JDK 24 because that's the supported version with latest FFM +# We need JDK 25 because that's the supported version with latest FFM # However, we also need JDK 23 at most because Gradle does not support 24. # Supported JDKs: corretto @@ -36,9 +36,9 @@ download_and_install_jdk() { if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in - "24") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" - expected_md5="3b543f4e971350b73d0ab6d8174cc030" + "25") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-25-aarch64-linux-jdk.tar.gz" + expected_md5="37588d5d2a24b26525b9c563ad65cc77" ;; *) echo "Unsupported JDK version: '$jdk_version'" @@ -47,9 +47,9 @@ download_and_install_jdk() { esac else case "$jdk_version" in - "24") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" - expected_md5="130885ded3cbfc712fbe9f7dace45a52" + "25") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-25-x64-linux-jdk.tar.gz" + expected_md5="7e56b1a9d71637ce4dc4047b23d0453e" ;; *) echo "Unsupported JDK version: '$jdk_version'" @@ -94,12 +94,12 @@ download_and_install_jdk() { cd "$HOME" } -# Usage: Install JDK 24 -download_and_install_jdk "24" +# Usage: Install JDK 25 +download_and_install_jdk "25" ls -la /usr/lib/jvm/ cd /usr/lib/jvm/ -ln -s jdk-24 default-jdk +ln -s jdk-25 default-jdk find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 679a1a1f..182db452 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64 \ No newline at end of file +org.gradle.java.installations.fromEnv=JAVA_HOME_25,JAVA_HOME_25_X64,JAVA_HOME_25_ARM64 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68d..2e111328 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME