Skip to content

Commit 36f49d1

Browse files
authored
Merge pull request #87 from DougGregor/java2swift-jar-files
Java2Swift improvements for producing configuration files from Jar files
2 parents 67f81b3 + 892e27e commit 36f49d1

File tree

7 files changed

+161
-58
lines changed

7 files changed

+161
-58
lines changed

Sources/Java2Swift/JavaToSwift.swift

Lines changed: 118 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -58,55 +58,118 @@ struct JavaToSwift: ParsableCommand {
5858
)
5959
var input: String
6060

61+
/// Describes what kind of generation action is being performed by
62+
/// Java2Swift.
63+
enum GenerationMode {
64+
/// Generate a configuration file given a Jar file.
65+
case configuration(jarFile: String)
66+
67+
/// Generate Swift wrappers for Java classes based on the given
68+
/// configuration.
69+
case classWrappers(Configuration)
70+
}
71+
6172
mutating func run() throws {
62-
var vmOptions: [String] = []
63-
let classpath = classPathWithJarFile
64-
if !classpath.isEmpty {
65-
vmOptions.append("-cp")
66-
vmOptions.append(contentsOf: classpath)
73+
// Determine the mode in which we'll execute.
74+
let generationMode: GenerationMode
75+
if jarFile {
76+
generationMode = .configuration(jarFile: input)
77+
} else {
78+
let config = try JavaTranslator.readConfiguration(from: URL(filePath: input))
79+
generationMode = .classWrappers(config)
80+
}
81+
82+
// Load all of the dependent configurations and associate them with Swift
83+
// modules.
84+
let dependentConfigs = try dependsOn.map { dependentConfig in
85+
guard let equalLoc = dependentConfig.firstIndex(of: "=") else {
86+
throw JavaToSwiftError.badConfigOption(dependentConfig)
87+
}
88+
89+
let afterEqual = dependentConfig.index(after: equalLoc)
90+
let swiftModuleName = String(dependentConfig[..<equalLoc])
91+
let configFileName = String(dependentConfig[afterEqual...])
92+
93+
let config = try JavaTranslator.readConfiguration(from: URL(filePath: configFileName))
94+
95+
return (swiftModuleName, config)
6796
}
6897

69-
let jvm = try JavaVirtualMachine.shared(vmOptions: vmOptions)
70-
try run(environment: jvm.environment())
98+
// Form a class path from all of our input sources:
99+
// * Command-line option --classpath
100+
var classPathPieces: [String] = classpath
101+
switch generationMode {
102+
case .configuration(jarFile: let jarFile):
103+
// * Jar file (in `-jar-file` mode)
104+
classPathPieces.append(jarFile)
105+
case .classWrappers(let config):
106+
// * Class path specified in the configuration file (if any)
107+
config.classPath.map { classPathPieces.append($0) }
108+
}
109+
110+
// * Classes paths from all dependent configuration files
111+
for (_, config) in dependentConfigs {
112+
config.classPath.map { classPathPieces.append($0) }
113+
}
114+
115+
// Bring up the Java VM.
116+
let jvm = try JavaVirtualMachine.shared(classPath: classPathPieces)
117+
118+
// Run the generation step.
119+
let classPath = classPathPieces.joined(separator: ":")
120+
switch generationMode {
121+
case .configuration(jarFile: let jarFile):
122+
try emitConfiguration(
123+
forJarFile: jarFile,
124+
classPath: classPath,
125+
environment: jvm.environment()
126+
)
127+
128+
case .classWrappers(let config):
129+
try generateWrappers(
130+
config: config,
131+
classPath: classPath,
132+
dependentConfigs: dependentConfigs,
133+
environment: jvm.environment()
134+
)
135+
}
71136
}
72137

73-
mutating func run(environment: JNIEnvironment) throws {
138+
/// Generate wrapper
139+
mutating func generateWrappers(
140+
config: Configuration,
141+
classPath: String,
142+
dependentConfigs: [(String, Configuration)],
143+
environment: JNIEnvironment
144+
) throws {
74145
let translator = JavaTranslator(
75146
swiftModuleName: moduleName,
76147
environment: environment
77148
)
78149

79-
// Load all of the configurations this depends on.
80-
for config in dependsOn {
81-
guard let equalLoc = config.firstIndex(of: "=") else {
82-
throw JavaToSwiftError.badConfigOption(config)
83-
}
84-
85-
let afterEqual = config.index(after: equalLoc)
86-
let swiftModuleName = String(config[..<equalLoc])
87-
let configFileName = String(config[afterEqual...])
88-
89-
try translator.loadDependentConfiguration(
90-
forSwiftModule: swiftModuleName,
91-
from: URL(filePath: configFileName)
150+
// Note all of the dependent configurations.
151+
for (swiftModuleName, dependentConfig) in dependentConfigs {
152+
translator.addConfiguration(
153+
dependentConfig,
154+
forSwiftModule: swiftModuleName
92155
)
93156
}
94157

95-
// Jar file mode: read a Jar file and output a configuration.
96-
if jarFile {
97-
return try emitConfiguration(forJarFile: input, environment: environment)
98-
}
99-
100-
// Load the configuration file.
101-
let config = try translator.readConfiguration(from: URL(filePath: input))
158+
// Add the configuration for this module.
159+
translator.addConfiguration(config, forSwiftModule: moduleName)
102160

103161
// Load all of the requested classes.
162+
#if false
104163
let classLoader = URLClassLoader(
105-
try classPathWithJarFile.map {
106-
try URL("file://\($0)", environment: environment)
107-
},
164+
[
165+
try URL("file://\(classPath)", environment: environment)
166+
],
108167
environment: environment
109168
)
169+
#else
170+
let classLoader = try JavaClass<ClassLoader>(in: environment)
171+
.getSystemClassLoader()!
172+
#endif
110173
var javaClasses: [JavaClass<JavaObject>] = []
111174
for (javaClassName, swiftName) in config.classes {
112175
guard let javaClass = try classLoader.loadClass(javaClassName) else {
@@ -146,15 +209,6 @@ struct JavaToSwift: ParsableCommand {
146209
}
147210
}
148211

149-
/// Return the class path augmented with the Jar file, if there is one.
150-
var classPathWithJarFile: [String] {
151-
if jarFile {
152-
return [input] + classpath
153-
}
154-
155-
return classpath
156-
}
157-
158212
func writeContents(_ contents: String, to filename: String, description: String) throws {
159213
if outputDirectory == "-" {
160214
print("// \(filename) - \(description)")
@@ -171,10 +225,13 @@ struct JavaToSwift: ParsableCommand {
171225
print(" done.")
172226
}
173227

174-
func emitConfiguration(forJarFile jarFileName: String, environment: JNIEnvironment) throws {
175-
var configuration = Configuration(
176-
classPath: classPathWithJarFile.joined(separator: ":")
177-
)
228+
func emitConfiguration(
229+
forJarFile jarFileName: String,
230+
classPath: String,
231+
environment: JNIEnvironment
232+
) throws {
233+
var configuration = Configuration(classPath: classPath)
234+
178235
let jarFile = try JarFile(jarFileName, false, environment: environment)
179236
for entry in jarFile.entries()! {
180237
// We only look at class files in the Jar file.
@@ -190,6 +247,11 @@ struct JavaToSwift: ParsableCommand {
190247
}
191248
}
192249

250+
// TODO: For now, skip all nested classes.
251+
if entry.getName().contains("$") {
252+
continue
253+
}
254+
193255
let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
194256
.dropLast(".class".count))
195257
configuration.classes[javaCanonicalName] =
@@ -205,9 +267,7 @@ struct JavaToSwift: ParsableCommand {
205267
// Write the file.
206268
try writeContents(
207269
contents,
208-
to: URL(filePath: outputDirectory)
209-
.appending(path: "Java2Swift.config")
210-
.path(percentEncoded: false),
270+
to: "Java2Swift.config",
211271
description: "Java2Swift configuration file"
212272
)
213273
}
@@ -238,3 +298,14 @@ extension String {
238298
return self
239299
}
240300
}
301+
302+
@JavaClass("java.lang.ClassLoader")
303+
public struct ClassLoader {
304+
@JavaMethod
305+
public func loadClass(_ arg0: String) throws -> JavaClass<JavaObject>?
306+
}
307+
308+
extension JavaClass<ClassLoader> {
309+
@JavaStaticMethod
310+
public func getSystemClassLoader() -> ClassLoader?
311+
}

Sources/Java2SwiftLib/JavaTranslator+Configuration.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,14 @@ import Foundation
1616

1717
extension JavaTranslator {
1818
/// Read a configuration file from the given URL.
19-
package func readConfiguration(from url: URL) throws -> Configuration {
19+
package static func readConfiguration(from url: URL) throws -> Configuration {
2020
let contents = try Data(contentsOf: url)
2121
return try JSONDecoder().decode(Configuration.self, from: contents)
2222
}
2323

2424
/// Load the configuration file with the given name to populate the known set of
2525
/// translated Java classes.
26-
package func loadDependentConfiguration(forSwiftModule swiftModule: String, from url: URL) throws {
27-
let config = try readConfiguration(from: url)
28-
29-
// TODO: Should we merge the class path from our dependencies?
30-
26+
package func addConfiguration(_ config: Configuration, forSwiftModule swiftModule: String) {
3127
for (javaClassName, swiftName) in config.classes {
3228
translatedClasses[javaClassName] = (
3329
swiftType: swiftName,

Sources/Java2SwiftLib/JavaTranslator.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,20 +429,21 @@ extension JavaTranslator {
429429
}
430430

431431
let throwsStr = javaMethod.throwsCheckedException ? "throws" : ""
432-
432+
let swiftMethodName = javaMethod.getName().escapedSwiftName
433433
let methodAttribute: AttributeSyntax = javaMethod.isStatic ? "@JavaStaticMethod" : "@JavaMethod";
434434
return """
435435
\(methodAttribute)
436-
public func \(raw: javaMethod.getName())\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
436+
public func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
437437
"""
438438
}
439439

440440
package func translateField(_ javaField: Field) throws -> DeclSyntax {
441441
let typeName = try getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: true)
442442
let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField";
443+
let swiftFieldName = javaField.getName().escapedSwiftName
443444
return """
444445
\(fieldAttribute)
445-
public var \(raw: javaField.getName()): \(raw: typeName)
446+
public var \(raw: swiftFieldName): \(raw: typeName)
446447
"""
447448
}
448449

Sources/Java2SwiftLib/StringExtras.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// SPDX-License-Identifier: Apache-2.0
1212
//
1313
//===----------------------------------------------------------------------===//
14+
import SwiftParser
1415

1516
extension String {
1617
/// Split the Swift type name into parent type + innermost type name.
@@ -24,4 +25,13 @@ extension String {
2425
name: String(suffix(from: index(after: lastDot)))
2526
)
2627
}
28+
29+
/// Escape a name with backticks if it's a Swift keyword.
30+
var escapedSwiftName: String {
31+
if isValidSwiftIdentifier(for: .variableName) {
32+
return self
33+
}
34+
35+
return "`\(self)`"
36+
}
2737
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"classes" : {
3+
"java.net.URI" : "URI",
4+
"java.net.URL" : "URL",
5+
"java.net.URLClassLoader" : "URLClassLoader"
6+
}
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"classes" : {
3+
"java.lang.annotation.Annotation" : "Annotation",
4+
"java.lang.reflect.AccessibleObject" : "AccessibleObject",
5+
"java.lang.reflect.AnnotatedType" : "AnnotatedType",
6+
"java.lang.reflect.Constructor" : "Constructor",
7+
"java.lang.reflect.Executable" : "Executable",
8+
"java.lang.reflect.Field" : "Field",
9+
"java.lang.reflect.GenericArrayType" : "GenericArrayType",
10+
"java.lang.reflect.GenericDeclaration" : "GenericDeclaration",
11+
"java.lang.reflect.Method" : "Method",
12+
"java.lang.reflect.Parameter" : "Parameter",
13+
"java.lang.reflect.ParameterizedType" : "ParameterizedType",
14+
"java.lang.reflect.Type" : "Type",
15+
"java.lang.reflect.TypeVariable" : "TypeVariable",
16+
"java.lang.reflect.WildcardType" : "WildcardType"
17+
}
18+
}

Sources/JavaKitVM/JavaVirtualMachine.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
4545
private init(
4646
classPath: [String] = [],
4747
vmOptions: [String] = [],
48-
ignoreUnrecognized: Bool = true
48+
ignoreUnrecognized: Bool = false
4949
) throws {
5050
var jvm: JavaVMPointer? = nil
5151
var environment: UnsafeMutableRawPointer? = nil
@@ -176,7 +176,7 @@ extension JavaVirtualMachine {
176176
public static func shared(
177177
classPath: [String] = [],
178178
vmOptions: [String] = [],
179-
ignoreUnrecognized: Bool = true
179+
ignoreUnrecognized: Bool = false
180180
) throws -> JavaVirtualMachine {
181181
try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in
182182
// If we already have a JavaVirtualMachine instance, return it.

0 commit comments

Comments
 (0)