diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/Java2Swift/JavaToSwift.swift index 4d7d582c..5799e678 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/Java2Swift/JavaToSwift.swift @@ -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[..(in: environment) + .getSystemClassLoader()! + #endif var javaClasses: [JavaClass] = [] for (javaClassName, swiftName) in config.classes { guard let javaClass = try classLoader.loadClass(javaClassName) else { @@ -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)") @@ -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. @@ -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] = @@ -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" ) } @@ -238,3 +298,14 @@ extension String { return self } } + +@JavaClass("java.lang.ClassLoader") +public struct ClassLoader { + @JavaMethod + public func loadClass(_ arg0: String) throws -> JavaClass? +} + +extension JavaClass { + @JavaStaticMethod + public func getSystemClassLoader() -> ClassLoader? +} diff --git a/Sources/Java2SwiftLib/JavaTranslator+Configuration.swift b/Sources/Java2SwiftLib/JavaTranslator+Configuration.swift index 6025731b..f13a8cce 100644 --- a/Sources/Java2SwiftLib/JavaTranslator+Configuration.swift +++ b/Sources/Java2SwiftLib/JavaTranslator+Configuration.swift @@ -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, diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index 7d2d2e77..2b482b13 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -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) """ } diff --git a/Sources/Java2SwiftLib/StringExtras.swift b/Sources/Java2SwiftLib/StringExtras.swift index 34729883..3e6d7748 100644 --- a/Sources/Java2SwiftLib/StringExtras.swift +++ b/Sources/Java2SwiftLib/StringExtras.swift @@ -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. @@ -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)`" + } } diff --git a/Sources/JavaKitNetwork/Java2Swift.config b/Sources/JavaKitNetwork/Java2Swift.config new file mode 100644 index 00000000..f44e2440 --- /dev/null +++ b/Sources/JavaKitNetwork/Java2Swift.config @@ -0,0 +1,7 @@ +{ + "classes" : { + "java.net.URI" : "URI", + "java.net.URL" : "URL", + "java.net.URLClassLoader" : "URLClassLoader" + } +} diff --git a/Sources/JavaKitReflection/Java2Swift.config b/Sources/JavaKitReflection/Java2Swift.config new file mode 100644 index 00000000..98eb4cd8 --- /dev/null +++ b/Sources/JavaKitReflection/Java2Swift.config @@ -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" + } +} diff --git a/Sources/JavaKitVM/JavaVirtualMachine.swift b/Sources/JavaKitVM/JavaVirtualMachine.swift index 2e95795c..d01580d7 100644 --- a/Sources/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/JavaKitVM/JavaVirtualMachine.swift @@ -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 @@ -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.