diff --git a/Samples/JavaSieve/.gitignore b/Samples/JavaSieve/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/Samples/JavaSieve/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift new file mode 100644 index 00000000..2420dcf1 --- /dev/null +++ b/Samples/JavaSieve/Package.swift @@ -0,0 +1,83 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +import class Foundation.FileManager +import class Foundation.ProcessInfo + +// 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. +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 + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + 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.") +#endif + +let package = Package( + name: "JavaSieve", + platforms: [ + .macOS(.v10_15), + ], + dependencies: [ + .package(name: "swift-java", path: "../../"), + ], + targets: [ + .target( + name: "JavaMath", + dependencies: [ + .product(name: "JavaKit", package: "swift-java"), + .product(name: "JavaKitJar", package: "swift-java"), + ], + swiftSettings: [ + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ], + plugins: [ + .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + ] + ), + + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "JavaSieve", + dependencies: [ + "JavaMath", + .product(name: "JavaKit", package: "swift-java"), + .product(name: "JavaKitCollection", package: "swift-java"), + .product(name: "JavaKitVM", package: "swift-java"), + ], + swiftSettings: [ + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ], + plugins: [ + .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + ] + ), + ] +) diff --git a/Samples/JavaSieve/README.md b/Samples/JavaSieve/README.md new file mode 100644 index 00000000..b8dc3c9a --- /dev/null +++ b/Samples/JavaSieve/README.md @@ -0,0 +1,32 @@ +# JavaKit Example: Using a Java library from Swift + +This package contains an example program that demonstrates importing a Java library distributed as a Jar file into Swift and using some APIs from that library. It demonstrates how to: + +* Use the Java2Swift tool to discover the classes in a Jar file and make them available in Swift +* Layer Swift wrappers for Java classes as separate Swift modules using Java2Swift +* Access static methods of Java classes from Swift + +This example wraps an [open-source Java library](https://github.com/gazman-sdk/quadratic-sieve-Java) implementing the [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) algorithm for finding prime numbers, among other algorithms. To get started, clone that repository and build a Jar file containing the library: + +``` +git clone https://github.com/gazman-sdk/quadratic-sieve-Java +cd quadratic-sieve-Java +sh ./gradlew jar +``` + +Then, copy the resulting Jar file (`./build/libs/QuadraticSieve-1.0.jar`) into the `Samples/JavaSieve` directory. + +Now we're ready to build and run the Swift program from `Samples/JavaSieve`: + +``` +swift run JavaSieve +``` + +The core of the example code is in `Sources/JavaSieve/main.swift`, using the static Java method `SieveOfEratosthenes.findPrimes`: + +```swift +let sieveClass = try JavaClass(in: jvm.environment()) +for prime in sieveClass.findPrimes(100)! { + print("Found prime: \(prime.intValue())") +} +``` diff --git a/Samples/JavaSieve/Sources/JavaMath/Java2Swift.config b/Samples/JavaSieve/Sources/JavaMath/Java2Swift.config new file mode 100644 index 00000000..28e56a35 --- /dev/null +++ b/Samples/JavaSieve/Sources/JavaMath/Java2Swift.config @@ -0,0 +1,9 @@ +{ + "classes" : { + "java.math.BigDecimal" : "BigDecimal", + "java.math.BigInteger" : "BigInteger", + "java.math.MathContext" : "MathContext", + "java.math.RoundingMode" : "RoundingMode", + "java.lang.Integer" : "JavaInteger", + } +} diff --git a/Samples/JavaSieve/Sources/JavaMath/dummy.swift b/Samples/JavaSieve/Sources/JavaMath/dummy.swift new file mode 100644 index 00000000..76f848f9 --- /dev/null +++ b/Samples/JavaSieve/Sources/JavaMath/dummy.swift @@ -0,0 +1,13 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// diff --git a/Samples/JavaSieve/Sources/JavaSieve/Java2Swift.config b/Samples/JavaSieve/Sources/JavaSieve/Java2Swift.config new file mode 100644 index 00000000..aa88865f --- /dev/null +++ b/Samples/JavaSieve/Sources/JavaSieve/Java2Swift.config @@ -0,0 +1,30 @@ +{ + "classPath" : "QuadraticSieve-1.0.jar", + "classes" : { + "com.gazman.quadratic_sieve.QuadraticSieve" : "QuadraticSieve", + "com.gazman.quadratic_sieve.core.BaseFact" : "BaseFact", + "com.gazman.quadratic_sieve.core.matrix.GaussianEliminationMatrix" : "GaussianEliminationMatrix", + "com.gazman.quadratic_sieve.core.matrix.Matrix" : "Matrix", + "com.gazman.quadratic_sieve.core.poly.PolyMiner" : "PolyMiner", + "com.gazman.quadratic_sieve.core.poly.WheelPool" : "WheelPool", + "com.gazman.quadratic_sieve.core.siever.BSmoothData" : "BSmoothData", + "com.gazman.quadratic_sieve.core.siever.BSmoothDataPool" : "BSmoothDataPool", + "com.gazman.quadratic_sieve.core.siever.Siever" : "Siever", + "com.gazman.quadratic_sieve.core.siever.VectorExtractor" : "VectorExtractor", + "com.gazman.quadratic_sieve.data.BSmooth" : "BSmooth", + "com.gazman.quadratic_sieve.data.DataQueue" : "DataQueue", + "com.gazman.quadratic_sieve.data.MagicNumbers" : "MagicNumbers", + "com.gazman.quadratic_sieve.data.PolynomialData" : "PolynomialData", + "com.gazman.quadratic_sieve.data.PrimeBase" : "PrimeBase", + "com.gazman.quadratic_sieve.data.VectorData" : "VectorData", + "com.gazman.quadratic_sieve.data.VectorWorkData" : "VectorWorkData", + "com.gazman.quadratic_sieve.debug.Analytics" : "Analytics", + "com.gazman.quadratic_sieve.debug.AssertUtils" : "AssertUtils", + "com.gazman.quadratic_sieve.debug.Logger" : "Logger", + "com.gazman.quadratic_sieve.fact.TrivialDivision" : "TrivialDivision", + "com.gazman.quadratic_sieve.primes.BigPrimes" : "BigPrimes", + "com.gazman.quadratic_sieve.primes.SieveOfEratosthenes" : "SieveOfEratosthenes", + "com.gazman.quadratic_sieve.utils.MathUtils" : "MathUtils", + "com.gazman.quadratic_sieve.wheel.Wheel" : "Wheel" + } +} diff --git a/Samples/JavaSieve/Sources/JavaSieve/main.swift b/Samples/JavaSieve/Sources/JavaSieve/main.swift new file mode 100644 index 00000000..f2f24a53 --- /dev/null +++ b/Samples/JavaSieve/Sources/JavaSieve/main.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 +// +//===----------------------------------------------------------------------===// + +import JavaKit +import JavaKitVM + +let jvm = try JavaVirtualMachine.shared(classPath: ["QuadraticSieve-1.0.jar"]) +do { + let sieveClass = try JavaClass(in: jvm.environment()) + for prime in sieveClass.findPrimes(100)! { + print("Found prime: \(prime.intValue())") + } +} catch { + print("Failure: \(error)") +} diff --git a/USER_GUIDE.md b/USER_GUIDE.md index b35e0ff7..b156f3e6 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -11,7 +11,7 @@ Before using this package, set the `JAVA_HOME` environment variable to point at Existing Java libraries can be wrapped for use in Swift with the `Java2Swift` tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `Java2Swift.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: -``` +```json { "classes" : { "java.math.BigInteger" : "BigInteger" @@ -91,6 +91,132 @@ if bigInt.isProbablePrime(10) { Swift ensures that the Java garbage collector will keep the object alive until `bigInt` (and any copies of it) are been destroyed. +### Importing a Jar file into Swift + +Java libraries are often distributed as Jar files. The `Java2Swift` tool can inspect a Jar file to create a `Java2Swift.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command: + +```swift +swift run Java2Swift --module-name JavaSieve --jar QuadraticSieve-1.0.jar +``` + +The resulting configuration file will look something like this: + +```json +{ + "classPath" : "QuadraticSieve-1.0.jar", + "classes" : { + "com.gazman.quadratic_sieve.QuadraticSieve" : "QuadraticSieve", + "com.gazman.quadratic_sieve.core.BaseFact" : "BaseFact", + "com.gazman.quadratic_sieve.core.matrix.GaussianEliminationMatrix" : "GaussianEliminationMatrix", + "com.gazman.quadratic_sieve.core.matrix.Matrix" : "Matrix", + "com.gazman.quadratic_sieve.core.poly.PolyMiner" : "PolyMiner", + "com.gazman.quadratic_sieve.core.poly.WheelPool" : "WheelPool", + "com.gazman.quadratic_sieve.core.siever.BSmoothData" : "BSmoothData", + "com.gazman.quadratic_sieve.core.siever.BSmoothDataPool" : "BSmoothDataPool", + "com.gazman.quadratic_sieve.core.siever.Siever" : "Siever", + "com.gazman.quadratic_sieve.core.siever.VectorExtractor" : "VectorExtractor", + "com.gazman.quadratic_sieve.data.BSmooth" : "BSmooth", + "com.gazman.quadratic_sieve.data.DataQueue" : "DataQueue", + "com.gazman.quadratic_sieve.data.MagicNumbers" : "MagicNumbers", + "com.gazman.quadratic_sieve.data.PolynomialData" : "PolynomialData", + "com.gazman.quadratic_sieve.data.PrimeBase" : "PrimeBase", + "com.gazman.quadratic_sieve.data.VectorData" : "VectorData", + "com.gazman.quadratic_sieve.data.VectorWorkData" : "VectorWorkData", + "com.gazman.quadratic_sieve.debug.Analytics" : "Analytics", + "com.gazman.quadratic_sieve.debug.AssertUtils" : "AssertUtils", + "com.gazman.quadratic_sieve.debug.Logger" : "Logger", + "com.gazman.quadratic_sieve.fact.TrivialDivision" : "TrivialDivision", + "com.gazman.quadratic_sieve.primes.BigPrimes" : "BigPrimes", + "com.gazman.quadratic_sieve.primes.SieveOfEratosthenes" : "SieveOfEratosthenes", + "com.gazman.quadratic_sieve.utils.MathUtils" : "MathUtils", + "com.gazman.quadratic_sieve.wheel.Wheel" : "Wheel" + } +} +``` + +As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`, `JavaKitVM`) and apply the `Java2Swift` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. + +If you inspect the build output, there are a number of warnings that look like this: + +```swift +warning: Unable to translate 'com.gazman.quadratic_sieve.QuadraticSieve' method 'generateN': Java class 'java.math.BigInteger' has not been translated into Swift +``` + +These warnings mean that some of the APIs in the Java library aren't available in Swift because their prerequisite types are missing. To address these warnings and get access to these APIs from Swift, we can wrap those Java classes. Expanding on the prior `JavaProbablyPrime` example, we define a new SwiftPM target `JavaMath` for the various types in the `java.math` package: + +```swift + .target( + name: "JavaMath", + dependencies: [ + .product(name: "JavaKit", package: "swift-java"), + ], + plugins: [ + .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + ] + ), +``` + +Then define a a Java2Swift configuration file in `Sources/JavaMath/Java2Swift.config` to bring in the types we need: + +```json +{ + "classes" : { + "java.math.BigDecimal" : "BigDecimal", + "java.math.BigInteger" : "BigInteger", + "java.math.MathContext" : "MathContext", + "java.math.RoundingMode" : "RoundingMode", + "java.lang.Integer" : "JavaInteger", + } +} +``` + +Finally, make the `JavaSieve` target depend on `JavaMath` and rebuild: the warnings related to `java.math.BigInteger` and friends will go away, and Java APIs that depend on them will now be available in Swift! + +### Calling Java static methods from Swift + +There are a number of prime-generation facilities in the Java library we imported. However, we are going to focus on the simple Sieve of Eratosthenes, which is declared like this in Java: + +```java +public class SieveOfEratosthenes { + public static List findPrimes(int limit) { ... } +} +``` + +In Java, static methods are called as members of the class itself. For Swift to call a Java static method, it needs a representation of the Java class. This is expressed as an instance of the generic type `JavaClass`, which can be created in a particular JNI environment like this: + +```swift +let sieveClass = try JavaClass(in: jvm.environment()) +``` + +Now we can call Java's static methods on that class as instance methods on the `JavaClass` instance, e.g., + +```swift +let primes = sieveClass.findPrimes(100) // returns a List? +``` + +Putting it all together, we can define a main program in `Sources/JavaSieve/main.swift` that looks like this: + +```swift +import JavaKit +import JavaKitVM + +let jvm = try JavaVirtualMachine.shared(classPath: ["QuadraticSieve-1.0.jar"]) +do { + let sieveClass = try JavaClass(in: jvm.environment()) + for prime in sieveClass.findPrimes(100)! { + print("Found prime: \(prime.intValue())") + } +} catch { + print("Failure: \(error)") +} +``` + +Note that we are passing the Jar file in the `classPath` argument when initializing the `JavaVirtualMachine` instance. Otherwise, the program will fail with an error because it cannot find the Java class `com.gazman.quadratic_sieve.primes.SieveOfEratosthenes`. + +## Using Java libraries from Swift + +This section describes how Java libraries and mapped into Swift and their use from Swift. + ### Translation from Java classes into Swift Each Java class that can be used from Swift is translated to a Swift `struct` that provides information about the Java class itself and is populated with the Swift projection of each of its constructors, methods, and fields. For example, here is an excerpt of the Swift projection of [`java.util.jar.JarFile`](https://docs.oracle.com/javase/8/docs/api/java/util/jar/JarFile.html): @@ -521,6 +647,3 @@ public final class SomeModule ... { public static void globalFunction() { ... } } ``` - - -