diff --git a/Sources/SwiftJava/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift index b45f60bfb..0af0de107 100644 --- a/Sources/SwiftJava/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -15,8 +15,7 @@ import Foundation extension String { - /// For a String that's of the form java.util.Vector, return the "Vector" - /// part. + /// For a String that's of the form java.util.Vector, return the "Vector" part. package var defaultSwiftNameForJavaClass: String { if let dotLoc = lastIndex(of: ".") { let afterDot = index(after: dotLoc) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index ae985e772..0af1e75bb 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -180,21 +180,24 @@ extension SwiftJava.WrapJavaCommand { // of classes to be translated if they were already specified. var allClassesToVisit = javaClasses var currentClassIndex: Int = 0 - while currentClassIndex < allClassesToVisit.count { + outerClassLoop: while currentClassIndex < allClassesToVisit.count { defer { currentClassIndex += 1 } - // The current class we're in. + // The current top-level class we're in. let currentClass = allClassesToVisit[currentClassIndex] + let currentClassName = currentClass.getName() guard let currentSwiftName = translator.translatedClasses[currentClass.getName()]?.swiftType else { continue } - // Find all of the nested classes that weren't explicitly translated - // already. - let nestedClasses: [JavaClass] = currentClass.getClasses().compactMap { nestedClass in - guard let nestedClass else { return nil } + // Find all of the nested classes that weren't explicitly translated already. + let nestedAndSuperclassNestedClasses = currentClass.getClasses() // watch out, this includes nested types from superclasses + let nestedClasses: [JavaClass] = nestedAndSuperclassNestedClasses.compactMap { nestedClass in + guard let nestedClass else { + return nil + } // If this is a local class, we're done. let javaClassName = nestedClass.getName() @@ -202,6 +205,14 @@ extension SwiftJava.WrapJavaCommand { return nil } + // We only want to visit and import types which are explicitly inside this decl, + // and NOT any of the types contained in the super classes. That would violate our "current class" + // nesting, because those are *actually* nested in the other class, not "the current one" (i.e. in a super class). + guard javaClassName.hasPrefix(currentClassName) else { + log.trace("Skip super-class nested class '\(javaClassName)', is not member of \(currentClassName). Will be visited independently.") + return nil + } + // If we have an inclusive filter, import only types from it for include in config.filterInclude ?? [] { guard javaClassName.starts(with: include) else { @@ -227,7 +238,9 @@ extension SwiftJava.WrapJavaCommand { .defaultSwiftNameForJavaClass let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)" - translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName) + let translatedSwiftName = SwiftTypeName(module: nil, name: swiftName) + translator.translatedClasses[javaClassName] = translatedSwiftName + log.debug("Record translated Java class '\(javaClassName)' -> \(translatedSwiftName)") return nestedClass } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index f56d26c5c..8071c36ad 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -58,10 +58,10 @@ package extension JavaTranslator { package var description: String { switch self { case .multipleClassesMappedToSameName(let swiftToJavaMapping): - """ - The following Java classes were mapped to the same Swift type name: - \(swiftToJavaMapping.map(mappingDescription(mapping:)).joined(separator: "\n")) - """ + """ + The following Java classes were mapped to the same Swift type name: + \(swiftToJavaMapping.map(mappingDescription(mapping:)).joined(separator: "\n")) + """ } } @@ -72,10 +72,6 @@ package extension JavaTranslator { } } func validateClassConfiguration() throws(ValidationError) { - // for a in translatedClasses { - // print("MAPPING = \(a.key) -> \(a.value.swiftModule?.escapedSwiftName ?? "").\(a.value.swiftType.escapedSwiftName)") - // } - // Group all classes by swift name let groupedDictionary: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = Dictionary(grouping: translatedClasses, by: { // SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift index d4596c901..74ed3ce28 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift @@ -49,4 +49,43 @@ final class BasicWrapJavaTests: XCTestCase { ) } + func test_wrapJava_doNotDupeImportNestedClassesFromSuperclassAutomatically() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class SuperClass { + class SuperNested {} + } + + class ExampleSimpleClass { + class SimpleNested {} + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.SuperClass", + "com.example.SuperClass$SuperNested", + "com.example.ExampleSimpleClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaClass("com.example.SuperClass") + open class SuperClass: JavaObject { + """, + // FIXME: the mapping configuration could be used to nest this properly but today we don't automatically? + """ + @JavaClass("com.example.SuperClass$SuperNested") + open class SuperNested: JavaObject { + """, + """ + @JavaClass("com.example.SuperClass") + open class SuperClass: JavaObject { + """, + ] + ) + } + } \ No newline at end of file