Skip to content

Commit 83c7b0f

Browse files
authored
Merge pull request #131 from DougGregor/java2swift-reference-vs-value-types
Java2Swift: Separate "reference type" from "value type" mappings
2 parents 3df2daa + b1aeb73 commit 83c7b0f

File tree

15 files changed

+689
-192
lines changed

15 files changed

+689
-192
lines changed

Sources/Java2Swift/JavaToSwift.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ struct JavaToSwift: ParsableCommand {
252252

253253

254254
let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)"
255-
translator.translatedClasses[javaClassName] = (swiftName, nil, true)
255+
translator.translatedClasses[javaClassName] = (swiftName, nil)
256256
return nestedClass
257257
}
258258

@@ -279,7 +279,8 @@ struct JavaToSwift: ParsableCommand {
279279
280280
"""
281281

282-
let swiftFileName = try! translator.getSwiftTypeName(javaClass).swiftName.replacing(".", with: "+") + ".swift"
282+
let swiftFileName = try! translator.getSwiftTypeName(javaClass, preferValueTypes: false)
283+
.swiftName.replacing(".", with: "+") + ".swift"
283284
try writeContents(
284285
swiftFileText,
285286
to: swiftFileName,

Sources/Java2SwiftLib/JavaClassTranslator.swift

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,11 @@ struct JavaClassTranslator {
103103
let fullName = javaClass.getName()
104104
self.javaClass = javaClass
105105
self.translator = translator
106-
self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName(fullName, escapeMemberNames: false)
106+
self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName(
107+
fullName,
108+
preferValueTypes: false,
109+
escapeMemberNames: false
110+
)
107111

108112
// Type parameters.
109113
self.javaTypeParameters = javaClass.getTypeParameters().compactMap { $0 }
@@ -112,7 +116,7 @@ struct JavaClassTranslator {
112116
// Superclass.
113117
if !javaClass.isInterface(), let javaSuperclass = javaClass.getSuperclass() {
114118
do {
115-
self.swiftSuperclass = try translator.getSwiftTypeName(javaSuperclass).swiftName
119+
self.swiftSuperclass = try translator.getSwiftTypeName(javaSuperclass, preferValueTypes: false).swiftName
116120
} catch {
117121
translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)")
118122
self.swiftSuperclass = nil
@@ -128,7 +132,11 @@ struct JavaClassTranslator {
128132
}
129133

130134
do {
131-
let typeName = try translator.getSwiftTypeNameAsString(javaType, outerOptional: .nonoptional)
135+
let typeName = try translator.getSwiftTypeNameAsString(
136+
javaType,
137+
preferValueTypes: false,
138+
outerOptional: .nonoptional
139+
)
132140
return "\(typeName)"
133141
} catch {
134142
translator.logUntranslated("Unable to translate '\(fullName)' interface '\(javaType.getTypeName())': \(error)")
@@ -314,7 +322,10 @@ extension JavaClassTranslator {
314322

315323
/// Render any nested classes that will not be rendered separately.
316324
func renderNestedClasses() -> [DeclSyntax] {
317-
return nestedClasses.compactMap { clazz in
325+
return nestedClasses
326+
.sorted {
327+
$0.getName() < $1.getName()
328+
}.compactMap { clazz in
318329
do {
319330
return try translator.translateClass(clazz)
320331
} catch {
@@ -456,7 +467,11 @@ extension JavaClassTranslator {
456467

457468
// Map the result type.
458469
let resultTypeStr: String
459-
let resultType = try translator.getSwiftTypeNameAsString(javaMethod.getGenericReturnType()!, outerOptional: .implicitlyUnwrappedOptional)
470+
let resultType = try translator.getSwiftTypeNameAsString(
471+
javaMethod.getGenericReturnType()!,
472+
preferValueTypes: true,
473+
outerOptional: .implicitlyUnwrappedOptional
474+
)
460475
if resultType != "Void" {
461476
resultTypeStr = " -> \(resultType)"
462477
} else {
@@ -477,7 +492,11 @@ extension JavaClassTranslator {
477492
/// Render a single Java field into the corresponding Swift property, or
478493
/// throw an error if that is not possible for any reason.
479494
package func renderField(_ javaField: Field) throws -> DeclSyntax {
480-
let typeName = try translator.getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: .implicitlyUnwrappedOptional)
495+
let typeName = try translator.getSwiftTypeNameAsString(
496+
javaField.getGenericType()!,
497+
preferValueTypes: true,
498+
outerOptional: .implicitlyUnwrappedOptional
499+
)
481500
let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField";
482501
let swiftFieldName = javaField.getName().escapedSwiftName
483502
return """
@@ -544,7 +563,11 @@ extension JavaClassTranslator {
544563
return try parameters.compactMap { javaParameter in
545564
guard let javaParameter else { return nil }
546565

547-
let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: .optional)
566+
let typeName = try translator.getSwiftTypeNameAsString(
567+
javaParameter.getParameterizedType()!,
568+
preferValueTypes: true,
569+
outerOptional: .optional
570+
)
548571
let paramName = javaParameter.getName()
549572
return "_ \(raw: paramName): \(raw: typeName)"
550573
}

Sources/Java2SwiftLib/JavaTranslator+Configuration.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ extension JavaTranslator {
2727
for (javaClassName, swiftName) in config.classes {
2828
translatedClasses[javaClassName] = (
2929
swiftType: swiftName,
30-
swiftModule: swiftModule,
31-
isOptional: true
30+
swiftModule: swiftModule
3231
)
3332
}
3433
}

Sources/Java2SwiftLib/JavaTranslator.swift

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,20 @@ package class JavaTranslator {
2828
let environment: JNIEnvironment
2929
let format: BasicFormat
3030

31-
/// A mapping from the canonical name of Java classes to the corresponding
32-
/// Swift type name, its Swift module, and whether we need to be working
33-
/// with optionals.
34-
package var translatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] =
35-
defaultTranslatedClasses
31+
/// A mapping from the name of each known Java class to the corresponding
32+
/// Swift type name and its Swift module.
33+
package var translatedClasses: [String: (swiftType: String, swiftModule: String?)] = [:]
34+
35+
/// A mapping from the name of each known Java class with the Swift value type
36+
/// (and its module) to which it is mapped.
37+
///
38+
/// The Java classes here can also be part of `translatedClasses`. The entry in
39+
/// `translatedClasses` should map to a representation of the Java class (i.e.,
40+
/// an AnyJavaObject-conforming type) whereas the entry here should map to
41+
/// a value type.
42+
package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [
43+
"java.lang.String": ("String", "JavaKit"),
44+
]
3645

3746
/// The set of Swift modules that need to be imported to make the generated
3847
/// code compile. Use `getImportDecls()` to format this into a list of
@@ -80,13 +89,6 @@ extension JavaTranslator {
8089
"JavaKit",
8190
"JavaRuntime",
8291
]
83-
84-
/// The default set of translated classes that do not come from JavaKit
85-
/// itself. This should only be used to refer to types that are built-in to
86-
/// JavaKit and therefore aren't captured in any configuration file.
87-
package static let defaultTranslatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] = [
88-
"java.lang.String": ("String", "JavaKit", false),
89-
]
9092
}
9193

9294
// MARK: Import translation
@@ -104,13 +106,21 @@ extension JavaTranslator {
104106
// MARK: Type translation
105107
extension JavaTranslator {
106108
/// Turn a Java type into a string.
107-
func getSwiftTypeNameAsString(_ javaType: Type, outerOptional: OptionalKind) throws -> String {
109+
func getSwiftTypeNameAsString(
110+
_ javaType: Type,
111+
preferValueTypes: Bool,
112+
outerOptional: OptionalKind
113+
) throws -> String {
108114
// Replace type variables with their bounds.
109115
if let typeVariable = javaType.as(TypeVariable<GenericDeclaration>.self),
110116
typeVariable.getBounds().count == 1,
111117
let bound = typeVariable.getBounds()[0]
112118
{
113-
return try getSwiftTypeNameAsString(bound, outerOptional: outerOptional)
119+
return try getSwiftTypeNameAsString(
120+
bound,
121+
preferValueTypes: preferValueTypes,
122+
outerOptional: outerOptional
123+
)
114124
}
115125

116126
// Replace wildcards with their upper bound.
@@ -119,21 +129,42 @@ extension JavaTranslator {
119129
let bound = wildcardType.getUpperBounds()[0]
120130
{
121131
// Replace a wildcard type with its first bound.
122-
return try getSwiftTypeNameAsString(bound, outerOptional: outerOptional)
132+
return try getSwiftTypeNameAsString(
133+
bound,
134+
preferValueTypes: preferValueTypes,
135+
outerOptional: outerOptional
136+
)
123137
}
124138

125139
// Handle array types by recursing into the component type.
126140
if let arrayType = javaType.as(GenericArrayType.self) {
127-
let elementType = try getSwiftTypeNameAsString(arrayType.getGenericComponentType()!, outerOptional: .optional)
128-
return "[\(elementType)]"
141+
if preferValueTypes {
142+
let elementType = try getSwiftTypeNameAsString(
143+
arrayType.getGenericComponentType()!,
144+
preferValueTypes: preferValueTypes,
145+
outerOptional: .optional
146+
)
147+
return "[\(elementType)]"
148+
}
149+
150+
let (swiftName, _) = try getSwiftTypeName(
151+
JavaClass<JavaArray>().as(JavaClass<JavaObject>.self)!,
152+
preferValueTypes: false
153+
)
154+
155+
return outerOptional.adjustTypeName(swiftName)
129156
}
130157

131158
// Handle parameterized types by recursing on the raw type and the type
132159
// arguments.
133160
if let parameterizedType = javaType.as(ParameterizedType.self),
134161
let rawJavaType = parameterizedType.getRawType()
135162
{
136-
var rawSwiftType = try getSwiftTypeNameAsString(rawJavaType, outerOptional: outerOptional)
163+
var rawSwiftType = try getSwiftTypeNameAsString(
164+
rawJavaType,
165+
preferValueTypes: false,
166+
outerOptional: outerOptional
167+
)
137168

138169
let optionalSuffix: String
139170
if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" {
@@ -145,7 +176,7 @@ extension JavaTranslator {
145176

146177
let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in
147178
try typeArg.map { typeArg in
148-
try getSwiftTypeNameAsString(typeArg, outerOptional: .nonoptional)
179+
try getSwiftTypeNameAsString(typeArg, preferValueTypes: false, outerOptional: .nonoptional)
149180
}
150181
}
151182

@@ -157,38 +188,50 @@ extension JavaTranslator {
157188
throw TranslationError.unhandledJavaType(javaType)
158189
}
159190

160-
let (swiftName, isOptional) = try getSwiftTypeName(javaClass)
191+
let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes)
161192
var resultString = swiftName
162193
if isOptional {
163-
switch outerOptional {
164-
case .implicitlyUnwrappedOptional:
165-
resultString += "!"
166-
case .optional:
167-
resultString += "?"
168-
case .nonoptional:
169-
break
170-
}
194+
resultString = outerOptional.adjustTypeName(resultString)
171195
}
172196
return resultString
173197
}
174198

175199
/// Translate a Java class into its corresponding Swift type name.
176-
package func getSwiftTypeName(_ javaClass: JavaClass<JavaObject>) throws -> (swiftName: String, isOptional: Bool) {
200+
package func getSwiftTypeName(
201+
_ javaClass: JavaClass<JavaObject>,
202+
preferValueTypes: Bool
203+
) throws -> (swiftName: String, isOptional: Bool) {
177204
let javaType = try JavaType(javaTypeName: javaClass.getName())
178-
let isSwiftOptional = javaType.isSwiftOptional
179-
return (
180-
try javaType.swiftTypeName { javaClassName in
181-
try self.getSwiftTypeNameFromJavaClassName(javaClassName)
182-
},
183-
isSwiftOptional
184-
)
205+
let isSwiftOptional = javaType.isSwiftOptional(stringIsValueType: preferValueTypes)
206+
207+
let swiftTypeName: String
208+
if !preferValueTypes, case .array(_) = javaType {
209+
swiftTypeName = try self.getSwiftTypeNameFromJavaClassName("java.lang.reflect.Array", preferValueTypes: false)
210+
} else {
211+
swiftTypeName = try javaType.swiftTypeName { javaClassName in
212+
try self.getSwiftTypeNameFromJavaClassName(javaClassName, preferValueTypes: preferValueTypes)
213+
}
214+
}
215+
216+
return (swiftTypeName, isSwiftOptional)
185217
}
186218

187219
/// Map a Java class name to its corresponding Swift type.
188220
func getSwiftTypeNameFromJavaClassName(
189221
_ name: String,
222+
preferValueTypes: Bool,
190223
escapeMemberNames: Bool = true
191224
) throws -> String {
225+
// If we want a value type, look for one.
226+
if preferValueTypes, let translatedValueType = translatedToValueTypes[name] {
227+
// Note that we need to import this Swift module.
228+
if translatedValueType.swiftModule != swiftModuleName {
229+
importedSwiftModules.insert(translatedValueType.swiftModule)
230+
}
231+
232+
return translatedValueType.swiftType
233+
}
234+
192235
if let translated = translatedClasses[name] {
193236
// Note that we need to import this Swift module.
194237
if let swiftModule = translated.swiftModule, swiftModule != swiftModuleName {

Sources/Java2SwiftLib/OptionalKind.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,13 @@ enum OptionalKind {
2222

2323
/// The value uses an implicitly-unwrapped optional.
2424
case implicitlyUnwrappedOptional
25+
26+
/// Adjust the given type name string based on the optionality of this type.
27+
func adjustTypeName(_ string: String) -> String {
28+
switch self {
29+
case .implicitlyUnwrappedOptional: return string + "!"
30+
case .optional: return string + "?"
31+
case .nonoptional: return string
32+
}
33+
}
2534
}

Sources/JavaKit/Java2Swift.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"classes" : {
3+
"java.lang.reflect.Array" : "JavaArray",
34
"java.lang.Boolean" : "JavaBoolean",
45
"java.lang.Byte" : "JavaByte",
56
"java.lang.Character" : "JavaCharacter",
@@ -14,6 +15,7 @@
1415
"java.lang.Object" : "JavaObject",
1516
"java.lang.RuntimeException" : "RuntimeException",
1617
"java.lang.Short" : "JavaShort",
18+
"java.lang.String" : "JavaString",
1719
"java.lang.Throwable" : "Throwable",
1820
"java.lang.Void" : "JavaVoid"
1921
}

0 commit comments

Comments
 (0)