Skip to content

Commit 8b547c3

Browse files
authored
Merge pull request #95 from DougGregor/javasieve-sample
Add JavaSieve example that shows using a Java library (as a Jar) from Swift
2 parents 207bd13 + fc423ab commit 8b547c3

File tree

8 files changed

+328
-4
lines changed

8 files changed

+328
-4
lines changed

Samples/JavaSieve/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

Samples/JavaSieve/Package.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
import class Foundation.FileManager
7+
import class Foundation.ProcessInfo
8+
9+
// Note: the JAVA_HOME environment variable must be set to point to where
10+
// Java is installed, e.g.,
11+
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
12+
func findJavaHome() -> String {
13+
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
14+
return home
15+
}
16+
17+
// This is a workaround for envs (some IDEs) which have trouble with
18+
// picking up env variables during the build process
19+
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
20+
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
21+
if let lastChar = home.last, lastChar.isNewline {
22+
return String(home.dropLast())
23+
}
24+
25+
return home
26+
}
27+
28+
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
29+
}
30+
let javaHome = findJavaHome()
31+
32+
let javaIncludePath = "\(javaHome)/include"
33+
#if os(Linux)
34+
let javaPlatformIncludePath = "\(javaIncludePath)/linux"
35+
#elseif os(macOS)
36+
let javaPlatformIncludePath = "\(javaIncludePath)/darwin"
37+
#else
38+
// TODO: Handle windows as well
39+
#error("Currently only macOS and Linux platforms are supported, this may change in the future.")
40+
#endif
41+
42+
let package = Package(
43+
name: "JavaSieve",
44+
platforms: [
45+
.macOS(.v10_15),
46+
],
47+
dependencies: [
48+
.package(name: "swift-java", path: "../../"),
49+
],
50+
targets: [
51+
.target(
52+
name: "JavaMath",
53+
dependencies: [
54+
.product(name: "JavaKit", package: "swift-java"),
55+
.product(name: "JavaKitJar", package: "swift-java"),
56+
],
57+
swiftSettings: [
58+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
59+
],
60+
plugins: [
61+
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
62+
]
63+
),
64+
65+
// Targets are the basic building blocks of a package, defining a module or a test suite.
66+
// Targets can depend on other targets in this package and products from dependencies.
67+
.executableTarget(
68+
name: "JavaSieve",
69+
dependencies: [
70+
"JavaMath",
71+
.product(name: "JavaKit", package: "swift-java"),
72+
.product(name: "JavaKitCollection", package: "swift-java"),
73+
.product(name: "JavaKitVM", package: "swift-java"),
74+
],
75+
swiftSettings: [
76+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
77+
],
78+
plugins: [
79+
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
80+
]
81+
),
82+
]
83+
)

Samples/JavaSieve/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# JavaKit Example: Using a Java library from Swift
2+
3+
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:
4+
5+
* Use the Java2Swift tool to discover the classes in a Jar file and make them available in Swift
6+
* Layer Swift wrappers for Java classes as separate Swift modules using Java2Swift
7+
* Access static methods of Java classes from Swift
8+
9+
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:
10+
11+
```
12+
git clone https://github.com/gazman-sdk/quadratic-sieve-Java
13+
cd quadratic-sieve-Java
14+
sh ./gradlew jar
15+
```
16+
17+
Then, copy the resulting Jar file (`./build/libs/QuadraticSieve-1.0.jar`) into the `Samples/JavaSieve` directory.
18+
19+
Now we're ready to build and run the Swift program from `Samples/JavaSieve`:
20+
21+
```
22+
swift run JavaSieve
23+
```
24+
25+
The core of the example code is in `Sources/JavaSieve/main.swift`, using the static Java method `SieveOfEratosthenes.findPrimes`:
26+
27+
```swift
28+
let sieveClass = try JavaClass<SieveOfEratosthenes>(in: jvm.environment())
29+
for prime in sieveClass.findPrimes(100)! {
30+
print("Found prime: \(prime.intValue())")
31+
}
32+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"classes" : {
3+
"java.math.BigDecimal" : "BigDecimal",
4+
"java.math.BigInteger" : "BigInteger",
5+
"java.math.MathContext" : "MathContext",
6+
"java.math.RoundingMode" : "RoundingMode",
7+
"java.lang.Integer" : "JavaInteger",
8+
}
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"classPath" : "QuadraticSieve-1.0.jar",
3+
"classes" : {
4+
"com.gazman.quadratic_sieve.QuadraticSieve" : "QuadraticSieve",
5+
"com.gazman.quadratic_sieve.core.BaseFact" : "BaseFact",
6+
"com.gazman.quadratic_sieve.core.matrix.GaussianEliminationMatrix" : "GaussianEliminationMatrix",
7+
"com.gazman.quadratic_sieve.core.matrix.Matrix" : "Matrix",
8+
"com.gazman.quadratic_sieve.core.poly.PolyMiner" : "PolyMiner",
9+
"com.gazman.quadratic_sieve.core.poly.WheelPool" : "WheelPool",
10+
"com.gazman.quadratic_sieve.core.siever.BSmoothData" : "BSmoothData",
11+
"com.gazman.quadratic_sieve.core.siever.BSmoothDataPool" : "BSmoothDataPool",
12+
"com.gazman.quadratic_sieve.core.siever.Siever" : "Siever",
13+
"com.gazman.quadratic_sieve.core.siever.VectorExtractor" : "VectorExtractor",
14+
"com.gazman.quadratic_sieve.data.BSmooth" : "BSmooth",
15+
"com.gazman.quadratic_sieve.data.DataQueue" : "DataQueue",
16+
"com.gazman.quadratic_sieve.data.MagicNumbers" : "MagicNumbers",
17+
"com.gazman.quadratic_sieve.data.PolynomialData" : "PolynomialData",
18+
"com.gazman.quadratic_sieve.data.PrimeBase" : "PrimeBase",
19+
"com.gazman.quadratic_sieve.data.VectorData" : "VectorData",
20+
"com.gazman.quadratic_sieve.data.VectorWorkData" : "VectorWorkData",
21+
"com.gazman.quadratic_sieve.debug.Analytics" : "Analytics",
22+
"com.gazman.quadratic_sieve.debug.AssertUtils" : "AssertUtils",
23+
"com.gazman.quadratic_sieve.debug.Logger" : "Logger",
24+
"com.gazman.quadratic_sieve.fact.TrivialDivision" : "TrivialDivision",
25+
"com.gazman.quadratic_sieve.primes.BigPrimes" : "BigPrimes",
26+
"com.gazman.quadratic_sieve.primes.SieveOfEratosthenes" : "SieveOfEratosthenes",
27+
"com.gazman.quadratic_sieve.utils.MathUtils" : "MathUtils",
28+
"com.gazman.quadratic_sieve.wheel.Wheel" : "Wheel"
29+
}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import JavaKit
16+
import JavaKitVM
17+
18+
let jvm = try JavaVirtualMachine.shared(classPath: ["QuadraticSieve-1.0.jar"])
19+
do {
20+
let sieveClass = try JavaClass<SieveOfEratosthenes>(in: jvm.environment())
21+
for prime in sieveClass.findPrimes(100)! {
22+
print("Found prime: \(prime.intValue())")
23+
}
24+
} catch {
25+
print("Failure: \(error)")
26+
}

USER_GUIDE.md

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Before using this package, set the `JAVA_HOME` environment variable to point at
1111
Existing Java libraries can be wrapped for use in Swift with the `Java2Swift`
1212
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`:
1313

14-
```
14+
```json
1515
{
1616
"classes" : {
1717
"java.math.BigInteger" : "BigInteger"
@@ -91,6 +91,132 @@ if bigInt.isProbablePrime(10) {
9191

9292
Swift ensures that the Java garbage collector will keep the object alive until `bigInt` (and any copies of it) are been destroyed.
9393

94+
### Importing a Jar file into Swift
95+
96+
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:
97+
98+
```swift
99+
swift run Java2Swift --module-name JavaSieve --jar QuadraticSieve-1.0.jar
100+
```
101+
102+
The resulting configuration file will look something like this:
103+
104+
```json
105+
{
106+
"classPath" : "QuadraticSieve-1.0.jar",
107+
"classes" : {
108+
"com.gazman.quadratic_sieve.QuadraticSieve" : "QuadraticSieve",
109+
"com.gazman.quadratic_sieve.core.BaseFact" : "BaseFact",
110+
"com.gazman.quadratic_sieve.core.matrix.GaussianEliminationMatrix" : "GaussianEliminationMatrix",
111+
"com.gazman.quadratic_sieve.core.matrix.Matrix" : "Matrix",
112+
"com.gazman.quadratic_sieve.core.poly.PolyMiner" : "PolyMiner",
113+
"com.gazman.quadratic_sieve.core.poly.WheelPool" : "WheelPool",
114+
"com.gazman.quadratic_sieve.core.siever.BSmoothData" : "BSmoothData",
115+
"com.gazman.quadratic_sieve.core.siever.BSmoothDataPool" : "BSmoothDataPool",
116+
"com.gazman.quadratic_sieve.core.siever.Siever" : "Siever",
117+
"com.gazman.quadratic_sieve.core.siever.VectorExtractor" : "VectorExtractor",
118+
"com.gazman.quadratic_sieve.data.BSmooth" : "BSmooth",
119+
"com.gazman.quadratic_sieve.data.DataQueue" : "DataQueue",
120+
"com.gazman.quadratic_sieve.data.MagicNumbers" : "MagicNumbers",
121+
"com.gazman.quadratic_sieve.data.PolynomialData" : "PolynomialData",
122+
"com.gazman.quadratic_sieve.data.PrimeBase" : "PrimeBase",
123+
"com.gazman.quadratic_sieve.data.VectorData" : "VectorData",
124+
"com.gazman.quadratic_sieve.data.VectorWorkData" : "VectorWorkData",
125+
"com.gazman.quadratic_sieve.debug.Analytics" : "Analytics",
126+
"com.gazman.quadratic_sieve.debug.AssertUtils" : "AssertUtils",
127+
"com.gazman.quadratic_sieve.debug.Logger" : "Logger",
128+
"com.gazman.quadratic_sieve.fact.TrivialDivision" : "TrivialDivision",
129+
"com.gazman.quadratic_sieve.primes.BigPrimes" : "BigPrimes",
130+
"com.gazman.quadratic_sieve.primes.SieveOfEratosthenes" : "SieveOfEratosthenes",
131+
"com.gazman.quadratic_sieve.utils.MathUtils" : "MathUtils",
132+
"com.gazman.quadratic_sieve.wheel.Wheel" : "Wheel"
133+
}
134+
}
135+
```
136+
137+
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.
138+
139+
If you inspect the build output, there are a number of warnings that look like this:
140+
141+
```swift
142+
warning: Unable to translate 'com.gazman.quadratic_sieve.QuadraticSieve' method 'generateN': Java class 'java.math.BigInteger' has not been translated into Swift
143+
```
144+
145+
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:
146+
147+
```swift
148+
.target(
149+
name: "JavaMath",
150+
dependencies: [
151+
.product(name: "JavaKit", package: "swift-java"),
152+
],
153+
plugins: [
154+
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
155+
]
156+
),
157+
```
158+
159+
Then define a a Java2Swift configuration file in `Sources/JavaMath/Java2Swift.config` to bring in the types we need:
160+
161+
```json
162+
{
163+
"classes" : {
164+
"java.math.BigDecimal" : "BigDecimal",
165+
"java.math.BigInteger" : "BigInteger",
166+
"java.math.MathContext" : "MathContext",
167+
"java.math.RoundingMode" : "RoundingMode",
168+
"java.lang.Integer" : "JavaInteger",
169+
}
170+
}
171+
```
172+
173+
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!
174+
175+
### Calling Java static methods from Swift
176+
177+
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:
178+
179+
```java
180+
public class SieveOfEratosthenes {
181+
public static List<Integer> findPrimes(int limit) { ... }
182+
}
183+
```
184+
185+
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:
186+
187+
```swift
188+
let sieveClass = try JavaClass<SieveOfEratosthenes>(in: jvm.environment())
189+
```
190+
191+
Now we can call Java's static methods on that class as instance methods on the `JavaClass` instance, e.g.,
192+
193+
```swift
194+
let primes = sieveClass.findPrimes(100) // returns a List<JavaInteger>?
195+
```
196+
197+
Putting it all together, we can define a main program in `Sources/JavaSieve/main.swift` that looks like this:
198+
199+
```swift
200+
import JavaKit
201+
import JavaKitVM
202+
203+
let jvm = try JavaVirtualMachine.shared(classPath: ["QuadraticSieve-1.0.jar"])
204+
do {
205+
let sieveClass = try JavaClass<SieveOfEratosthenes>(in: jvm.environment())
206+
for prime in sieveClass.findPrimes(100)! {
207+
print("Found prime: \(prime.intValue())")
208+
}
209+
} catch {
210+
print("Failure: \(error)")
211+
}
212+
```
213+
214+
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`.
215+
216+
## Using Java libraries from Swift
217+
218+
This section describes how Java libraries and mapped into Swift and their use from Swift.
219+
94220
### Translation from Java classes into Swift
95221

96222
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 ... {
521647
public static void globalFunction() { ... }
522648
}
523649
```
524-
525-
526-

0 commit comments

Comments
 (0)