Skip to content

Make nested classes explicitly-specifiable and work through some integration issues #118

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 10 commits into from
Oct 27, 2024
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
3 changes: 2 additions & 1 deletion Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin {
/// Determine the set of Swift files that will be emitted by the Java2Swift
/// tool.
let outputSwiftFiles = config.classes.map { (javaClassName, swiftName) in
outputDirectory.appending(path: "\(swiftName).swift")
let swiftNestedName = swiftName.replacingOccurrences(of: ".", with: "+")
return outputDirectory.appending(path: "\(swiftNestedName).swift")
}

// Find the Java .class files generated from prior plugins.
Expand Down
1 change: 1 addition & 0 deletions Samples/JavaSieve/Sources/JavaSieve/Java2Swift.config
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"com.gazman.quadratic_sieve.data.VectorWorkData" : "VectorWorkData",
"com.gazman.quadratic_sieve.debug.Analytics" : "Analytics",
"com.gazman.quadratic_sieve.debug.AssertUtils" : "AssertUtils",
"com.gazman.quadratic_sieve.debug.AssertUtils$Tester" : "AssertUtils.Tester",
"com.gazman.quadratic_sieve.debug.Logger" : "Logger",
"com.gazman.quadratic_sieve.fact.TrivialDivision" : "TrivialDivision",
"com.gazman.quadratic_sieve.primes.BigPrimes" : "BigPrimes",
Expand Down
121 changes: 83 additions & 38 deletions Sources/Java2Swift/JavaToSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,48 +201,69 @@ struct JavaToSwift: ParsableCommand {
// Add the configuration for this module.
translator.addConfiguration(config, forSwiftModule: moduleName)

// Load all of the requested classes.
#if false
let classLoader = URLClassLoader(
[
try URL("file://\(classPath)", environment: environment)
],
environment: environment
)
#else
// Load all of the explicitly-requested classes.
let classLoader = try JavaClass<ClassLoader>(environment: environment)
.getSystemClassLoader()!
#endif
var javaClasses: [JavaClass<JavaObject>] = []
for (javaClassName, swiftName) in config.classes {
guard let javaClass = try classLoader.loadClass(javaClassName) else {
print("warning: could not find Java class '\(javaClassName)'")
continue
}

// Add this class to the list of classes we'll translate.
javaClasses.append(javaClass)
}

// Find all of the nested classes for each class, adding them to the list
// of classes to be translated if they were already specified.
var allClassesToVisit = javaClasses
var currentClassIndex: Int = 0
while currentClassIndex < allClassesToVisit.count {
defer {
currentClassIndex += 1
}

// The current class we're in.
let currentClass = allClassesToVisit[currentClassIndex]
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<JavaObject>] = currentClass.getClasses().compactMap { nestedClass in
guard let nestedClass else { return nil }

// Replace any $'s within the Java class name (which separate nested
// classes) with .'s (which represent nesting in Swift).
let translatedSwiftName = swiftName.replacing("$", with: ".")

// Note that we will be translating this Java class, so it is a known class.
translator.translatedClasses[javaClassName] = (translatedSwiftName, nil, true)

var classes: [JavaClass<JavaObject>?] = 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)
// If this is a local class, we're done.
let javaClassName = nestedClass.getName()
if javaClassName.isLocalJavaClass {
return nil
}

// If this class has been explicitly mentioned, we're done.
if translator.translatedClasses[javaClassName] != nil {
return nil
}

// Record this as a translated class.
let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName
.defaultSwiftNameForJavaClass


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

// If there were no new nested classes, there's nothing to do.
if nestedClasses.isEmpty {
continue
}

// Record all of the nested classes that we will visit.
translator.nestedClasses[currentClass.getName()] = nestedClasses
allClassesToVisit.append(contentsOf: nestedClasses)
}

// Translate all of the Java classes into Swift classes.
Expand Down Expand Up @@ -285,7 +306,7 @@ struct JavaToSwift: ParsableCommand {
javaClassName = javaClassNameOpt
}

return (javaClassName, swiftName)
return (javaClassName, swiftName.javaClassNameToCanonicalName)
}

mutating func writeContents(_ contents: String, to filename: String, description: String) throws {
Expand Down Expand Up @@ -326,12 +347,9 @@ struct JavaToSwift: ParsableCommand {
continue
}

// If any of the segments of the Java name start with a number, it's a
// local class that cannot be mapped into Swift.
for segment in entry.getName().split(separator: "$") {
if let firstChar = segment.first, firstChar.isNumber {
continue
}
// If this is a local class, it cannot be mapped into Swift.
if entry.getName().isLocalJavaClass {
continue
}

let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
Expand Down Expand Up @@ -374,10 +392,10 @@ extension String {
fileprivate var defaultSwiftNameForJavaClass: String {
if let dotLoc = lastIndex(of: ".") {
let afterDot = index(after: dotLoc)
return String(self[afterDot...])
return String(self[afterDot...]).javaClassNameToCanonicalName.adjustedSwiftTypeName
}

return self
return javaClassNameToCanonicalName.adjustedSwiftTypeName
}
}

Expand All @@ -391,3 +409,30 @@ extension JavaClass<ClassLoader> {
@JavaStaticMethod
public func getSystemClassLoader() -> ClassLoader?
}

extension String {
/// Replace all of the $'s for nested names with "." to turn a Java class
/// name into a Java canonical class name,
fileprivate var javaClassNameToCanonicalName: String {
return replacing("$", with: ".")
}

/// Whether this is the name of an anonymous class.
fileprivate var isLocalJavaClass: Bool {
for segment in split(separator: "$") {
if let firstChar = segment.first, firstChar.isNumber {
return true
}
}

return false
}

/// Adjust type name for "bad" type names that don't work well in Swift.
fileprivate var adjustedSwiftTypeName: String {
switch self {
case "Type": return "JavaType"
default: return self
}
}
}
63 changes: 51 additions & 12 deletions Sources/Java2SwiftLib/JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ package class JavaTranslator {
/// methods will be implemented in Swift.
package var swiftNativeImplementations: Set<String> = []

/// The set of nested classes that we should traverse from the given class,
/// indexed by the name of the class.
///
/// TODO: Make JavaClass Hashable so we can index by the object?
package var nestedClasses: [String: [JavaClass<JavaObject>]] = [:]

package init(
swiftModuleName: String,
environment: JNIEnvironment,
Expand Down Expand Up @@ -79,7 +85,6 @@ extension JavaTranslator {
/// 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.Class": ("JavaClass", "JavaKit", true),
"java.lang.String": ("String", "JavaKit", false),
]
}
Expand Down Expand Up @@ -165,19 +170,28 @@ extension JavaTranslator {
let javaType = try JavaType(javaTypeName: javaClass.getName())
let isSwiftOptional = javaType.isSwiftOptional
return (
try javaType.swiftTypeName(resolver: self.getSwiftTypeNameFromJavaClassName(_:)),
try javaType.swiftTypeName { javaClassName in
try self.getSwiftTypeNameFromJavaClassName(javaClassName)
},
isSwiftOptional
)
}

/// Map a Java class name to its corresponding Swift type.
private func getSwiftTypeNameFromJavaClassName(_ name: String) throws -> String {
private func getSwiftTypeNameFromJavaClassName(
_ name: String,
escapeMemberNames: Bool = true
) throws -> String {
if let translated = translatedClasses[name] {
// Note that we need to import this Swift module.
if let swiftModule = translated.swiftModule, swiftModule != swiftModuleName {
importedSwiftModules.insert(swiftModule)
}

if escapeMemberNames {
return translated.swiftType.escapingSwiftMemberNames
}

return translated.swiftType
}

Expand All @@ -192,7 +206,7 @@ extension JavaTranslator {
/// JavaClass to house static methods.
package func translateClass(_ javaClass: JavaClass<JavaObject>) throws -> [DeclSyntax] {
let fullName = javaClass.getName()
let swiftTypeName = try getSwiftTypeNameFromJavaClassName(fullName)
let swiftTypeName = try getSwiftTypeNameFromJavaClassName(fullName, escapeMemberNames: false)
let (swiftParentType, swiftInnermostTypeName) = swiftTypeName.splitSwiftTypeName()

// If the swift parent type has not been translated, don't try to translate this one
Expand Down Expand Up @@ -391,14 +405,12 @@ 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
}
let subClassDecls = (nestedClasses[fullName] ?? []).compactMap { clazz in
do {
return try translateClass(clazz)
} catch {
logUntranslated("Unable to translate '\(fullName)' subclass '\(clazz.getName())': \(error)")
return nil
}
}.flatMap(\.self)

Expand Down Expand Up @@ -633,3 +645,30 @@ extension JavaTranslator {
}
}
}

extension String {
/// Escape Swift types that involve member name references like '.Type'
fileprivate var escapingSwiftMemberNames: String {
var count = 0
return split(separator: ".").map { component in
defer {
count += 1
}

if count > 0 && component.memberRequiresBackticks {
return "`\(component)`"
}

return String(component)
}.joined(separator: ".")
}
}

extension Substring {
fileprivate var memberRequiresBackticks: Bool {
switch self {
case "Type": return true
default: return false
}
}
}
2 changes: 1 addition & 1 deletion Sources/JavaKit/Optional+JavaObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension Optional: JavaValue where Wrapped: AnyJavaObject {
}

public static var javaType: JavaType {
JavaType(canonicalClassName: Wrapped.fullJavaClassName)
JavaType(className: Wrapped.fullJavaClassName)
}

public static func jniMethodCall(
Expand Down
36 changes: 18 additions & 18 deletions Sources/JavaKitJar/generated/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,57 +133,57 @@ extension Attributes {
}
}
extension JavaClass<Attributes.Name> {
@JavaStaticField
@JavaStaticField(isFinal: true)
public var MANIFEST_VERSION: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var SIGNATURE_VERSION: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var CONTENT_TYPE: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var CLASS_PATH: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var MAIN_CLASS: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var SEALED: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var EXTENSION_LIST: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var EXTENSION_NAME: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var EXTENSION_INSTALLATION: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var IMPLEMENTATION_TITLE: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var IMPLEMENTATION_VERSION: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var IMPLEMENTATION_VENDOR: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var IMPLEMENTATION_VENDOR_ID: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var IMPLEMENTATION_URL: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var SPECIFICATION_TITLE: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var SPECIFICATION_VERSION: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var SPECIFICATION_VENDOR: Attributes.Name?

@JavaStaticField
@JavaStaticField(isFinal: true)
public var MULTI_RELEASE: Attributes.Name?
}
2 changes: 1 addition & 1 deletion Sources/JavaTypes/JavaType+JavaSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension JavaType {
self = try JavaType(mangledName: name)

case let className:
self = JavaType(canonicalClassName: className)
self = JavaType(className: className)
}
}
}
Expand Down
Loading