Skip to content

Java2Swift: Separate "reference type" from "value type" mappings #131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Sources/Java2Swift/JavaToSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ struct JavaToSwift: ParsableCommand {


let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)"
translator.translatedClasses[javaClassName] = (swiftName, nil, true)
translator.translatedClasses[javaClassName] = (swiftName, nil)
return nestedClass
}

Expand All @@ -279,7 +279,8 @@ struct JavaToSwift: ParsableCommand {

"""

let swiftFileName = try! translator.getSwiftTypeName(javaClass).swiftName.replacing(".", with: "+") + ".swift"
let swiftFileName = try! translator.getSwiftTypeName(javaClass, preferValueTypes: false)
.swiftName.replacing(".", with: "+") + ".swift"
try writeContents(
swiftFileText,
to: swiftFileName,
Expand Down
37 changes: 30 additions & 7 deletions Sources/Java2SwiftLib/JavaClassTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ struct JavaClassTranslator {
let fullName = javaClass.getName()
self.javaClass = javaClass
self.translator = translator
self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName(fullName, escapeMemberNames: false)
self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName(
fullName,
preferValueTypes: false,
escapeMemberNames: false
)

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

do {
let typeName = try translator.getSwiftTypeNameAsString(javaType, outerOptional: .nonoptional)
let typeName = try translator.getSwiftTypeNameAsString(
javaType,
preferValueTypes: false,
outerOptional: .nonoptional
)
return "\(typeName)"
} catch {
translator.logUntranslated("Unable to translate '\(fullName)' interface '\(javaType.getTypeName())': \(error)")
Expand Down Expand Up @@ -314,7 +322,10 @@ extension JavaClassTranslator {

/// Render any nested classes that will not be rendered separately.
func renderNestedClasses() -> [DeclSyntax] {
return nestedClasses.compactMap { clazz in
return nestedClasses
.sorted {
$0.getName() < $1.getName()
}.compactMap { clazz in
do {
return try translator.translateClass(clazz)
} catch {
Expand Down Expand Up @@ -456,7 +467,11 @@ extension JavaClassTranslator {

// Map the result type.
let resultTypeStr: String
let resultType = try translator.getSwiftTypeNameAsString(javaMethod.getGenericReturnType()!, outerOptional: .implicitlyUnwrappedOptional)
let resultType = try translator.getSwiftTypeNameAsString(
javaMethod.getGenericReturnType()!,
preferValueTypes: true,
outerOptional: .implicitlyUnwrappedOptional
)
if resultType != "Void" {
resultTypeStr = " -> \(resultType)"
} else {
Expand All @@ -477,7 +492,11 @@ extension JavaClassTranslator {
/// Render a single Java field into the corresponding Swift property, or
/// throw an error if that is not possible for any reason.
package func renderField(_ javaField: Field) throws -> DeclSyntax {
let typeName = try translator.getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: .implicitlyUnwrappedOptional)
let typeName = try translator.getSwiftTypeNameAsString(
javaField.getGenericType()!,
preferValueTypes: true,
outerOptional: .implicitlyUnwrappedOptional
)
let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField";
let swiftFieldName = javaField.getName().escapedSwiftName
return """
Expand Down Expand Up @@ -544,7 +563,11 @@ extension JavaClassTranslator {
return try parameters.compactMap { javaParameter in
guard let javaParameter else { return nil }

let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: .optional)
let typeName = try translator.getSwiftTypeNameAsString(
javaParameter.getParameterizedType()!,
preferValueTypes: true,
outerOptional: .optional
)
let paramName = javaParameter.getName()
return "_ \(raw: paramName): \(raw: typeName)"
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/Java2SwiftLib/JavaTranslator+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ extension JavaTranslator {
for (javaClassName, swiftName) in config.classes {
translatedClasses[javaClassName] = (
swiftType: swiftName,
swiftModule: swiftModule,
isOptional: true
swiftModule: swiftModule
)
}
}
Expand Down
115 changes: 79 additions & 36 deletions Sources/Java2SwiftLib/JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,20 @@ package class JavaTranslator {
let environment: JNIEnvironment
let format: BasicFormat

/// A mapping from the canonical name of Java classes to the corresponding
/// Swift type name, its Swift module, and whether we need to be working
/// with optionals.
package var translatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] =
defaultTranslatedClasses
/// A mapping from the name of each known Java class to the corresponding
/// Swift type name and its Swift module.
package var translatedClasses: [String: (swiftType: String, swiftModule: String?)] = [:]

/// A mapping from the name of each known Java class with the Swift value type
/// (and its module) to which it is mapped.
///
/// The Java classes here can also be part of `translatedClasses`. The entry in
/// `translatedClasses` should map to a representation of the Java class (i.e.,
/// an AnyJavaObject-conforming type) whereas the entry here should map to
/// a value type.
package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [
"java.lang.String": ("String", "JavaKit"),
]

/// The set of Swift modules that need to be imported to make the generated
/// code compile. Use `getImportDecls()` to format this into a list of
Expand Down Expand Up @@ -80,13 +89,6 @@ extension JavaTranslator {
"JavaKit",
"JavaRuntime",
]

/// The default set of translated classes that do not come from JavaKit
/// itself. This should only be used to refer to types that are built-in to
/// JavaKit and therefore aren't captured in any configuration file.
package static let defaultTranslatedClasses: [String: (swiftType: String, swiftModule: String?, isOptional: Bool)] = [
"java.lang.String": ("String", "JavaKit", false),
]
}

// MARK: Import translation
Expand All @@ -104,13 +106,21 @@ extension JavaTranslator {
// MARK: Type translation
extension JavaTranslator {
/// Turn a Java type into a string.
func getSwiftTypeNameAsString(_ javaType: Type, outerOptional: OptionalKind) throws -> String {
func getSwiftTypeNameAsString(
_ javaType: Type,
preferValueTypes: Bool,
outerOptional: OptionalKind
) throws -> String {
// Replace type variables with their bounds.
if let typeVariable = javaType.as(TypeVariable<GenericDeclaration>.self),
typeVariable.getBounds().count == 1,
let bound = typeVariable.getBounds()[0]
{
return try getSwiftTypeNameAsString(bound, outerOptional: outerOptional)
return try getSwiftTypeNameAsString(
bound,
preferValueTypes: preferValueTypes,
outerOptional: outerOptional
)
}

// Replace wildcards with their upper bound.
Expand All @@ -119,21 +129,42 @@ extension JavaTranslator {
let bound = wildcardType.getUpperBounds()[0]
{
// Replace a wildcard type with its first bound.
return try getSwiftTypeNameAsString(bound, outerOptional: outerOptional)
return try getSwiftTypeNameAsString(
bound,
preferValueTypes: preferValueTypes,
outerOptional: outerOptional
)
}

// Handle array types by recursing into the component type.
if let arrayType = javaType.as(GenericArrayType.self) {
let elementType = try getSwiftTypeNameAsString(arrayType.getGenericComponentType()!, outerOptional: .optional)
return "[\(elementType)]"
if preferValueTypes {
let elementType = try getSwiftTypeNameAsString(
arrayType.getGenericComponentType()!,
preferValueTypes: preferValueTypes,
outerOptional: .optional
)
return "[\(elementType)]"
}

let (swiftName, _) = try getSwiftTypeName(
JavaClass<JavaArray>().as(JavaClass<JavaObject>.self)!,
preferValueTypes: false
)

return outerOptional.adjustTypeName(swiftName)
}

// Handle parameterized types by recursing on the raw type and the type
// arguments.
if let parameterizedType = javaType.as(ParameterizedType.self),
let rawJavaType = parameterizedType.getRawType()
{
var rawSwiftType = try getSwiftTypeNameAsString(rawJavaType, outerOptional: outerOptional)
var rawSwiftType = try getSwiftTypeNameAsString(
rawJavaType,
preferValueTypes: false,
outerOptional: outerOptional
)

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

let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in
try typeArg.map { typeArg in
try getSwiftTypeNameAsString(typeArg, outerOptional: .nonoptional)
try getSwiftTypeNameAsString(typeArg, preferValueTypes: false, outerOptional: .nonoptional)
}
}

Expand All @@ -157,38 +188,50 @@ extension JavaTranslator {
throw TranslationError.unhandledJavaType(javaType)
}

let (swiftName, isOptional) = try getSwiftTypeName(javaClass)
let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes)
var resultString = swiftName
if isOptional {
switch outerOptional {
case .implicitlyUnwrappedOptional:
resultString += "!"
case .optional:
resultString += "?"
case .nonoptional:
break
}
resultString = outerOptional.adjustTypeName(resultString)
}
return resultString
}

/// Translate a Java class into its corresponding Swift type name.
package func getSwiftTypeName(_ javaClass: JavaClass<JavaObject>) throws -> (swiftName: String, isOptional: Bool) {
package func getSwiftTypeName(
_ javaClass: JavaClass<JavaObject>,
preferValueTypes: Bool
) throws -> (swiftName: String, isOptional: Bool) {
let javaType = try JavaType(javaTypeName: javaClass.getName())
let isSwiftOptional = javaType.isSwiftOptional
return (
try javaType.swiftTypeName { javaClassName in
try self.getSwiftTypeNameFromJavaClassName(javaClassName)
},
isSwiftOptional
)
let isSwiftOptional = javaType.isSwiftOptional(stringIsValueType: preferValueTypes)

let swiftTypeName: String
if !preferValueTypes, case .array(_) = javaType {
swiftTypeName = try self.getSwiftTypeNameFromJavaClassName("java.lang.reflect.Array", preferValueTypes: false)
} else {
swiftTypeName = try javaType.swiftTypeName { javaClassName in
try self.getSwiftTypeNameFromJavaClassName(javaClassName, preferValueTypes: preferValueTypes)
}
}

return (swiftTypeName, isSwiftOptional)
}

/// Map a Java class name to its corresponding Swift type.
func getSwiftTypeNameFromJavaClassName(
_ name: String,
preferValueTypes: Bool,
escapeMemberNames: Bool = true
) throws -> String {
// If we want a value type, look for one.
if preferValueTypes, let translatedValueType = translatedToValueTypes[name] {
// Note that we need to import this Swift module.
if translatedValueType.swiftModule != swiftModuleName {
importedSwiftModules.insert(translatedValueType.swiftModule)
}

return translatedValueType.swiftType
}

if let translated = translatedClasses[name] {
// Note that we need to import this Swift module.
if let swiftModule = translated.swiftModule, swiftModule != swiftModuleName {
Expand Down
9 changes: 9 additions & 0 deletions Sources/Java2SwiftLib/OptionalKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ enum OptionalKind {

/// The value uses an implicitly-unwrapped optional.
case implicitlyUnwrappedOptional

/// Adjust the given type name string based on the optionality of this type.
func adjustTypeName(_ string: String) -> String {
switch self {
case .implicitlyUnwrappedOptional: return string + "!"
case .optional: return string + "?"
case .nonoptional: return string
}
}
}
2 changes: 2 additions & 0 deletions Sources/JavaKit/Java2Swift.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"classes" : {
"java.lang.reflect.Array" : "JavaArray",
"java.lang.Boolean" : "JavaBoolean",
"java.lang.Byte" : "JavaByte",
"java.lang.Character" : "JavaCharacter",
Expand All @@ -14,6 +15,7 @@
"java.lang.Object" : "JavaObject",
"java.lang.RuntimeException" : "RuntimeException",
"java.lang.Short" : "JavaShort",
"java.lang.String" : "JavaString",
"java.lang.Throwable" : "Throwable",
"java.lang.Void" : "JavaVoid"
}
Expand Down
Loading