Skip to content

Commit ec76084

Browse files
authored
Add Support for Nested Classes (#115)
* First pass * Keep going * Add Tests for Subclasses * Fix issues with nested classes * Remove $ checks * Correctly support $ * remove tick * Add Subclass Generated * Update enums and tests * Fix incorrect comment * Some pr feedback
1 parent 07b0c54 commit ec76084

File tree

4 files changed

+197
-21
lines changed

4 files changed

+197
-21
lines changed

Sources/Java2Swift/JavaToSwift.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,27 @@ struct JavaToSwift: ParsableCommand {
228228

229229
// Note that we will be translating this Java class, so it is a known class.
230230
translator.translatedClasses[javaClassName] = (translatedSwiftName, nil, true)
231+
232+
var classes: [JavaClass<JavaObject>?] = javaClass.getClasses()
233+
234+
// Go through all subclasses to find all of the classes to translate
235+
while let internalClass = classes.popLast() {
236+
if let internalClass {
237+
let (javaName, swiftName) = names(from: internalClass.getName())
238+
// If we have already been through this class, don't go through it again
239+
guard translator.translatedClasses[javaName] == nil else { continue }
240+
let currentClassName = swiftName
241+
let currentSanitizedClassName = currentClassName.replacing("$", with: ".")
242+
classes.append(contentsOf: internalClass.getClasses())
243+
translator.translatedClasses[javaName] = (currentSanitizedClassName, nil, true)
244+
}
245+
}
231246
}
232247

233248
// Translate all of the Java classes into Swift classes.
234249
for javaClass in javaClasses {
235250
translator.startNewFile()
236-
let swiftClassDecls = translator.translateClass(javaClass)
251+
let swiftClassDecls = try translator.translateClass(javaClass)
237252
let importDecls = translator.getImportDecls()
238253

239254
let swiftFileText = """
@@ -247,11 +262,32 @@ struct JavaToSwift: ParsableCommand {
247262
try writeContents(
248263
swiftFileText,
249264
to: swiftFileName,
250-
description: "Java class '\(javaClass.getCanonicalName())' translation"
265+
description: "Java class '\(javaClass.getName())' translation"
251266
)
252267
}
253268
}
254269

270+
private func names(from javaClassNameOpt: String) -> (javaClassName: String, swiftName: String) {
271+
let javaClassName: String
272+
let swiftName: String
273+
if let equalLoc = javaClassNameOpt.firstIndex(of: "=") {
274+
let afterEqual = javaClassNameOpt.index(after: equalLoc)
275+
javaClassName = String(javaClassNameOpt[..<equalLoc])
276+
swiftName = String(javaClassNameOpt[afterEqual...])
277+
} else {
278+
if let dotLoc = javaClassNameOpt.lastIndex(of: ".") {
279+
let afterDot = javaClassNameOpt.index(after: dotLoc)
280+
swiftName = String(javaClassNameOpt[afterDot...])
281+
} else {
282+
swiftName = javaClassNameOpt
283+
}
284+
285+
javaClassName = javaClassNameOpt
286+
}
287+
288+
return (javaClassName, swiftName)
289+
}
290+
255291
mutating func writeContents(_ contents: String, to filename: String, description: String) throws {
256292
guard let outputDir = actualOutputDirectory else {
257293
print("// \(filename) - \(description)")
@@ -298,11 +334,6 @@ struct JavaToSwift: ParsableCommand {
298334
}
299335
}
300336

301-
// TODO: For now, skip all nested classes.
302-
if entry.getName().contains("$") {
303-
continue
304-
}
305-
306337
let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
307338
.dropLast(".class".count))
308339
configuration.classes[javaCanonicalName] =

Sources/Java2SwiftLib/JavaTranslator.swift

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,24 @@ extension JavaTranslator {
190190
/// Translates the given Java class into the corresponding Swift type. This
191191
/// can produce multiple declarations, such as a separate extension of
192192
/// JavaClass to house static methods.
193-
package func translateClass(_ javaClass: JavaClass<JavaObject>) -> [DeclSyntax] {
194-
let fullName = javaClass.getCanonicalName()
195-
let swiftTypeName = try! getSwiftTypeNameFromJavaClassName(fullName)
193+
package func translateClass(_ javaClass: JavaClass<JavaObject>) throws -> [DeclSyntax] {
194+
let fullName = javaClass.getName()
195+
let swiftTypeName = try getSwiftTypeNameFromJavaClassName(fullName)
196+
let (swiftParentType, swiftInnermostTypeName) = swiftTypeName.splitSwiftTypeName()
197+
198+
// If the swift parent type has not been translated, don't try to translate this one
199+
if let swiftParentType,
200+
!translatedClasses.contains(where: { _, value in value.swiftType == swiftParentType })
201+
{
202+
logUntranslated("Unable to translate '\(fullName)' parent class: \(swiftParentType) not found")
203+
return []
204+
}
196205

197206
// Superclass.
198207
let extends: String
199208
if !javaClass.isInterface(),
200209
let superclass = javaClass.getSuperclass(),
201-
superclass.getCanonicalName() != "java.lang.Object"
210+
superclass.getName() != "java.lang.Object"
202211
{
203212
do {
204213
extends = ", extends: \(try getSwiftTypeName(superclass).swiftName).self"
@@ -265,7 +274,7 @@ extension JavaTranslator {
265274
)
266275

267276
if !enumConstants.isEmpty {
268-
let enumName = "\(swiftTypeName)Cases"
277+
let enumName = "\(swiftInnermostTypeName)Cases"
269278
members.append(
270279
contentsOf: translateToEnumValue(name: enumName, enumFields: enumConstants)
271280
)
@@ -278,7 +287,7 @@ extension JavaTranslator {
278287
do {
279288
let implementedInSwift = constructor.isNative &&
280289
constructor.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) &&
281-
swiftNativeImplementations.contains(javaClass.getCanonicalName())
290+
swiftNativeImplementations.contains(javaClass.getName())
282291

283292
let translated = try translateConstructor(
284293
constructor,
@@ -312,7 +321,7 @@ extension JavaTranslator {
312321

313322
let implementedInSwift = method.isNative &&
314323
method.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) &&
315-
swiftNativeImplementations.contains(javaClass.getCanonicalName())
324+
swiftNativeImplementations.contains(javaClass.getName())
316325

317326
// Translate the method if we can.
318327
do {
@@ -357,7 +366,6 @@ extension JavaTranslator {
357366
}
358367

359368
// Emit the struct declaration describing the java class.
360-
let (swiftParentType, swiftInnermostTypeName) = swiftTypeName.splitSwiftTypeName()
361369
let classOrInterface: String = javaClass.isInterface() ? "JavaInterface" : "JavaClass";
362370
var classDecl =
363371
"""
@@ -383,6 +391,20 @@ extension JavaTranslator {
383391

384392
topLevelDecls.append(classDecl)
385393

394+
let subClassDecls = javaClass.getClasses().compactMap {
395+
$0.flatMap { clazz in
396+
do {
397+
return try translateClass(clazz)
398+
} catch {
399+
logUntranslated("Unable to translate '\(fullName)' subclass '\(clazz.getName())': \(error)")
400+
return nil
401+
}
402+
}
403+
}.flatMap(\.self)
404+
405+
topLevelDecls.append(
406+
contentsOf: subClassDecls
407+
)
386408
// Translate static members.
387409
var staticMembers: [DeclSyntax] = []
388410

@@ -438,7 +460,7 @@ extension JavaTranslator {
438460
// Members that are native and will instead go into a NativeMethods
439461
// protocol.
440462
var nativeMembers: [DeclSyntax] = []
441-
if swiftNativeImplementations.contains(javaClass.getCanonicalName()) {
463+
if swiftNativeImplementations.contains(javaClass.getName()) {
442464
nativeMembers.append(
443465
contentsOf: javaClass.getDeclaredMethods().compactMap {
444466
$0.flatMap { method in

Sources/JavaKitJar/generated/Attributes.swift

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public struct Attributes {
4444
@JavaMethod
4545
public func getValue(_ arg0: String) -> String
4646

47+
@JavaMethod
48+
public func getValue(_ arg0: Attributes.Name?) -> String
49+
4750
@JavaMethod
4851
public func isEmpty() -> Bool
4952

@@ -95,3 +98,92 @@ public struct Attributes {
9598
@JavaMethod
9699
public func getOrDefault(_ arg0: JavaObject?, _ arg1: JavaObject?) -> JavaObject?
97100
}
101+
extension Attributes {
102+
@JavaClass("java.util.jar.Attributes$Name")
103+
public struct Name {
104+
@JavaMethod
105+
public init(_ arg0: String, environment: JNIEnvironment? = nil)
106+
107+
@JavaMethod
108+
public func equals(_ arg0: JavaObject?) -> Bool
109+
110+
@JavaMethod
111+
public func toString() -> String
112+
113+
@JavaMethod
114+
public func hashCode() -> Int32
115+
116+
@JavaMethod
117+
public func getClass() -> JavaClass<JavaObject>?
118+
119+
@JavaMethod
120+
public func notify()
121+
122+
@JavaMethod
123+
public func notifyAll()
124+
125+
@JavaMethod
126+
public func wait(_ arg0: Int64) throws
127+
128+
@JavaMethod
129+
public func wait(_ arg0: Int64, _ arg1: Int32) throws
130+
131+
@JavaMethod
132+
public func wait() throws
133+
}
134+
}
135+
extension JavaClass<Attributes.Name> {
136+
@JavaStaticField
137+
public var MANIFEST_VERSION: Attributes.Name?
138+
139+
@JavaStaticField
140+
public var SIGNATURE_VERSION: Attributes.Name?
141+
142+
@JavaStaticField
143+
public var CONTENT_TYPE: Attributes.Name?
144+
145+
@JavaStaticField
146+
public var CLASS_PATH: Attributes.Name?
147+
148+
@JavaStaticField
149+
public var MAIN_CLASS: Attributes.Name?
150+
151+
@JavaStaticField
152+
public var SEALED: Attributes.Name?
153+
154+
@JavaStaticField
155+
public var EXTENSION_LIST: Attributes.Name?
156+
157+
@JavaStaticField
158+
public var EXTENSION_NAME: Attributes.Name?
159+
160+
@JavaStaticField
161+
public var EXTENSION_INSTALLATION: Attributes.Name?
162+
163+
@JavaStaticField
164+
public var IMPLEMENTATION_TITLE: Attributes.Name?
165+
166+
@JavaStaticField
167+
public var IMPLEMENTATION_VERSION: Attributes.Name?
168+
169+
@JavaStaticField
170+
public var IMPLEMENTATION_VENDOR: Attributes.Name?
171+
172+
@JavaStaticField
173+
public var IMPLEMENTATION_VENDOR_ID: Attributes.Name?
174+
175+
@JavaStaticField
176+
public var IMPLEMENTATION_URL: Attributes.Name?
177+
178+
@JavaStaticField
179+
public var SPECIFICATION_TITLE: Attributes.Name?
180+
181+
@JavaStaticField
182+
public var SPECIFICATION_VERSION: Attributes.Name?
183+
184+
@JavaStaticField
185+
public var SPECIFICATION_VENDOR: Attributes.Name?
186+
187+
@JavaStaticField
188+
public var MULTI_RELEASE: Attributes.Name?
189+
}

Tests/Java2SwiftTests/Java2SwiftTests.swift

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ var jvm: JavaVirtualMachine {
2424
}
2525

2626
@JavaClass("java.time.Month")
27-
public struct JavaMonth {
28-
29-
}
27+
public struct JavaMonth {}
28+
@JavaClass("java.lang.ProcessBuilder")
29+
struct ProcessBuilder {}
3030

3131
class Java2SwiftTests: XCTestCase {
3232
func testJavaLangObjectMapping() throws {
@@ -114,6 +114,36 @@ class Java2SwiftTests: XCTestCase {
114114
]
115115
)
116116
}
117+
118+
func testNestedSubclasses() throws {
119+
try assertTranslatedClass(
120+
ProcessBuilder.self,
121+
swiftTypeName: "ProcessBuilder",
122+
translatedClasses: [
123+
"java.lang.ProcessBuilder": ("ProcessBuilder", nil, true),
124+
"java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.Redirect", nil, true),
125+
"java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.Redirect.Type", nil, true),
126+
],
127+
expectedChunks: [
128+
"import JavaKit",
129+
"""
130+
@JavaMethod
131+
public func redirectInput() -> ProcessBuilder.Redirect?
132+
""",
133+
"""
134+
extension ProcessBuilder {
135+
@JavaClass("java.lang.ProcessBuilder$Redirect")
136+
public struct Redirect {
137+
""",
138+
"""
139+
extension ProcessBuilder.Redirect {
140+
@JavaClass("java.lang.ProcessBuilder$Redirect$Type")
141+
public struct Type {
142+
"""
143+
]
144+
)
145+
}
146+
117147
}
118148

119149
@JavaClass("java.util.ArrayList")
@@ -145,9 +175,10 @@ func assertTranslatedClass<JavaClassType: AnyJavaObject>(
145175
translator.translatedClasses = translatedClasses
146176
translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil, true)
147177

178+
148179
translator.startNewFile()
149-
let translatedDecls = translator.translateClass(
150-
try JavaClass<JavaObject>(
180+
let translatedDecls = try translator.translateClass(
181+
JavaClass<JavaObject>(
151182
javaThis: javaType.getJNIClass(in: environment),
152183
environment: environment)
153184
)

0 commit comments

Comments
 (0)