Skip to content

Commit b8ee12c

Browse files
authored
Merge pull request #73 from DougGregor/java2swift-split
Split the Java2Swift tool out into a library target and add a test harness
2 parents f7b4587 + a04219c commit b8ee12c

File tree

8 files changed

+142
-21
lines changed

8 files changed

+142
-21
lines changed

Package.swift

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ let package = Package(
8383

8484
.executable(
8585
name: "Java2Swift",
86-
targets: ["Java2Swift"]
86+
targets: ["Java2SwiftTool"]
8787
),
8888

8989
// ==== jextract-swift (extract Java accessors from Swift interface files)
@@ -230,13 +230,12 @@ let package = Package(
230230
]
231231
),
232232

233-
.executableTarget(
234-
name: "Java2Swift",
233+
.target(
234+
name: "Java2SwiftLib",
235235
dependencies: [
236236
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
237237
.product(name: "SwiftSyntax", package: "swift-syntax"),
238238
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
239-
.product(name: "ArgumentParser", package: "swift-argument-parser"),
240239
"JavaKit",
241240
"JavaKitJar",
242241
"JavaKitReflection",
@@ -250,6 +249,26 @@ let package = Package(
250249
]
251250
),
252251

252+
.executableTarget(
253+
name: "Java2SwiftTool",
254+
dependencies: [
255+
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
256+
.product(name: "SwiftSyntax", package: "swift-syntax"),
257+
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
258+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
259+
"JavaKit",
260+
"JavaKitJar",
261+
"JavaKitNetwork",
262+
"JavaKitVM",
263+
"Java2SwiftLib",
264+
],
265+
266+
swiftSettings: [
267+
.swiftLanguageMode(.v5),
268+
.enableUpcomingFeature("BareSlashRegexLiterals")
269+
]
270+
),
271+
253272
.target(
254273
name: "JExtractSwift",
255274
dependencies: [
@@ -301,7 +320,15 @@ let package = Package(
301320
.swiftLanguageMode(.v5)
302321
]
303322
),
304-
323+
324+
.testTarget(
325+
name: "Java2SwiftTests",
326+
dependencies: ["Java2SwiftLib"],
327+
swiftSettings: [
328+
.swiftLanguageMode(.v5)
329+
]
330+
),
331+
305332
.testTarget(
306333
name: "JExtractSwiftTests",
307334
dependencies: [

Sources/Java2Swift/JavaTranslator+TranslationManifest.swift renamed to Sources/Java2SwiftLib/JavaTranslator+TranslationManifest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Foundation
1717
extension JavaTranslator {
1818
/// Load the manifest file with the given name to populate the known set of
1919
/// translated Java classes.
20-
func loadTranslationManifest(from url: URL) throws {
20+
package func loadTranslationManifest(from url: URL) throws {
2121
let contents = try Data(contentsOf: url)
2222
let manifest = try JSONDecoder().decode(TranslationManifest.self, from: contents)
2323
for (javaClassName, swiftName) in manifest.translatedClasses {
@@ -30,7 +30,7 @@ extension JavaTranslator {
3030
}
3131

3232
/// Emit the translation manifest for this source file
33-
func encodeTranslationManifest() throws -> String {
33+
package func encodeTranslationManifest() throws -> String {
3434
let encoder = JSONEncoder()
3535
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
3636
var contents = String(data: try encoder.encode(manifest), encoding: .utf8)!

Sources/Java2Swift/JavaTranslator.swift renamed to Sources/Java2SwiftLib/JavaTranslator.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import SwiftSyntaxBuilder
2121

2222
/// Utility that translates Java classes into Swift source code to access
2323
/// those Java classes.
24-
class JavaTranslator {
24+
package class JavaTranslator {
2525
/// The name of the Swift module that we are translating into.
2626
let swiftModuleName: String
2727

@@ -36,18 +36,18 @@ class JavaTranslator {
3636
/// which is absolutely not scalable. We need a better way to be able to
3737
/// discover already-translated Java classes to get their corresponding
3838
/// Swift types and modules.
39-
var translatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] =
39+
package var translatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] =
4040
defaultTranslatedClasses
4141

4242
/// The set of Swift modules that need to be imported to make the generated
4343
/// code compile. Use `getImportDecls()` to format this into a list of
4444
/// import declarations.
45-
var importedSwiftModules: Set<String> = JavaTranslator.defaultImportedSwiftModules
45+
package var importedSwiftModules: Set<String> = JavaTranslator.defaultImportedSwiftModules
4646

4747
/// The manifest for the module being translated.
48-
var manifest: TranslationManifest
48+
package var manifest: TranslationManifest
4949

50-
init(
50+
package init(
5151
swiftModuleName: String,
5252
environment: JNIEnvironment,
5353
format: BasicFormat = JavaTranslator.defaultFormat
@@ -59,7 +59,7 @@ class JavaTranslator {
5959
}
6060

6161
/// Clear out any per-file state when we want to start a new file.
62-
func startNewFile() {
62+
package func startNewFile() {
6363
importedSwiftModules = Self.defaultImportedSwiftModules
6464
}
6565

@@ -83,7 +83,7 @@ extension JavaTranslator {
8383
/// The default set of translated classes that do not come from JavaKit
8484
/// itself. This should only be used to refer to types that are built-in to
8585
/// JavaKit and therefore aren't captured in any manifest.
86-
private static let defaultTranslatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] = [
86+
package static let defaultTranslatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] = [
8787
"java.lang.Class": ("JavaClass", "JavaKit", true),
8888
"java.lang.String": ("String", "JavaKit", false),
8989
]
@@ -92,7 +92,7 @@ extension JavaTranslator {
9292
// MARK: Import translation
9393
extension JavaTranslator {
9494
/// Retrieve the import declarations.
95-
func getImportDecls() -> [DeclSyntax] {
95+
package func getImportDecls() -> [DeclSyntax] {
9696
importedSwiftModules.filter {
9797
$0 != swiftModuleName
9898
}.sorted().map {
@@ -166,7 +166,7 @@ extension JavaTranslator {
166166
}
167167

168168
/// Translate a Java class into its corresponding Swift type name.
169-
func getSwiftTypeName(_ javaClass: JavaClass<JavaObject>) throws -> (swiftName: String, isOptional: Bool) {
169+
package func getSwiftTypeName(_ javaClass: JavaClass<JavaObject>) throws -> (swiftName: String, isOptional: Bool) {
170170
let javaType = try JavaType(javaTypeName: javaClass.getName())
171171
let isSwiftOptional = javaType.isSwiftOptional
172172
return (
@@ -195,7 +195,7 @@ extension JavaTranslator {
195195
/// Translates the given Java class into the corresponding Swift type. This
196196
/// can produce multiple declarations, such as a separate extension of
197197
/// JavaClass to house static methods.
198-
func translateClass(_ javaClass: JavaClass<JavaObject>) -> [DeclSyntax] {
198+
package func translateClass(_ javaClass: JavaClass<JavaObject>) -> [DeclSyntax] {
199199
let fullName = javaClass.getCanonicalName()
200200
let swiftTypeName = try! getSwiftTypeNameFromJavaClassName(fullName)
201201

@@ -409,7 +409,7 @@ extension JavaTranslator {
409409
// MARK: Method and constructor translation
410410
extension JavaTranslator {
411411
/// Translates the given Java constructor into a Swift declaration.
412-
func translateConstructor(_ javaConstructor: Constructor<some AnyJavaObject>) throws -> DeclSyntax {
412+
package func translateConstructor(_ javaConstructor: Constructor<some AnyJavaObject>) throws -> DeclSyntax {
413413
let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment"]
414414
let parametersStr = parameters.map { $0.description }.joined(separator: ", ")
415415
let throwsStr = javaConstructor.throwsCheckedException ? "throws" : ""
@@ -421,7 +421,7 @@ extension JavaTranslator {
421421
}
422422

423423
/// Translates the given Java method into a Swift declaration.
424-
func translateMethod(
424+
package func translateMethod(
425425
_ javaMethod: Method,
426426
genericParameterClause: String = "",
427427
whereClause: String = ""
@@ -449,7 +449,7 @@ extension JavaTranslator {
449449
"""
450450
}
451451

452-
func translateField(_ javaField: Field) throws -> DeclSyntax {
452+
package func translateField(_ javaField: Field) throws -> DeclSyntax {
453453
let typeName = try getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: true)
454454
let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField";
455455
return """

Sources/Java2Swift/TranslationManifest.swift renamed to Sources/Java2SwiftLib/TranslationManifest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
/// Manifest describing the a Swift module containing translations of
1616
/// Java classes into Swift types.
17-
struct TranslationManifest: Codable {
17+
package struct TranslationManifest: Codable {
1818
/// The Swift module name.
1919
var swiftModule: String
2020

Sources/Java2Swift/JavaToSwift.swift renamed to Sources/Java2SwiftTool/JavaToSwift.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import ArgumentParser
1616
import Foundation
17+
import Java2SwiftLib
1718
import JavaKit
1819
import JavaKitJar
1920
import JavaKitNetwork
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import JavaKit
16+
import Java2SwiftLib
17+
import JavaKitVM
18+
import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
19+
20+
/// Handy reference to the JVM abstraction.
21+
var jvm: JavaVirtualMachine {
22+
get throws {
23+
try .shared()
24+
}
25+
}
26+
27+
class Java2SwiftTests: XCTestCase {
28+
func testJavaLangObjectMapping() async throws {
29+
try assertTranslatedClass(
30+
JavaObject.self,
31+
swiftTypeName: "MyJavaObject",
32+
expectedChunks: [
33+
"import JavaKit",
34+
"""
35+
@JavaClass("java.lang.Object")
36+
public struct MyJavaObject {
37+
""",
38+
"""
39+
@JavaMethod
40+
public func toString() -> String
41+
""",
42+
"""
43+
@JavaMethod
44+
public func wait() throws
45+
"""
46+
]
47+
)
48+
}
49+
}
50+
51+
/// Translate a Java class and assert that the translated output contains
52+
/// each of the expected "chunks" of text.
53+
func assertTranslatedClass<JavaClassType: AnyJavaObject>(
54+
_ javaType: JavaClassType.Type,
55+
swiftTypeName: String,
56+
translatedClasses: [
57+
String: (swiftType: String, swiftModule: String?, isOptional: Bool)
58+
] = JavaTranslator.defaultTranslatedClasses,
59+
expectedChunks: [String],
60+
file: StaticString = #filePath,
61+
line: UInt = #line
62+
) throws {
63+
let environment = try jvm.environment()
64+
let translator = JavaTranslator(
65+
swiftModuleName: "SwiftModule",
66+
environment: environment
67+
)
68+
69+
translator.translatedClasses = translatedClasses
70+
translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil, true)
71+
72+
translator.startNewFile()
73+
let translatedDecls = translator.translateClass(
74+
try JavaClass<JavaObject>(
75+
javaThis: javaType.getJNIClass(in: environment),
76+
environment: environment)
77+
)
78+
let importDecls = translator.getImportDecls()
79+
80+
let swiftFileText = """
81+
// Auto-generated by Java-to-Swift wrapper generator.
82+
\(importDecls.map { $0.description }.joined())
83+
\(translatedDecls.map { $0.description }.joined(separator: "\n"))
84+
"""
85+
86+
for expectedChunk in expectedChunks {
87+
if swiftFileText.contains(expectedChunk) {
88+
continue
89+
}
90+
91+
XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line)
92+
}
93+
}

0 commit comments

Comments
 (0)