Skip to content

Commit 37bce97

Browse files
authored
Merge pull request #140 from DougGregor/java-classes-to-swift-classes
JavaKit: Macro and translator support for mapping Java classes to Swift classes
2 parents 70fb530 + 0e3fc53 commit 37bce97

29 files changed

+1075
-88
lines changed

Sources/Java2Swift/JavaToSwift.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ struct JavaToSwift: ParsableCommand {
183183
) throws {
184184
let translator = JavaTranslator(
185185
swiftModuleName: moduleName,
186-
environment: environment
186+
environment: environment,
187+
translateAsClass: false
187188
)
188189

189190
// Keep track of all of the Java classes that will have

Sources/Java2SwiftLib/JavaClassTranslator.swift

Lines changed: 238 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ struct JavaClassTranslator {
2626
/// The Java class (or interface) being translated.
2727
let javaClass: JavaClass<JavaObject>
2828

29+
/// Whether to translate this Java class into a Swift class.
30+
///
31+
/// This will be false for Java interfaces.
32+
let translateAsClass: Bool
33+
2934
/// The type parameters to the Java class or interface.
3035
let javaTypeParameters: [TypeVariable<JavaClass<JavaObject>>]
3136

@@ -37,6 +42,10 @@ struct JavaClassTranslator {
3742
/// class.
3843
let swiftTypeName: String
3944

45+
/// The effective Java superclass object, which is the nearest
46+
/// superclass that has been mapped into Swift.
47+
let effectiveJavaSuperclass: JavaClass<JavaObject>?
48+
4049
/// The Swift name of the superclass.
4150
let swiftSuperclass: String?
4251

@@ -103,6 +112,7 @@ struct JavaClassTranslator {
103112
let fullName = javaClass.getName()
104113
self.javaClass = javaClass
105114
self.translator = translator
115+
self.translateAsClass = translator.translateAsClass && !javaClass.isInterface()
106116
self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName(
107117
fullName,
108118
preferValueTypes: false,
@@ -114,14 +124,24 @@ struct JavaClassTranslator {
114124
self.nestedClasses = translator.nestedClasses[fullName] ?? []
115125

116126
// Superclass.
117-
if !javaClass.isInterface(), let javaSuperclass = javaClass.getSuperclass() {
118-
do {
119-
self.swiftSuperclass = try translator.getSwiftTypeName(javaSuperclass, preferValueTypes: false).swiftName
120-
} catch {
121-
translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)")
122-
self.swiftSuperclass = nil
127+
if !javaClass.isInterface() {
128+
var javaSuperclass = javaClass.getSuperclass()
129+
var swiftSuperclass: String? = nil
130+
while let javaSuperclassNonOpt = javaSuperclass {
131+
do {
132+
swiftSuperclass = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName
133+
break
134+
} catch {
135+
translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)")
136+
}
137+
138+
javaSuperclass = javaSuperclassNonOpt.getSuperclass()
123139
}
140+
141+
self.effectiveJavaSuperclass = javaSuperclass
142+
self.swiftSuperclass = swiftSuperclass
124143
} else {
144+
self.effectiveJavaSuperclass = nil
125145
self.swiftSuperclass = nil
126146
}
127147

@@ -161,9 +181,15 @@ struct JavaClassTranslator {
161181
}
162182

163183
// Gather methods.
164-
for method in javaClass.getMethods() {
184+
let methods = translateAsClass
185+
? javaClass.getDeclaredMethods()
186+
: javaClass.getMethods()
187+
for method in methods {
165188
guard let method else { continue }
166189

190+
// Only look at public and protected methods here.
191+
guard method.isPublic || method.isProtected else { continue }
192+
167193
// Skip any methods that are expected to be implemented in Swift. We will
168194
// visit them in the second pass, over the *declared* methods, because
169195
// we want to see non-public methods as well.
@@ -178,6 +204,7 @@ struct JavaClassTranslator {
178204
}
179205

180206
if translator.swiftNativeImplementations.contains(javaClass.getName()) {
207+
// Gather the native methods we're going to implement.
181208
for method in javaClass.getDeclaredMethods() {
182209
guard let method else { continue }
183210

@@ -209,6 +236,12 @@ extension JavaClassTranslator {
209236
return
210237
}
211238

239+
// Don't include inherited fields when translating to a class.
240+
if translateAsClass &&
241+
!field.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) {
242+
return
243+
}
244+
212245
fields.append(field)
213246
}
214247

@@ -283,8 +316,18 @@ extension JavaClassTranslator {
283316
// Collect all of the members of this type.
284317
let members = properties + enumDecls + initializers + instanceMethods
285318

286-
// Compute the "extends" clause for the superclass.
287-
let extends = swiftSuperclass.map { ", extends: \($0).self" } ?? ""
319+
// Compute the "extends" clause for the superclass (of the struct
320+
// formulation) or the inheritance clause (for the class
321+
// formulation).
322+
let extends: String
323+
let inheritanceClause: String
324+
if translateAsClass {
325+
extends = ""
326+
inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? ""
327+
} else {
328+
extends = swiftSuperclass.map { ", extends: \($0).self" } ?? ""
329+
inheritanceClause = ""
330+
}
288331

289332
// Compute the string to capture all of the interfaces.
290333
let interfacesStr: String
@@ -297,10 +340,11 @@ extension JavaClassTranslator {
297340

298341
// Emit the struct declaration describing the java class.
299342
let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass";
343+
let introducer = translateAsClass ? "open class" : "public struct"
300344
var classDecl: DeclSyntax =
301345
"""
302346
@\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr))
303-
public struct \(raw: swiftInnermostTypeName)\(raw: genericParameterClause) {
347+
\(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) {
304348
\(raw: members.map { $0.description }.joined(separator: "\n\n"))
305349
}
306350
"""
@@ -447,9 +491,11 @@ extension JavaClassTranslator {
447491
let parametersStr = parameters.map { $0.description }.joined(separator: ", ")
448492
let throwsStr = javaConstructor.throwsCheckedException ? "throws" : ""
449493
let accessModifier = javaConstructor.isPublic ? "public " : ""
494+
let convenienceModifier = translateAsClass ? "convenience " : ""
495+
let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : ""
450496
return """
451497
@JavaMethod
452-
\(raw: accessModifier)init(\(raw: parametersStr))\(raw: throwsStr)
498+
\(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr)
453499
"""
454500
}
455501

@@ -483,9 +529,14 @@ extension JavaClassTranslator {
483529
let methodAttribute: AttributeSyntax = implementedInSwift
484530
? ""
485531
: javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n";
486-
let accessModifier = implementedInSwift ? "" : "public "
532+
let accessModifier = implementedInSwift ? ""
533+
: (javaMethod.isStatic || !translateAsClass) ? "public "
534+
: "open "
535+
let overrideOpt = (translateAsClass && !javaMethod.isStatic && isOverride(javaMethod))
536+
? "override "
537+
: ""
487538
return """
488-
\(methodAttribute)\(raw: accessModifier)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
539+
\(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
489540
"""
490541
}
491542

@@ -532,20 +583,23 @@ extension JavaClassTranslator {
532583
}
533584
"""
534585

586+
let convenienceModifier = translateAsClass ? "convenience " : ""
535587
let initSyntax: DeclSyntax = """
536-
public init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) {
588+
public \(raw: convenienceModifier)init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) {
537589
let _environment = if let environment {
538590
environment
539591
} else {
540592
try! JavaVirtualMachine.shared().environment()
541593
}
542-
let classObj = try! JavaClass<Self>(environment: _environment)
594+
let classObj = try! JavaClass<\(raw: swiftInnermostTypeName)>(environment: _environment)
543595
switch enumValue {
544596
\(raw: enumConstants.map {
545597
return """
546598
case .\($0.getName()):
547599
if let \($0.getName()) = classObj.\($0.getName()) {
548-
self = \($0.getName())
600+
\(translateAsClass
601+
? "self.init(javaHolder: \($0.getName()).javaHolder)"
602+
: "self = \($0.getName())")
549603
} else {
550604
fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class")
551605
}
@@ -611,3 +665,171 @@ struct MethodCollector {
611665
methods.append(method)
612666
}
613667
}
668+
669+
// MARK: Utility functions
670+
extension JavaClassTranslator {
671+
/// Determine whether this method is an override of another Java
672+
/// method.
673+
func isOverride(_ method: Method) -> Bool {
674+
var currentSuperclass = effectiveJavaSuperclass
675+
while let currentSuperclassNonOpt = currentSuperclass {
676+
// Set the loop up for the next run.
677+
defer {
678+
currentSuperclass = currentSuperclassNonOpt.getSuperclass()
679+
}
680+
681+
do {
682+
// If this class didn't get translated into Swift, skip it.
683+
if translator.translatedClasses[currentSuperclassNonOpt.getName()] == nil {
684+
continue
685+
}
686+
687+
// If this superclass declares a method with the same parameter types,
688+
// we have an override.
689+
guard let overriddenMethod = try currentSuperclassNonOpt
690+
.getDeclaredMethod(method.getName(), method.getParameterTypes()) else {
691+
continue
692+
}
693+
694+
// Ignore non-public, non-protected methods because they would not
695+
// have been render into the Swift superclass.
696+
if !overriddenMethod.isPublic && !overriddenMethod.isProtected {
697+
continue
698+
}
699+
700+
// We know that Java considers this method an override. However, it is
701+
// possible that Swift will not consider it an override, because Java
702+
// has subtyping relations that Swift does not.
703+
if method.getGenericReturnType().isEqualToOrSubtypeOf(overriddenMethod.getGenericReturnType()) {
704+
return true
705+
}
706+
} catch {
707+
}
708+
}
709+
710+
return false
711+
}
712+
}
713+
714+
extension [Type?] {
715+
/// Determine whether the types in the array match the other array.
716+
func allTypesEqual(_ other: [Type?]) -> Bool {
717+
if self.count != other.count {
718+
return false
719+
}
720+
721+
for (selfType, otherType) in zip(self, other) {
722+
if !selfType!.isEqualTo(otherType!) {
723+
return false
724+
}
725+
}
726+
727+
return true
728+
}
729+
}
730+
731+
extension Type {
732+
/// Adjust the given type to use its bounds, mirroring what we do in
733+
/// mapping Java types into Swift.
734+
func adjustToJavaBounds(adjusted: inout Bool) -> Type {
735+
if let typeVariable = self.as(TypeVariable<GenericDeclaration>.self),
736+
typeVariable.getBounds().count == 1,
737+
let bound = typeVariable.getBounds()[0] {
738+
adjusted = true
739+
return bound
740+
}
741+
742+
if let wildcardType = self.as(WildcardType.self),
743+
wildcardType.getUpperBounds().count == 1,
744+
let bound = wildcardType.getUpperBounds()[0] {
745+
adjusted = true
746+
return bound
747+
}
748+
749+
return self
750+
}
751+
752+
/// Determine whether this type is equivalent to or a subtype of the other
753+
/// type.
754+
func isEqualTo(_ other: Type) -> Bool {
755+
// First, adjust types to their bounds, if we need to.
756+
var anyAdjusted: Bool = false
757+
let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted)
758+
let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted)
759+
if anyAdjusted {
760+
return adjustedSelf.isEqualTo(adjustedOther)
761+
}
762+
763+
// If both are classes, check for equivalence.
764+
if let selfClass = self.as(JavaClass<JavaObject>.self),
765+
let otherClass = other.as(JavaClass<JavaObject>.self) {
766+
return selfClass.equals(otherClass.as(JavaObject.self))
767+
}
768+
769+
// If both are arrays, check that their component types are equivalent.
770+
if let selfArray = self.as(GenericArrayType.self),
771+
let otherArray = other.as(GenericArrayType.self) {
772+
return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType())
773+
}
774+
775+
// If both are parameterized types, check their raw type and type
776+
// arguments for equivalence.
777+
if let selfParameterizedType = self.as(ParameterizedType.self),
778+
let otherParameterizedType = other.as(ParameterizedType.self) {
779+
if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) {
780+
return false
781+
}
782+
783+
return selfParameterizedType.getActualTypeArguments()
784+
.allTypesEqual(otherParameterizedType.getActualTypeArguments())
785+
}
786+
787+
// If both are type variables, compare their bounds.
788+
// FIXME: This is a hack.
789+
if let selfTypeVariable = self.as(TypeVariable<GenericDeclaration>.self),
790+
let otherTypeVariable = other.as(TypeVariable<GenericDeclaration>.self) {
791+
return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds())
792+
}
793+
794+
// If both are wildcards, compare their upper and lower bounds.
795+
if let selfWildcard = self.as(WildcardType.self),
796+
let otherWildcard = other.as(WildcardType.self) {
797+
return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds())
798+
&& selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds())
799+
}
800+
801+
return false
802+
}
803+
804+
/// Determine whether this type is equivalent to or a subtype of the
805+
/// other type.
806+
func isEqualToOrSubtypeOf(_ other: Type) -> Bool {
807+
// First, adjust types to their bounds, if we need to.
808+
var anyAdjusted: Bool = false
809+
let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted)
810+
let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted)
811+
if anyAdjusted {
812+
return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther)
813+
}
814+
815+
if isEqualTo(other) {
816+
return true
817+
}
818+
819+
// If both are classes, check for subclassing.
820+
if let selfClass = self.as(JavaClass<JavaObject>.self),
821+
let otherClass = other.as(JavaClass<JavaObject>.self) {
822+
return selfClass.isSubclass(of: otherClass)
823+
}
824+
825+
// Anything object-like is a subclass of java.lang.Object
826+
if let otherClass = other.as(JavaClass<JavaObject>.self),
827+
otherClass.getName() == "java.lang.Object" {
828+
if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) ||
829+
self.is(WildcardType.self) || self.is(TypeVariable<GenericDeclaration>.self) {
830+
return true
831+
}
832+
}
833+
return false
834+
}
835+
}

Sources/Java2SwiftLib/JavaTranslator.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ package class JavaTranslator {
2626
let swiftModuleName: String
2727

2828
let environment: JNIEnvironment
29+
30+
/// Whether to translate Java classes into classes (rather than structs).
31+
let translateAsClass: Bool
32+
2933
let format: BasicFormat
3034

3135
/// A mapping from the name of each known Java class to the corresponding
@@ -61,10 +65,12 @@ package class JavaTranslator {
6165
package init(
6266
swiftModuleName: String,
6367
environment: JNIEnvironment,
68+
translateAsClass: Bool = false,
6469
format: BasicFormat = JavaTranslator.defaultFormat
6570
) {
6671
self.swiftModuleName = swiftModuleName
6772
self.environment = environment
73+
self.translateAsClass = translateAsClass
6874
self.format = format
6975
}
7076

0 commit comments

Comments
 (0)