Skip to content

JavaKit: Rework @ImplementsJava to be more like @implements language feature #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ let package = Package(
],
swiftSettings: [
.swiftLanguageMode(.v5),
.enableUpcomingFeature("BareSlashRegexLiterals")
.enableUpcomingFeature("BareSlashRegexLiterals"),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
]
),

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

swiftSettings: [
.swiftLanguageMode(.v5),
.enableUpcomingFeature("BareSlashRegexLiterals")
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
.enableUpcomingFeature("BareSlashRegexLiterals"),
]
),

Expand All @@ -296,7 +298,8 @@ let package = Package(
"JavaTypes",
],
swiftSettings: [
.swiftLanguageMode(.v5)
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
]
),

Expand All @@ -314,7 +317,8 @@ let package = Package(
name: "JavaKitTests",
dependencies: ["JavaKit", "JavaKitNetwork"],
swiftSettings: [
.swiftLanguageMode(.v5)
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),

Expand All @@ -341,7 +345,8 @@ let package = Package(
name: "Java2SwiftTests",
dependencies: ["Java2SwiftLib"],
swiftSettings: [
.swiftLanguageMode(.v5)
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),

Expand Down
34 changes: 33 additions & 1 deletion Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,44 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin {
outputDirectory.appending(path: "\(swiftName).swift")
}

// Find the Java .class files generated from prior plugins.
let compiledClassFiles = sourceModule.pluginGeneratedResources.filter { url in
url.pathExtension == "class"
}

if let firstClassFile = compiledClassFiles.first {
// Keep stripping off parts of the path until we hit the "Java" part.
// That's where the class path starts.
var classpath = firstClassFile
while classpath.lastPathComponent != "Java" {
classpath.deleteLastPathComponent()
}
arguments += [ "--classpath", classpath.path() ]

// For each of the class files, note that it can have Swift-native
// implementations. We figure this out based on the path.
for classFile in compiledClassFiles {
var classFile = classFile.deletingPathExtension()
var classNameComponents: [String] = []

while classFile.lastPathComponent != "Java" {
classNameComponents.append(classFile.lastPathComponent)
classFile.deleteLastPathComponent()
}

let className = classNameComponents
.reversed()
.joined(separator: ".")
arguments += [ "--swift-native-implementation", className]
}
}

return [
.buildCommand(
displayName: "Wrapping \(config.classes.count) Java classes target \(sourceModule.name) in Swift",
executable: try context.tool(named: "Java2Swift").url,
arguments: arguments,
inputFiles: [ configFile ],
inputFiles: [ configFile ] + compiledClassFiles,
outputFiles: outputSwiftFiles
)
]
Expand Down
41 changes: 39 additions & 2 deletions Samples/JavaKitSampleApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,42 @@
import CompilerPluginSupport
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: "JavaKitSampleApp",
platforms: [
Expand Down Expand Up @@ -34,11 +70,12 @@ let package = Package(
.product(name: "JavaKitJar", package: "swift-java"),
],
swiftSettings: [
.swiftLanguageMode(.v5)
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
plugins: [
.plugin(name: "JavaCompilerPlugin", package: "swift-java"),
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
.plugin(name: "JavaCompilerPlugin", package: "swift-java")
]
),
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"classes" : {
"java.util.ArrayList" : "ArrayList"
"java.util.ArrayList" : "ArrayList",
"com.example.swift.HelloSwift" : "HelloSwift",
"com.example.swift.HelloSubclass" : "HelloSubclass",
"com.example.swift.JavaKitSampleMain" : "JavaKitSampleMain"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,10 @@ enum SwiftWrappedError: Error {
case message(String)
}

@JavaClass("com.example.swift.HelloSwift")
struct HelloSwift {
@JavaImplementation("com.example.swift.HelloSwift")
extension HelloSwift: HelloSwiftNativeMethods {
@JavaMethod
init(environment: JNIEnvironment)

@JavaMethod
func sayHelloBack(_ i: Int32) -> Double

@JavaMethod
func greet(_ name: String)

@JavaMethod
func doublesToStrings(doubles: [Double]) -> [String]

@JavaMethod
func throwMessage(message: String) throws

@JavaField
var value: Double

@JavaField
var name: String

@ImplementsJava
func sayHello(i: Int32, _ j: Int32) -> Int32 {
func sayHello(_ i: Int32, _ j: Int32) -> Int32 {
print("Hello from Swift!")
let answer = self.sayHelloBack(i + j)
print("Swift got back \(answer) from Java")
Expand All @@ -65,7 +44,7 @@ struct HelloSwift {
self.name = "a 🗑️-collected language"
_ = self.sayHelloBack(42)

let strings = doublesToStrings(doubles: [3.14159, 2.71828])
let strings = doublesToStrings([3.14159, 2.71828])
print("Converting doubles to strings: \(strings)")

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

// Create a new instance.
let helloSubFromSwift = HelloSubclass(greeting: "Hello from Swift", environment: javaEnvironment)
let helloSubFromSwift = HelloSubclass("Hello from Swift", environment: javaEnvironment)
helloSubFromSwift.greetMe()

do {
try throwMessage(message: "I am an error")
try throwMessage("I am an error")
} catch {
print("Caught Java error: \(error)")
}

return i * j
}

@ImplementsJava
func throwMessageFromSwift(message: String) throws -> String {
@JavaMethod
func throwMessageFromSwift(_ message: String) throws -> String {
throw SwiftWrappedError.message(message)
}
}

extension JavaClass<HelloSwift> {
@JavaStaticField
var initialValue: Double
}

@JavaClass("com.example.swift.HelloSubclass", extends: HelloSwift.self)
struct HelloSubclass {
@JavaField
var greeting: String

@JavaMethod
func greetMe()

@JavaMethod
init(greeting: String, environment: JNIEnvironment)
}


func removeLast(arrayList: ArrayList<JavaClass<HelloSwift>>) {
if let lastObject = arrayList.getLast() {
_ = arrayList.remove(lastObject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public HelloSubclass(String greeting) {
this.greeting = greeting;
}

private void greetMe() {
public void greetMe() {
super.greet(greeting);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
package com.example.swift;

public class HelloSwift {
private double value;
private static double initialValue = 3.14159;
private String name = "Java";
public double value;
public static double initialValue = 3.14159;
public String name = "Java";

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

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

String[] doublesToStrings(double[] doubles) {
public String[] doublesToStrings(double[] doubles) {
int size = doubles.length;
String[] strings = new String[size];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
* For the Swift implementation refer to
*/
public class JavaKitSampleMain {

public static void main(String[] args) {
int result = new HelloSubclass("Swift").sayHello(17, 25);
System.out.println("sayHello(17, 25) = " + result);
Expand Down
9 changes: 9 additions & 0 deletions Sources/Java2Swift/JavaToSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ struct JavaToSwift: ParsableCommand {
)
var classpath: [String] = []

@Option(
help: "The names of Java classes whose declared native methods will be implemented in Swift."
)
var swiftNativeImplementation: [String] = []

@Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the Java2Swift configuration file.")
var outputDirectory: String? = nil

Expand Down Expand Up @@ -181,6 +186,10 @@ struct JavaToSwift: ParsableCommand {
environment: environment
)

// Keep track of all of the Java classes that will have
// Swift-native implementations.
translator.swiftNativeImplementations = Set(swiftNativeImplementation)

// Note all of the dependent configurations.
for (swiftModuleName, dependentConfig) in dependentConfigs {
translator.addConfiguration(
Expand Down
Loading