diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/Java2Swift/JavaToSwift.swift index 80058ccd..86d3cf8d 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/Java2Swift/JavaToSwift.swift @@ -228,12 +228,27 @@ struct JavaToSwift: ParsableCommand { // Note that we will be translating this Java class, so it is a known class. translator.translatedClasses[javaClassName] = (translatedSwiftName, nil, true) + + var classes: [JavaClass?] = javaClass.getClasses() + + // Go through all subclasses to find all of the classes to translate + while let internalClass = classes.popLast() { + if let internalClass { + let (javaName, swiftName) = names(from: internalClass.getName()) + // If we have already been through this class, don't go through it again + guard translator.translatedClasses[javaName] == nil else { continue } + let currentClassName = swiftName + let currentSanitizedClassName = currentClassName.replacing("$", with: ".") + classes.append(contentsOf: internalClass.getClasses()) + translator.translatedClasses[javaName] = (currentSanitizedClassName, nil, true) + } + } } // Translate all of the Java classes into Swift classes. for javaClass in javaClasses { translator.startNewFile() - let swiftClassDecls = translator.translateClass(javaClass) + let swiftClassDecls = try translator.translateClass(javaClass) let importDecls = translator.getImportDecls() let swiftFileText = """ @@ -247,11 +262,32 @@ struct JavaToSwift: ParsableCommand { try writeContents( swiftFileText, to: swiftFileName, - description: "Java class '\(javaClass.getCanonicalName())' translation" + description: "Java class '\(javaClass.getName())' translation" ) } } + private func names(from javaClassNameOpt: String) -> (javaClassName: String, swiftName: String) { + let javaClassName: String + let swiftName: String + if let equalLoc = javaClassNameOpt.firstIndex(of: "=") { + let afterEqual = javaClassNameOpt.index(after: equalLoc) + javaClassName = String(javaClassNameOpt[..) -> [DeclSyntax] { - let fullName = javaClass.getCanonicalName() - let swiftTypeName = try! getSwiftTypeNameFromJavaClassName(fullName) + package func translateClass(_ javaClass: JavaClass) throws -> [DeclSyntax] { + let fullName = javaClass.getName() + let swiftTypeName = try getSwiftTypeNameFromJavaClassName(fullName) + let (swiftParentType, swiftInnermostTypeName) = swiftTypeName.splitSwiftTypeName() + + // If the swift parent type has not been translated, don't try to translate this one + if let swiftParentType, + !translatedClasses.contains(where: { _, value in value.swiftType == swiftParentType }) + { + logUntranslated("Unable to translate '\(fullName)' parent class: \(swiftParentType) not found") + return [] + } // Superclass. let extends: String if !javaClass.isInterface(), let superclass = javaClass.getSuperclass(), - superclass.getCanonicalName() != "java.lang.Object" + superclass.getName() != "java.lang.Object" { do { extends = ", extends: \(try getSwiftTypeName(superclass).swiftName).self" @@ -265,7 +274,7 @@ extension JavaTranslator { ) if !enumConstants.isEmpty { - let enumName = "\(swiftTypeName)Cases" + let enumName = "\(swiftInnermostTypeName)Cases" members.append( contentsOf: translateToEnumValue(name: enumName, enumFields: enumConstants) ) @@ -278,7 +287,7 @@ extension JavaTranslator { do { let implementedInSwift = constructor.isNative && constructor.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) && - swiftNativeImplementations.contains(javaClass.getCanonicalName()) + swiftNativeImplementations.contains(javaClass.getName()) let translated = try translateConstructor( constructor, @@ -312,7 +321,7 @@ extension JavaTranslator { let implementedInSwift = method.isNative && method.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) && - swiftNativeImplementations.contains(javaClass.getCanonicalName()) + swiftNativeImplementations.contains(javaClass.getName()) // Translate the method if we can. do { @@ -357,7 +366,6 @@ extension JavaTranslator { } // Emit the struct declaration describing the java class. - let (swiftParentType, swiftInnermostTypeName) = swiftTypeName.splitSwiftTypeName() let classOrInterface: String = javaClass.isInterface() ? "JavaInterface" : "JavaClass"; var classDecl = """ @@ -383,6 +391,20 @@ extension JavaTranslator { topLevelDecls.append(classDecl) + let subClassDecls = javaClass.getClasses().compactMap { + $0.flatMap { clazz in + do { + return try translateClass(clazz) + } catch { + logUntranslated("Unable to translate '\(fullName)' subclass '\(clazz.getName())': \(error)") + return nil + } + } + }.flatMap(\.self) + + topLevelDecls.append( + contentsOf: subClassDecls + ) // Translate static members. var staticMembers: [DeclSyntax] = [] @@ -438,7 +460,7 @@ extension JavaTranslator { // Members that are native and will instead go into a NativeMethods // protocol. var nativeMembers: [DeclSyntax] = [] - if swiftNativeImplementations.contains(javaClass.getCanonicalName()) { + if swiftNativeImplementations.contains(javaClass.getName()) { nativeMembers.append( contentsOf: javaClass.getDeclaredMethods().compactMap { $0.flatMap { method in diff --git a/Sources/JavaKitJar/generated/Attributes.swift b/Sources/JavaKitJar/generated/Attributes.swift index 22aa68a4..74a1352c 100644 --- a/Sources/JavaKitJar/generated/Attributes.swift +++ b/Sources/JavaKitJar/generated/Attributes.swift @@ -44,6 +44,9 @@ public struct Attributes { @JavaMethod public func getValue(_ arg0: String) -> String + @JavaMethod + public func getValue(_ arg0: Attributes.Name?) -> String + @JavaMethod public func isEmpty() -> Bool @@ -95,3 +98,92 @@ public struct Attributes { @JavaMethod public func getOrDefault(_ arg0: JavaObject?, _ arg1: JavaObject?) -> JavaObject? } +extension Attributes { + @JavaClass("java.util.jar.Attributes$Name") + public struct Name { + @JavaMethod + public init(_ arg0: String, environment: JNIEnvironment? = nil) + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func hashCode() -> Int32 + + @JavaMethod + public func getClass() -> JavaClass? + + @JavaMethod + public func notify() + + @JavaMethod + public func notifyAll() + + @JavaMethod + public func wait(_ arg0: Int64) throws + + @JavaMethod + public func wait(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + public func wait() throws + } +} +extension JavaClass { + @JavaStaticField + public var MANIFEST_VERSION: Attributes.Name? + + @JavaStaticField + public var SIGNATURE_VERSION: Attributes.Name? + + @JavaStaticField + public var CONTENT_TYPE: Attributes.Name? + + @JavaStaticField + public var CLASS_PATH: Attributes.Name? + + @JavaStaticField + public var MAIN_CLASS: Attributes.Name? + + @JavaStaticField + public var SEALED: Attributes.Name? + + @JavaStaticField + public var EXTENSION_LIST: Attributes.Name? + + @JavaStaticField + public var EXTENSION_NAME: Attributes.Name? + + @JavaStaticField + public var EXTENSION_INSTALLATION: Attributes.Name? + + @JavaStaticField + public var IMPLEMENTATION_TITLE: Attributes.Name? + + @JavaStaticField + public var IMPLEMENTATION_VERSION: Attributes.Name? + + @JavaStaticField + public var IMPLEMENTATION_VENDOR: Attributes.Name? + + @JavaStaticField + public var IMPLEMENTATION_VENDOR_ID: Attributes.Name? + + @JavaStaticField + public var IMPLEMENTATION_URL: Attributes.Name? + + @JavaStaticField + public var SPECIFICATION_TITLE: Attributes.Name? + + @JavaStaticField + public var SPECIFICATION_VERSION: Attributes.Name? + + @JavaStaticField + public var SPECIFICATION_VENDOR: Attributes.Name? + + @JavaStaticField + public var MULTI_RELEASE: Attributes.Name? +} diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index a2e3cb34..5d446162 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -24,9 +24,9 @@ var jvm: JavaVirtualMachine { } @JavaClass("java.time.Month") -public struct JavaMonth { - -} +public struct JavaMonth {} +@JavaClass("java.lang.ProcessBuilder") +struct ProcessBuilder {} class Java2SwiftTests: XCTestCase { func testJavaLangObjectMapping() throws { @@ -114,6 +114,36 @@ class Java2SwiftTests: XCTestCase { ] ) } + + func testNestedSubclasses() throws { + try assertTranslatedClass( + ProcessBuilder.self, + swiftTypeName: "ProcessBuilder", + translatedClasses: [ + "java.lang.ProcessBuilder": ("ProcessBuilder", nil, true), + "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.Redirect", nil, true), + "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.Redirect.Type", nil, true), + ], + expectedChunks: [ + "import JavaKit", + """ + @JavaMethod + public func redirectInput() -> ProcessBuilder.Redirect? + """, + """ + extension ProcessBuilder { + @JavaClass("java.lang.ProcessBuilder$Redirect") + public struct Redirect { + """, + """ + extension ProcessBuilder.Redirect { + @JavaClass("java.lang.ProcessBuilder$Redirect$Type") + public struct Type { + """ + ] + ) + } + } @JavaClass("java.util.ArrayList") @@ -145,9 +175,10 @@ func assertTranslatedClass( translator.translatedClasses = translatedClasses translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil, true) + translator.startNewFile() - let translatedDecls = translator.translateClass( - try JavaClass( + let translatedDecls = try translator.translateClass( + JavaClass( javaThis: javaType.getJNIClass(in: environment), environment: environment) )