Skip to content
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
165 changes: 118 additions & 47 deletions Sources/Java2Swift/JavaToSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,55 +58,118 @@ struct JavaToSwift: ParsableCommand {
)
var input: String

/// Describes what kind of generation action is being performed by
/// Java2Swift.
enum GenerationMode {
/// Generate a configuration file given a Jar file.
case configuration(jarFile: String)

/// Generate Swift wrappers for Java classes based on the given
/// configuration.
case classWrappers(Configuration)
}

mutating func run() throws {
var vmOptions: [String] = []
let classpath = classPathWithJarFile
if !classpath.isEmpty {
vmOptions.append("-cp")
vmOptions.append(contentsOf: classpath)
// Determine the mode in which we'll execute.
let generationMode: GenerationMode
if jarFile {
generationMode = .configuration(jarFile: input)
} else {
let config = try JavaTranslator.readConfiguration(from: URL(filePath: input))
generationMode = .classWrappers(config)
}

// Load all of the dependent configurations and associate them with Swift
// modules.
let dependentConfigs = try dependsOn.map { dependentConfig in
guard let equalLoc = dependentConfig.firstIndex(of: "=") else {
throw JavaToSwiftError.badConfigOption(dependentConfig)
}

let afterEqual = dependentConfig.index(after: equalLoc)
let swiftModuleName = String(dependentConfig[..<equalLoc])
let configFileName = String(dependentConfig[afterEqual...])

let config = try JavaTranslator.readConfiguration(from: URL(filePath: configFileName))

return (swiftModuleName, config)
}

let jvm = try JavaVirtualMachine.shared(vmOptions: vmOptions)
try run(environment: jvm.environment())
// Form a class path from all of our input sources:
// * Command-line option --classpath
var classPathPieces: [String] = classpath
switch generationMode {
case .configuration(jarFile: let jarFile):
// * Jar file (in `-jar-file` mode)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice this before, -jar probably would spell nicer for the option btw.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! PR incoming

classPathPieces.append(jarFile)
case .classWrappers(let config):
// * Class path specified in the configuration file (if any)
config.classPath.map { classPathPieces.append($0) }
}

// * Classes paths from all dependent configuration files
for (_, config) in dependentConfigs {
config.classPath.map { classPathPieces.append($0) }
}

// Bring up the Java VM.
let jvm = try JavaVirtualMachine.shared(classPath: classPathPieces)

// Run the generation step.
let classPath = classPathPieces.joined(separator: ":")
switch generationMode {
case .configuration(jarFile: let jarFile):
try emitConfiguration(
forJarFile: jarFile,
classPath: classPath,
environment: jvm.environment()
)

case .classWrappers(let config):
try generateWrappers(
config: config,
classPath: classPath,
dependentConfigs: dependentConfigs,
environment: jvm.environment()
)
}
}

mutating func run(environment: JNIEnvironment) throws {
/// Generate wrapper
mutating func generateWrappers(
config: Configuration,
classPath: String,
dependentConfigs: [(String, Configuration)],
environment: JNIEnvironment
) throws {
let translator = JavaTranslator(
swiftModuleName: moduleName,
environment: environment
)

// Load all of the configurations this depends on.
for config in dependsOn {
guard let equalLoc = config.firstIndex(of: "=") else {
throw JavaToSwiftError.badConfigOption(config)
}

let afterEqual = config.index(after: equalLoc)
let swiftModuleName = String(config[..<equalLoc])
let configFileName = String(config[afterEqual...])

try translator.loadDependentConfiguration(
forSwiftModule: swiftModuleName,
from: URL(filePath: configFileName)
// Note all of the dependent configurations.
for (swiftModuleName, dependentConfig) in dependentConfigs {
translator.addConfiguration(
dependentConfig,
forSwiftModule: swiftModuleName
)
}

// Jar file mode: read a Jar file and output a configuration.
if jarFile {
return try emitConfiguration(forJarFile: input, environment: environment)
}

// Load the configuration file.
let config = try translator.readConfiguration(from: URL(filePath: input))
// Add the configuration for this module.
translator.addConfiguration(config, forSwiftModule: moduleName)

// Load all of the requested classes.
#if false
let classLoader = URLClassLoader(
try classPathWithJarFile.map {
try URL("file://\($0)", environment: environment)
},
[
try URL("file://\(classPath)", environment: environment)
],
environment: environment
)
#else
let classLoader = try JavaClass<ClassLoader>(in: environment)
.getSystemClassLoader()!
#endif
var javaClasses: [JavaClass<JavaObject>] = []
for (javaClassName, swiftName) in config.classes {
guard let javaClass = try classLoader.loadClass(javaClassName) else {
Expand Down Expand Up @@ -146,15 +209,6 @@ struct JavaToSwift: ParsableCommand {
}
}

/// Return the class path augmented with the Jar file, if there is one.
var classPathWithJarFile: [String] {
if jarFile {
return [input] + classpath
}

return classpath
}

func writeContents(_ contents: String, to filename: String, description: String) throws {
if outputDirectory == "-" {
print("// \(filename) - \(description)")
Expand All @@ -171,10 +225,13 @@ struct JavaToSwift: ParsableCommand {
print(" done.")
}

func emitConfiguration(forJarFile jarFileName: String, environment: JNIEnvironment) throws {
var configuration = Configuration(
classPath: classPathWithJarFile.joined(separator: ":")
)
func emitConfiguration(
forJarFile jarFileName: String,
classPath: String,
environment: JNIEnvironment
) throws {
var configuration = Configuration(classPath: classPath)

let jarFile = try JarFile(jarFileName, false, environment: environment)
for entry in jarFile.entries()! {
// We only look at class files in the Jar file.
Expand All @@ -190,6 +247,11 @@ struct JavaToSwift: ParsableCommand {
}
}

// TODO: For now, skip all nested classes.
if entry.getName().contains("$") {
continue
}

let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
.dropLast(".class".count))
configuration.classes[javaCanonicalName] =
Expand All @@ -205,9 +267,7 @@ struct JavaToSwift: ParsableCommand {
// Write the file.
try writeContents(
contents,
to: URL(filePath: outputDirectory)
.appending(path: "Java2Swift.config")
.path(percentEncoded: false),
to: "Java2Swift.config",
description: "Java2Swift configuration file"
)
}
Expand Down Expand Up @@ -238,3 +298,14 @@ extension String {
return self
}
}

@JavaClass("java.lang.ClassLoader")
public struct ClassLoader {
@JavaMethod
public func loadClass(_ arg0: String) throws -> JavaClass<JavaObject>?
}

extension JavaClass<ClassLoader> {
@JavaStaticMethod
public func getSystemClassLoader() -> ClassLoader?
}
8 changes: 2 additions & 6 deletions Sources/Java2SwiftLib/JavaTranslator+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@ import Foundation

extension JavaTranslator {
/// Read a configuration file from the given URL.
package func readConfiguration(from url: URL) throws -> Configuration {
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.
package func loadDependentConfiguration(forSwiftModule swiftModule: String, from url: URL) throws {
let config = try readConfiguration(from: url)

// TODO: Should we merge the class path from our dependencies?

package func addConfiguration(_ config: Configuration, forSwiftModule swiftModule: String) {
for (javaClassName, swiftName) in config.classes {
translatedClasses[javaClassName] = (
swiftType: swiftName,
Expand Down
7 changes: 4 additions & 3 deletions Sources/Java2SwiftLib/JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -429,20 +429,21 @@ extension JavaTranslator {
}

let throwsStr = javaMethod.throwsCheckedException ? "throws" : ""

let swiftMethodName = javaMethod.getName().escapedSwiftName
let methodAttribute: AttributeSyntax = javaMethod.isStatic ? "@JavaStaticMethod" : "@JavaMethod";
return """
\(methodAttribute)
public func \(raw: javaMethod.getName())\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
public func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
"""
}

package func translateField(_ javaField: Field) throws -> DeclSyntax {
let typeName = try getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: true)
let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField";
let swiftFieldName = javaField.getName().escapedSwiftName
return """
\(fieldAttribute)
public var \(raw: javaField.getName()): \(raw: typeName)
public var \(raw: swiftFieldName): \(raw: typeName)
"""
}

Expand Down
10 changes: 10 additions & 0 deletions Sources/Java2SwiftLib/StringExtras.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SwiftParser

extension String {
/// Split the Swift type name into parent type + innermost type name.
Expand All @@ -24,4 +25,13 @@ extension String {
name: String(suffix(from: index(after: lastDot)))
)
}

/// Escape a name with backticks if it's a Swift keyword.
var escapedSwiftName: String {
if isValidSwiftIdentifier(for: .variableName) {
return self
}

return "`\(self)`"
}
}
7 changes: 7 additions & 0 deletions Sources/JavaKitNetwork/Java2Swift.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"classes" : {
"java.net.URI" : "URI",
"java.net.URL" : "URL",
"java.net.URLClassLoader" : "URLClassLoader"
}
}
18 changes: 18 additions & 0 deletions Sources/JavaKitReflection/Java2Swift.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"classes" : {
"java.lang.annotation.Annotation" : "Annotation",
"java.lang.reflect.AccessibleObject" : "AccessibleObject",
"java.lang.reflect.AnnotatedType" : "AnnotatedType",
"java.lang.reflect.Constructor" : "Constructor",
"java.lang.reflect.Executable" : "Executable",
"java.lang.reflect.Field" : "Field",
"java.lang.reflect.GenericArrayType" : "GenericArrayType",
"java.lang.reflect.GenericDeclaration" : "GenericDeclaration",
"java.lang.reflect.Method" : "Method",
"java.lang.reflect.Parameter" : "Parameter",
"java.lang.reflect.ParameterizedType" : "ParameterizedType",
"java.lang.reflect.Type" : "Type",
"java.lang.reflect.TypeVariable" : "TypeVariable",
"java.lang.reflect.WildcardType" : "WildcardType"
}
}
4 changes: 2 additions & 2 deletions Sources/JavaKitVM/JavaVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
private init(
classPath: [String] = [],
vmOptions: [String] = [],
ignoreUnrecognized: Bool = true
ignoreUnrecognized: Bool = false
) throws {
var jvm: JavaVMPointer? = nil
var environment: UnsafeMutableRawPointer? = nil
Expand Down Expand Up @@ -176,7 +176,7 @@ extension JavaVirtualMachine {
public static func shared(
classPath: [String] = [],
vmOptions: [String] = [],
ignoreUnrecognized: Bool = true
ignoreUnrecognized: Bool = false
) throws -> JavaVirtualMachine {
try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in
// If we already have a JavaVirtualMachine instance, return it.
Expand Down