Skip to content

Commit b1a82db

Browse files
authored
JavaKit: Rework @ImplementsJava to be more like @implements language feature (#105)
1 parent a6efd5d commit b1a82db

26 files changed

+557
-375
lines changed

Package.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ let package = Package(
262262
],
263263
swiftSettings: [
264264
.swiftLanguageMode(.v5),
265-
.enableUpcomingFeature("BareSlashRegexLiterals")
265+
.enableUpcomingFeature("BareSlashRegexLiterals"),
266+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
266267
]
267268
),
268269

@@ -281,7 +282,8 @@ let package = Package(
281282

282283
swiftSettings: [
283284
.swiftLanguageMode(.v5),
284-
.enableUpcomingFeature("BareSlashRegexLiterals")
285+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
286+
.enableUpcomingFeature("BareSlashRegexLiterals"),
285287
]
286288
),
287289

@@ -296,7 +298,8 @@ let package = Package(
296298
"JavaTypes",
297299
],
298300
swiftSettings: [
299-
.swiftLanguageMode(.v5)
301+
.swiftLanguageMode(.v5),
302+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
300303
]
301304
),
302305

@@ -314,7 +317,8 @@ let package = Package(
314317
name: "JavaKitTests",
315318
dependencies: ["JavaKit", "JavaKitNetwork"],
316319
swiftSettings: [
317-
.swiftLanguageMode(.v5)
320+
.swiftLanguageMode(.v5),
321+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
318322
]
319323
),
320324

@@ -341,7 +345,8 @@ let package = Package(
341345
name: "Java2SwiftTests",
342346
dependencies: ["Java2SwiftLib"],
343347
swiftSettings: [
344-
.swiftLanguageMode(.v5)
348+
.swiftLanguageMode(.v5),
349+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
345350
]
346351
),
347352

Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,44 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin {
9393
outputDirectory.appending(path: "\(swiftName).swift")
9494
}
9595

96+
// Find the Java .class files generated from prior plugins.
97+
let compiledClassFiles = sourceModule.pluginGeneratedResources.filter { url in
98+
url.pathExtension == "class"
99+
}
100+
101+
if let firstClassFile = compiledClassFiles.first {
102+
// Keep stripping off parts of the path until we hit the "Java" part.
103+
// That's where the class path starts.
104+
var classpath = firstClassFile
105+
while classpath.lastPathComponent != "Java" {
106+
classpath.deleteLastPathComponent()
107+
}
108+
arguments += [ "--classpath", classpath.path() ]
109+
110+
// For each of the class files, note that it can have Swift-native
111+
// implementations. We figure this out based on the path.
112+
for classFile in compiledClassFiles {
113+
var classFile = classFile.deletingPathExtension()
114+
var classNameComponents: [String] = []
115+
116+
while classFile.lastPathComponent != "Java" {
117+
classNameComponents.append(classFile.lastPathComponent)
118+
classFile.deleteLastPathComponent()
119+
}
120+
121+
let className = classNameComponents
122+
.reversed()
123+
.joined(separator: ".")
124+
arguments += [ "--swift-native-implementation", className]
125+
}
126+
}
127+
96128
return [
97129
.buildCommand(
98130
displayName: "Wrapping \(config.classes.count) Java classes target \(sourceModule.name) in Swift",
99131
executable: try context.tool(named: "Java2Swift").url,
100132
arguments: arguments,
101-
inputFiles: [ configFile ],
133+
inputFiles: [ configFile ] + compiledClassFiles,
102134
outputFiles: outputSwiftFiles
103135
)
104136
]

Samples/JavaKitSampleApp/Package.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,42 @@
44
import CompilerPluginSupport
55
import PackageDescription
66

7+
import class Foundation.FileManager
8+
import class Foundation.ProcessInfo
9+
10+
// Note: the JAVA_HOME environment variable must be set to point to where
11+
// Java is installed, e.g.,
12+
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
13+
func findJavaHome() -> String {
14+
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
15+
return home
16+
}
17+
18+
// This is a workaround for envs (some IDEs) which have trouble with
19+
// picking up env variables during the build process
20+
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
21+
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
22+
if let lastChar = home.last, lastChar.isNewline {
23+
return String(home.dropLast())
24+
}
25+
26+
return home
27+
}
28+
29+
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
30+
}
31+
let javaHome = findJavaHome()
32+
33+
let javaIncludePath = "\(javaHome)/include"
34+
#if os(Linux)
35+
let javaPlatformIncludePath = "\(javaIncludePath)/linux"
36+
#elseif os(macOS)
37+
let javaPlatformIncludePath = "\(javaIncludePath)/darwin"
38+
#else
39+
// TODO: Handle windows as well
40+
#error("Currently only macOS and Linux platforms are supported, this may change in the future.")
41+
#endif
42+
743
let package = Package(
844
name: "JavaKitSampleApp",
945
platforms: [
@@ -34,11 +70,12 @@ let package = Package(
3470
.product(name: "JavaKitJar", package: "swift-java"),
3571
],
3672
swiftSettings: [
37-
.swiftLanguageMode(.v5)
73+
.swiftLanguageMode(.v5),
74+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
3875
],
3976
plugins: [
77+
.plugin(name: "JavaCompilerPlugin", package: "swift-java"),
4078
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
41-
.plugin(name: "JavaCompilerPlugin", package: "swift-java")
4279
]
4380
),
4481
]
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"classes" : {
3-
"java.util.ArrayList" : "ArrayList"
3+
"java.util.ArrayList" : "ArrayList",
4+
"com.example.swift.HelloSwift" : "HelloSwift",
5+
"com.example.swift.HelloSubclass" : "HelloSubclass",
6+
"com.example.swift.JavaKitSampleMain" : "JavaKitSampleMain"
47
}
58
}

Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,10 @@ enum SwiftWrappedError: Error {
1818
case message(String)
1919
}
2020

21-
@JavaClass("com.example.swift.HelloSwift")
22-
struct HelloSwift {
21+
@JavaImplementation("com.example.swift.HelloSwift")
22+
extension HelloSwift: HelloSwiftNativeMethods {
2323
@JavaMethod
24-
init(environment: JNIEnvironment)
25-
26-
@JavaMethod
27-
func sayHelloBack(_ i: Int32) -> Double
28-
29-
@JavaMethod
30-
func greet(_ name: String)
31-
32-
@JavaMethod
33-
func doublesToStrings(doubles: [Double]) -> [String]
34-
35-
@JavaMethod
36-
func throwMessage(message: String) throws
37-
38-
@JavaField
39-
var value: Double
40-
41-
@JavaField
42-
var name: String
43-
44-
@ImplementsJava
45-
func sayHello(i: Int32, _ j: Int32) -> Int32 {
24+
func sayHello(_ i: Int32, _ j: Int32) -> Int32 {
4625
print("Hello from Swift!")
4726
let answer = self.sayHelloBack(i + j)
4827
print("Swift got back \(answer) from Java")
@@ -65,7 +44,7 @@ struct HelloSwift {
6544
self.name = "a 🗑️-collected language"
6645
_ = self.sayHelloBack(42)
6746

68-
let strings = doublesToStrings(doubles: [3.14159, 2.71828])
47+
let strings = doublesToStrings([3.14159, 2.71828])
6948
print("Converting doubles to strings: \(strings)")
7049

7150
// Try downcasting
@@ -83,42 +62,24 @@ struct HelloSwift {
8362
assert(!newHello.is(HelloSubclass.self))
8463

8564
// Create a new instance.
86-
let helloSubFromSwift = HelloSubclass(greeting: "Hello from Swift", environment: javaEnvironment)
65+
let helloSubFromSwift = HelloSubclass("Hello from Swift", environment: javaEnvironment)
8766
helloSubFromSwift.greetMe()
8867

8968
do {
90-
try throwMessage(message: "I am an error")
69+
try throwMessage("I am an error")
9170
} catch {
9271
print("Caught Java error: \(error)")
9372
}
9473

9574
return i * j
9675
}
9776

98-
@ImplementsJava
99-
func throwMessageFromSwift(message: String) throws -> String {
77+
@JavaMethod
78+
func throwMessageFromSwift(_ message: String) throws -> String {
10079
throw SwiftWrappedError.message(message)
10180
}
10281
}
10382

104-
extension JavaClass<HelloSwift> {
105-
@JavaStaticField
106-
var initialValue: Double
107-
}
108-
109-
@JavaClass("com.example.swift.HelloSubclass", extends: HelloSwift.self)
110-
struct HelloSubclass {
111-
@JavaField
112-
var greeting: String
113-
114-
@JavaMethod
115-
func greetMe()
116-
117-
@JavaMethod
118-
init(greeting: String, environment: JNIEnvironment)
119-
}
120-
121-
12283
func removeLast(arrayList: ArrayList<JavaClass<HelloSwift>>) {
12384
if let lastObject = arrayList.getLast() {
12485
_ = arrayList.remove(lastObject)

Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSubclass.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public HelloSubclass(String greeting) {
2121
this.greeting = greeting;
2222
}
2323

24-
private void greetMe() {
24+
public void greetMe() {
2525
super.greet(greeting);
2626
}
2727
}

Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
package com.example.swift;
1616

1717
public class HelloSwift {
18-
private double value;
19-
private static double initialValue = 3.14159;
20-
private String name = "Java";
18+
public double value;
19+
public static double initialValue = 3.14159;
20+
public String name = "Java";
2121

2222
static {
2323
System.loadLibrary("JavaKitExample");
@@ -31,7 +31,7 @@ public HelloSwift() {
3131
public native String throwMessageFromSwift(String message) throws Exception;
3232

3333
// To be called back by the native code
34-
private double sayHelloBack(int i) {
34+
public double sayHelloBack(int i) {
3535
System.out.println("And hello back from " + name + "! You passed me " + i);
3636
return value;
3737
}
@@ -40,7 +40,7 @@ public void greet(String name) {
4040
System.out.println("Salutations, " + name);
4141
}
4242

43-
String[] doublesToStrings(double[] doubles) {
43+
public String[] doublesToStrings(double[] doubles) {
4444
int size = doubles.length;
4545
String[] strings = new String[size];
4646

Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/JavaKitSampleMain.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
* For the Swift implementation refer to
2020
*/
2121
public class JavaKitSampleMain {
22-
2322
public static void main(String[] args) {
2423
int result = new HelloSubclass("Swift").sayHello(17, 25);
2524
System.out.println("sayHello(17, 25) = " + result);

Sources/Java2Swift/JavaToSwift.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ struct JavaToSwift: ParsableCommand {
4848
)
4949
var classpath: [String] = []
5050

51+
@Option(
52+
help: "The names of Java classes whose declared native methods will be implemented in Swift."
53+
)
54+
var swiftNativeImplementation: [String] = []
55+
5156
@Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the Java2Swift configuration file.")
5257
var outputDirectory: String? = nil
5358

@@ -181,6 +186,10 @@ struct JavaToSwift: ParsableCommand {
181186
environment: environment
182187
)
183188

189+
// Keep track of all of the Java classes that will have
190+
// Swift-native implementations.
191+
translator.swiftNativeImplementations = Set(swiftNativeImplementation)
192+
184193
// Note all of the dependent configurations.
185194
for (swiftModuleName, dependentConfig) in dependentConfigs {
186195
translator.addConfiguration(

0 commit comments

Comments
 (0)