Skip to content

Commit b1aeb73

Browse files
committed
Java2Swift: Separate "reference type" from "value type" mappings
While in general we want to map Java classes to Swift value types, there are places where we need to work with the class type, such as generic parameters. Identify those places where it is okay to bridge to a value type (e.g., parameters, results, field types) and do so. Elsewhere, use the Swift type that directly wraps the Java class. For example, when dealing with superclasses or with generic arguments to Java generic classes. As part of this, provide a class mapping for java.lang.String (as JavaString) and Java arrays (as the non-generic JavaArray) so we have a way to refer to such types in positions that cannot use the value type. Test this out with a few APIs that need it. Fixes issue #128.
1 parent 5a61886 commit b1aeb73

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)