From bcd5f76971aad0bd4a069e979caa1b950665675a Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Sun, 28 Sep 2025 10:25:01 +0100 Subject: [PATCH 01/28] WIP: fixing up wrap-java behavior, also adding tests which compile and extract, just like the command does towards generic parent class in wrap-java mode --- Package.swift | 2 + .../org/swift/swiftkitffm/ThrowsFFMTest.java | 41 ++++ .../String+Extensions.swift | 18 +- .../Configuration.swift | 6 +- .../Commands/ConfigureCommand.swift | 51 +++-- .../Commands/ResolveCommand.swift | 22 +- .../Commands/WrapJavaCommand.swift | 19 +- .../SwiftJavaBaseAsyncParsableCommand.swift | 4 +- .../JavaClassTranslator.swift | 3 + .../SwiftJavaToolLib/JavaHomeSupport.swift | 92 ++++++++ .../JavaTranslator+Configuration.swift | 11 +- .../JavaTranslator+Validation.swift | 45 +++- Sources/SwiftJavaToolLib/JavaTranslator.swift | 14 +- .../FFMNestedTypesTests.swift | 56 +++++ .../JNI/JNINestedTypesTests.swift | 10 + .../Java2SwiftTests.swift | 88 ++++---- .../JavaTranslatorValidationTests.swift | 10 +- .../SwiftJavaToolLibTests/TempFileTools.swift | 34 +++ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 211 ++++++++++++++++++ 19 files changed, 623 insertions(+), 114 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java rename Sources/{SwiftJavaTool => SwiftJava}/String+Extensions.swift (82%) create mode 100644 Sources/SwiftJavaToolLib/JavaHomeSupport.swift create mode 100644 Tests/JExtractSwiftTests/FFMNestedTypesTests.swift create mode 100644 Tests/SwiftJavaToolLibTests/TempFileTools.swift create mode 100644 Tests/SwiftJavaToolLibTests/WrapJavaTests.swift diff --git a/Package.swift b/Package.swift index 722859eed..44b61be97 100644 --- a/Package.swift +++ b/Package.swift @@ -202,6 +202,8 @@ let package = Package( ], dependencies: [ + .package(url: "https://github.com/apple/swift-log", from: "1.2.0"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java new file mode 100644 index 000000000..ea08c321b --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ThrowsFFMTest { + @Test + void throwSwiftError() { + try (var arena = SwiftArena.ofConfined()) { + var funcs = ThrowingFuncs.init(12, arena); + funcs.throwError(); + } catch (Exception ex) { + assertEquals("java.lang.Exception: MyExampleSwiftError(message: \"yes, it\\'s an error!\")", ex.toString()); + return; + } + + throw new AssertionError("Expected Swift error to be thrown as exception"); + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift similarity index 82% rename from Sources/SwiftJavaTool/String+Extensions.swift rename to Sources/SwiftJava/String+Extensions.swift index 304e217d5..94fb19286 100644 --- a/Sources/SwiftJavaTool/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -13,17 +13,15 @@ //===----------------------------------------------------------------------===// import Foundation -import ArgumentParser -import SwiftJavaToolLib -import SwiftJava -import JavaUtilJar -import SwiftJavaToolLib -import SwiftJavaConfigurationShared +// import SwiftJavaToolLib +// import SwiftJava +// import JavaUtilJar +// import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" /// part. - var defaultSwiftNameForJavaClass: String { + package var defaultSwiftNameForJavaClass: String { if let dotLoc = lastIndex(of: ".") { let afterDot = index(after: dotLoc) return String(self[afterDot...]).javaClassNameToCanonicalName.adjustedSwiftTypeName @@ -36,12 +34,12 @@ extension String { extension String { /// Replace all of the $'s for nested names with "." to turn a Java class /// name into a Java canonical class name, - var javaClassNameToCanonicalName: String { + package var javaClassNameToCanonicalName: String { return replacing("$", with: ".") } /// Whether this is the name of an anonymous class. - var isLocalJavaClass: Bool { + package var isLocalJavaClass: Bool { for segment in split(separator: "$") { if let firstChar = segment.first, firstChar.isNumber { return true @@ -52,7 +50,7 @@ extension String { } /// Adjust type name for "bad" type names that don't work well in Swift. - var adjustedSwiftTypeName: String { + package var adjustedSwiftTypeName: String { switch self { case "Type": return "JavaType" default: return self diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index c9d0cedfc..f08f72363 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -65,7 +65,7 @@ public struct Configuration: Codable { asyncFuncMode ?? .default } - // ==== java 2 swift --------------------------------------------------------- + // ==== wrap-java --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. public var classpath: String? = nil @@ -85,6 +85,10 @@ public struct Configuration: Codable { // Generate class files suitable for the specified Java SE release. public var targetCompatibility: JavaVersion? + /// Filter input Java types by their package prefix if set. + /// Can be overriden by `--filter-java-package`. + public var filterJavaPackage: String? + // ==== dependencies --------------------------------------------------------- // Java dependencies we need to fetch for this target. diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 9a05a2867..c560b9488 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -72,7 +72,7 @@ extension SwiftJava.ConfigureCommand { let jvm = try self.makeJVM(classpathEntries: classpathEntries) - try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment()) + try emitConfiguration(classpathEntries: classpathEntries, environment: jvm.environment()) } /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. @@ -97,32 +97,42 @@ extension SwiftJava.ConfigureCommand { // TODO: make this perhaps "emit type mappings" mutating func emitConfiguration( - classpath: [String], + classpathEntries: [String], environment: JNIEnvironment ) throws { + var log = Self.log + log.logLevel = .init(rawValue: self.logLevel.rawValue)! + + + log.info("Run: emit configuration...") + var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() + if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage { - print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") + log.debug("Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") + } else if let filterJavaPackage = configuration.filterJavaPackage { + // take the package filter from the configuration file + self.commonJVMOptions.filterJavaPackage = filterJavaPackage + } else { + log.debug("Generate Java->Swift type mappings. No package filter applied.") } - print("[java-swift][debug] Classpath: \(classpath)") + log.debug("Classpath: \(classpathEntries)") - if classpath.isEmpty { - print("[java-swift][warning] Classpath is empty!") + if classpathEntries.isEmpty { + log.warning("Classpath is empty!") } // Get a fresh or existing configuration we'll amend - var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() if amendExistingConfig { - print("[swift-java] Amend existing swift-java.config file...") + log.info("Amend existing swift-java.config file...") } - configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct? + configuration.classpath = classpathEntries.joined(separator: ":") // TODO: is this correct? // Import types from all the classpath entries; // Note that we use the package level filtering, so users have some control over what gets imported. - let classpathEntries = classpath.split(separator: ":").map(String.init) for entry in classpathEntries { guard fileOrDirectoryExists(at: entry) else { // We only log specific jars missing, as paths may be empty directories that won't hurt not existing. - print("[debug][swift-java] Classpath entry does not exist: \(entry)") + log.debug("Classpath entry does not exist: \(entry)") continue } @@ -135,9 +145,9 @@ extension SwiftJava.ConfigureCommand { environment: environment ) } else if FileManager.default.fileExists(atPath: entry) { - print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") + log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") } else { - print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)") + log.warning("Classpath entry does not exist, skipping: \(entry)") } } @@ -158,6 +168,8 @@ extension SwiftJava.ConfigureCommand { forJar jarFile: JarFile, environment: JNIEnvironment ) throws { + let log = Self.log + for entry in jarFile.entries()! { // We only look at class files in the Jar file. guard entry.getName().hasSuffix(".class") else { @@ -183,6 +195,7 @@ extension SwiftJava.ConfigureCommand { let javaCanonicalName = String(entry.getName().replacing("/", with: ".") .dropLast(".class".count)) + if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage, !javaCanonicalName.hasPrefix(filterJavaPackage) { // Skip classes which don't match our expected prefix @@ -195,7 +208,17 @@ extension SwiftJava.ConfigureCommand { continue } - configuration.classes?[javaCanonicalName] = + if configuration.classes == nil { + configuration.classes = [:] + } + + if let configuredSwiftName = configuration.classes![javaCanonicalName] { + log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.") + } else { + log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.") + } + + configuration.classes![javaCanonicalName] = javaCanonicalName.defaultSwiftNameForJavaClass } } diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 58c64690b..b30ed9689 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -104,7 +104,10 @@ extension SwiftJava.ResolveCommand { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") - let dependenciesClasspath = await resolveDependencies(dependencies: dependencies) + let workDir = URL(fileURLWithPath: self.commonOptions.outputDirectory!) + .appending(path: "resolver-dir") + + let dependenciesClasspath = await resolveDependencies(workDir: workDir, dependencies: dependencies) let classpathEntries = dependenciesClasspath.split(separator: ":") print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "") @@ -122,10 +125,15 @@ extension SwiftJava.ResolveCommand { /// /// - Parameter dependencies: maven-style dependencies to resolve /// - Returns: Colon-separated classpath - func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { - let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .appendingPathComponent(".build") - let resolverDir = try! createTemporaryDirectory(in: workDir) + func resolveDependencies(workDir: URL, dependencies: [JavaDependencyDescriptor]) async -> String { + print("Create directory: \(workDir.absoluteString)") + + let resolverDir: URL + do { + resolverDir = try createTemporaryDirectory(in: workDir) + } catch { + fatalError("Unable to create temp directory at: \(workDir.absoluteString)! \(error)") + } defer { try? FileManager.default.removeItem(at: resolverDir) } @@ -162,7 +170,9 @@ extension SwiftJava.ResolveCommand { } else { let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "")>>>") + "Command was: \(CommandLine.arguments.joined(separator: " ").bold)\n" + + "Output was: <<<\(outString)>>>;\n" + + "Err was: <<<\(errString)>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 43362edbd..a1e179f2a 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -139,12 +139,21 @@ extension SwiftJava.WrapJavaCommand { translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule) // Load all of the explicitly-requested classes. - let classLoader = try JavaClass(environment: environment) + let classLoader = try! JavaClass(environment: environment) .getSystemClassLoader()! var javaClasses: [JavaClass] = [] for (javaClassName, _) in config.classes ?? [:] { + + if let filterPackage = config.filterJavaPackage { + if javaClassName.starts(with: filterPackage) { + log.info("SKIP Wrapping java type: \(javaClassName)") + continue + } + } + log.info("Wrapping java type: \(javaClassName)") + guard let javaClass = try classLoader.loadClass(javaClassName) else { - print("warning: could not find Java class '\(javaClassName)'") + log.warning("Could not load Java class '\(javaClassName)', skipping.") continue } @@ -178,8 +187,9 @@ extension SwiftJava.WrapJavaCommand { return nil } + // If this class has been explicitly mentioned, we're done. - if translator.translatedClasses[javaClassName] != nil { + guard translator.translatedClasses[javaClassName] == nil else { return nil } @@ -187,9 +197,8 @@ extension SwiftJava.WrapJavaCommand { let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName .defaultSwiftNameForJavaClass - let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)" - translator.translatedClasses[javaClassName] = (swiftName, nil) + translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName) return nestedClass } diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 9763b4b38..8413ae6fd 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -81,7 +81,7 @@ extension SwiftJavaBaseAsyncParsableCommand { // If we haven't tried to create the output directory yet, do so now before // we write any files to it. - // if !createdOutputDirectory { + // if !createdOutputDirectory { // FIXME: do we need this try FileManager.default.createDirectory( at: outputDir, withIntermediateDirectories: true @@ -124,8 +124,6 @@ extension SwiftJavaBaseAsyncParsableCommand { if outputDirectory == "-" { return nil } -// print("[debug][swift-java] Module base directory based on outputDirectory!") -// return URL(fileURLWithPath: outputDirectory) } // Put the result into Sources/\(swiftModule). diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index a7c4d76f7..89878805f 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -148,6 +148,8 @@ struct JavaClassTranslator { self.swiftSuperclass = nil } + print("[\(fullName)] SWUFT SUPER CLASS = \(swiftSuperclass)") + // Interfaces. self.swiftInterfaces = javaClass.getGenericInterfaces().compactMap { (javaType) -> String? in guard let javaType else { @@ -791,6 +793,7 @@ extension JavaClassTranslator { return true } } catch { + // FIXME: logging } } diff --git a/Sources/SwiftJavaToolLib/JavaHomeSupport.swift b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift new file mode 100644 index 000000000..5d13d3a4d --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Detected JAVA_HOME for this process. +package let javaHome = findJavaHome() + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +public func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty + { + return home + } + + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" + && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil + { + // Just ignore that we're missing a JAVA_HOME when building in Swift Package Index during general processing where no Java is needed. However, do _not_ suppress the error during SPI's compatibility build stage where Java is required. + return "" + } + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} + +/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. +public func getJavaHomeFromLibexecJavaHome() -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") + + // Check if the executable exists before trying to run it + guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { + print("/usr/libexec/java_home does not exist") + return nil + } + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe // Redirect standard error to the same pipe for simplicity + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters( + in: .whitespacesAndNewlines) + + if task.terminationStatus == 0 { + return output + } else { + print("java_home terminated with status: \(task.terminationStatus)") + // Optionally, log the error output for debugging + if let errorOutput = String( + data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { + print("Error output: \(errorOutput)") + } + return nil + } + } catch { + print("Error running java_home: \(error)") + return nil + } +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index 4fc9feb04..d2ac37414 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -16,11 +16,6 @@ import Foundation import SwiftJavaConfigurationShared extension JavaTranslator { -// /// Read a configuration file from the given URL. -// package static func readConfiguration(from url: URL) throws -> Configuration { -// let contents = try Data(contentsOf: url) -// return try JSONDecoder().decode(Configuration.self, from: contents) -// } /// Load the configuration file with the given name to populate the known set of /// translated Java classes. @@ -30,10 +25,8 @@ extension JavaTranslator { } for (javaClassName, swiftName) in classes { - translatedClasses[javaClassName] = ( - swiftType: swiftName, - swiftModule: swiftModule - ) + print("SET = \(javaClassName)") // XXX + translatedClasses[javaClassName] = SwiftTypeName(module: swiftModule, name: swiftName) } } } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index 9d4d00ca4..e4e39839d 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -12,24 +12,37 @@ // //===----------------------------------------------------------------------===// -package extension JavaTranslator { - struct SwiftTypeName: Hashable { - let swiftType: String - let swiftModule: String? +public typealias JavaFullyQualifiedTypeName = String - package init(swiftType: String, swiftModule: String?) { - self.swiftType = swiftType - self.swiftModule = swiftModule +package struct SwiftTypeName: Hashable, CustomStringConvertible { + package let swiftModule: String? + package let swiftType: String + + package init(module: String?, name: String) { + self.swiftModule = module + self.swiftType = name + } + + package var description: String { + if let swiftModule { + "`\(swiftModule)/\(swiftType)`" + } else { + "`\(swiftType)`" } } +} + +package extension JavaTranslator { struct SwiftToJavaMapping: Equatable { let swiftType: SwiftTypeName - let javaTypes: [String] + let javaTypes: [JavaFullyQualifiedTypeName] - package init(swiftType: SwiftTypeName, javaTypes: [String]) { + package init(swiftType: SwiftTypeName, javaTypes: [JavaFullyQualifiedTypeName]) { self.swiftType = swiftType self.javaTypes = javaTypes + precondition(!javaTypes.contains("com.google.protobuf.AbstractMessage$Builder"), + "\(swiftType) mapped as \(javaTypes)\n\(CommandLine.arguments.joined(separator: " "))") // XXX } } @@ -48,15 +61,23 @@ package extension JavaTranslator { private func mappingDescription(mapping: SwiftToJavaMapping) -> String { let javaTypes = mapping.javaTypes.map { "'\($0)'" }.joined(separator: ", ") - return "Swift Type: '\(mapping.swiftType.swiftModule ?? "")'.'\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" + return "Swift module: '\(mapping.swiftType.swiftModule ?? "")', type: '\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" } } 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: [(String, (String, String?))]] = Dictionary(grouping: translatedClasses, by: { SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) }) + let groupedDictionary: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = Dictionary(grouping: translatedClasses, by: { + // SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) + $0.value + }) // Find all that are mapped to multiple names - let multipleClassesMappedToSameName: [SwiftTypeName: [(String, (String, String?))]] = groupedDictionary.filter { (key: SwiftTypeName, value: [(String, (String, String?))]) in + let multipleClassesMappedToSameName: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = groupedDictionary.filter { + (key: SwiftTypeName, value: [(JavaFullyQualifiedTypeName, SwiftTypeName)]) in value.count > 1 } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 7cb8a7eeb..689e2cea4 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -35,7 +35,9 @@ package class JavaTranslator { /// A mapping from the name of each known Java class to the corresponding /// Swift type name and its Swift module. - package var translatedClasses: [String: (swiftType: String, swiftModule: String?)] = [:] + package var translatedClasses: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject") + ] /// A mapping from the name of each known Java class with the Swift value type /// (and its module) to which it is mapped. @@ -44,8 +46,8 @@ package class JavaTranslator { /// `translatedClasses` should map to a representation of the Java class (i.e., /// an AnyJavaObject-conforming type) whereas the entry here should map to /// a value type. - package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [ - "java.lang.String": ("String", "SwiftJava"), + package let translatedToValueTypes: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.String": SwiftTypeName(module: "SwiftJava", name: "String"), ] /// The set of Swift modules that need to be imported to make the generated @@ -233,7 +235,10 @@ extension JavaTranslator { if preferValueTypes, let translatedValueType = translatedToValueTypes[name] { // Note that we need to import this Swift module. if translatedValueType.swiftModule != swiftModuleName { - importedSwiftModules.insert(translatedValueType.swiftModule) + guard let module = translatedValueType.swiftModule else { + preconditionFailure("Translated value type must have Swift module, but was nil! Type: \(translatedValueType)") + } + importedSwiftModules.insert(module) } return translatedValueType.swiftType @@ -252,6 +257,7 @@ extension JavaTranslator { return translated.swiftType } + print("DEBUG >>> Not translated: \(name)") throw TranslationError.untranslatedJavaClass(name) } } diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift new file mode 100644 index 000000000..240ec1633 --- /dev/null +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import SwiftJavaConfigurationShared +import Testing + +final class FFMNestedTypesTests { + let class_interfaceFile = + """ + public enum MyNamespace { } + + extension MyNamespace { + public struct MyNestedStruct { + public func test() {} + } + } + """ + + @Test("Import: Nested type in extension MyNamespace { struct MyName {} }") + func test_nested_in_extension() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .error + + try st.analyze(file: "Fake.swift", text: class_interfaceFile) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + guard let ty = st.importedTypes["MyNamespace.MyNestedStruct"] else { + fatalError("Didn't import nested type!") + } + + + + } + +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift index 50a0ae264..919f857d0 100644 --- a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -13,20 +13,30 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import SwiftJavaConfigurationShared import Testing @Suite struct JNINestedTypesTests { let source1 = """ + """ public class A { public class B { public func g(c: C) {} + extension MyNamespace { public struct C { public func h(b: B) {} } } + """ + } + func test_nested_in_extension() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .error public func f(a: A, b: A.B, c: A.B.C) {} """ diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index f251766ac..de5ee774e 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -64,7 +64,7 @@ class Java2SwiftTests: XCTestCase { JavaClass.self, swiftTypeName: "MyJavaClass", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -115,10 +115,10 @@ class Java2SwiftTests: XCTestCase { MyArrayList.self, swiftTypeName: "JavaArrayList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.lang.reflect.Array": ("JavaArray", nil), - "java.util.List": ("JavaList", nil), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.lang.reflect.Array": SwiftTypeName(module: nil, name: "JavaArray"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ """ @@ -138,8 +138,8 @@ class Java2SwiftTests: XCTestCase { MyLinkedList.self, swiftTypeName: "JavaLinkedList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.util.List": ("JavaList", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), ], expectedChunks: [ """ @@ -155,9 +155,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.Redirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.Redirect.Type", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect.Type"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -195,9 +195,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.PBRedirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.PBRedirect.JavaType", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect.JavaType"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -248,9 +248,9 @@ class Java2SwiftTests: XCTestCase { MyObjects.self, swiftTypeName: "MyJavaObjects", translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.Supplier" : ("MySupplier", "JavaUtilFunction"), - "java.lang.String" : ("JavaString", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.Supplier" : SwiftTypeName(module: "JavaUtilFunction", name: "MySupplier"), + "java.lang.String" : SwiftTypeName(module: "SwiftJava", name: "JavaString"), ], expectedChunks: [ """ @@ -305,7 +305,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaString", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -380,9 +380,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.ClassLoader" : ("ClassLoader", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.ClassLoader" : SwiftTypeName(module: "SwiftJava", name: "ClassLoader"), + "java.net.URL" : SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -411,8 +411,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.net.URL": SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -440,9 +440,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaByte", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Number" : ("JavaNumber", "SwiftJava"), - "java.lang.Byte" : ("JavaByte", "SwiftJava"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Number": SwiftTypeName(module: "SwiftJava", name: "JavaNumber"), + "java.lang.Byte": SwiftTypeName(module: "SwiftJava", name: "JavaByte"), ], expectedChunks: [ "import SwiftJava", @@ -464,8 +464,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "MyJavaIntFunction", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ "import SwiftJava", @@ -487,11 +487,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Method", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -521,11 +521,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Constructor", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -555,10 +555,10 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "NIOByteBuffer", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.nio.Buffer": ("NIOBuffer", "JavaNio"), - "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaNio"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class": SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.nio.Buffer": SwiftTypeName(module: "JavaNio", name: "NIOBuffer"), + "java.nio.ByteBuffer": SwiftTypeName(module: "JavaNio", name: "NIOByteBuffer"), ], expectedChunks: [ "import JavaNio", @@ -643,9 +643,7 @@ func assertTranslatedClass( _ javaType: JavaClassType.Type, swiftTypeName: String, asClass: Bool = false, - translatedClasses: [ - String: (swiftType: String, swiftModule: String?) - ] = [:], + translatedClasses: [String: SwiftTypeName] = [:], nestedClasses: [String: [JavaClass]] = [:], expectedChunks: [String], file: StaticString = #filePath, @@ -659,7 +657,7 @@ func assertTranslatedClass( ) translator.translatedClasses = translatedClasses - translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil) + translator.translatedClasses[javaType.fullJavaClassName] = SwiftTypeName(module: nil, name: swiftTypeName) translator.nestedClasses = nestedClasses translator.startNewFile() diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index 220f1c613..e9cbb2240 100644 --- a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -19,10 +19,10 @@ final class JavaTranslatorValidationTests: XCTestCase { func testValidationError() throws { let translator = try JavaTranslator(swiftModuleName: "SwiftModule", environment: jvm.environment()) translator.translatedClasses = [ - "TestClass": ("Class1", "Module1"), - "TestClass2": ("Class1", "Module2"), - "TestClass3": ("Class1", "Module1"), - "TestClass4": ("Class1", nil) + "TestClass": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass2": SwiftTypeName(module: "Module2", name: "Class1"), + "TestClass3": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass4": SwiftTypeName(module: nil, name: "Class1") ] XCTAssertThrowsError(try translator.validateClassConfiguration()) { error in @@ -31,7 +31,7 @@ final class JavaTranslatorValidationTests: XCTestCase { switch validationError { case .multipleClassesMappedToSameName(let swiftToJavaMapping): XCTAssertEqual(swiftToJavaMapping, [ - JavaTranslator.SwiftToJavaMapping(swiftType: .init(swiftType: "Class1", swiftModule: "Module1"), + JavaTranslator.SwiftToJavaMapping(swiftType: .init(module: "Module1", name: "Class1"), javaTypes: ["TestClass", "TestClass3"]) ]) } diff --git a/Tests/SwiftJavaToolLibTests/TempFileTools.swift b/Tests/SwiftJavaToolLibTests/TempFileTools.swift new file mode 100644 index 000000000..5b133108d --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/TempFileTools.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Example demonstrating how to create a temporary file using Swift Foundation APIs +public class TempFile { + + public static func create( + suffix: String, + _ contents: String = "", + in tempDirectory: URL = FileManager.default.temporaryDirectory) throws -> URL { + let tempFileName = "tmp_\(UUID().uuidString).\(suffix)" + let tempFileURL = tempDirectory.appendingPathComponent(tempFileName) + + try contents.write(to: tempFileURL, atomically: true, encoding: .utf8) + + return tempFileURL + } + public static func delete(at fileURL: URL) throws { + try FileManager.default.removeItem(at: fileURL) + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift new file mode 100644 index 000000000..efe91628d --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -0,0 +1,211 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 + +class WrapJavaTests: XCTestCase { + + func testWrapJavaFromCompiledJavaSource() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class ExampleSimpleClass {} + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """ + ] + ) + } + + /* + /Users/ktoso/code/voldemort-swift-java/.build/plugins/outputs/voldemort-swift-java/VoldemortSwiftJava/destination/SwiftJavaPlugin/generated/CompressingStore.swift:6:30: error: reference to generic type 'AbstractStore' requires arguments in <...> + 4 | + 5 | @JavaClass("voldemort.store.compress.CompressingStore") + 6 | open class CompressingStore: AbstractStore { + | `- error: reference to generic type 'AbstractStore' requires arguments in <...> + 7 | @JavaMethod + 8 | open override func getCapability(_ arg0: StoreCapabilityType?) -> JavaObject! + +/Users/ktoso/code/voldemort-swift-java/.build/plugins/outputs/voldemort-swift-java/VoldemortSwiftJava/destination/SwiftJavaPlugin/generated/AbstractStore.swift:6:12: note: generic type 'AbstractStore' declared here + 4 | + 5 | @JavaClass("voldemort.store.AbstractStore", implements: Store.self) + 6 | open class AbstractStore: JavaObject { + | `- note: generic type 'AbstractStore' declared here + 7 | @JavaMethod + 8 | @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) + */ + func testGenericSuperclass() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class ByteArray {} + // class CompressingStore extends AbstractStore {} + abstract class AbstractStore implements Store {} + interface Store {} + + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ByteArray", + "com.example.Store", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ByteArray") + open class ByteArray: JavaObject { + """, + """ + @JavaInterface("com.example.Store") + public struct Store { + """ + ] + ) + } +} + +fileprivate func createTemporaryDirectory(in directory: URL) throws -> URL { + let uuid = UUID().uuidString + let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") + + try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + return resolverDirectoryURL +} + +/// Returns the directory that should be added to the classpath of the JVM to analyze the sources. +func compileJava(_ sourceText: String) async throws -> URL { + let sourceFile = try TempFile.create(suffix: "java", sourceText) + + let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) + + let javacProcess = try await _Subprocess.run( + .path("/usr/bin/javac"), + arguments: [ + "-d", classesDirectory.path, // output directory for .class files + sourceFile.path + ], + output: .string(limit: Int.max, encoding: UTF8.self), + error: .string(limit: Int.max, encoding: UTF8.self) + ) + + // Check if compilation was successful + guard javacProcess.terminationStatus.isSuccess else { + let outString = javacProcess.standardOutput ?? "" + let errString = javacProcess.standardError ?? "" + fatalError("javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\n" + + "OUT: \(outString)\n" + + "ERROR: \(errString)") + } + + return classesDirectory +} + +/// Translate a Java class and assert that the translated output contains +/// each of the expected "chunks" of text. +func assertWrapJavaOutput( + javaClassNames: [String], + classpath: [URL], + expectedChunks: [String], + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + let jvm = try JavaVirtualMachine.shared( + classpath: classpath.map(\.path), + replace: false + ) + + let environment = try jvm.environment() + let translator = JavaTranslator( + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + let classLoader = try! JavaClass(environment: environment) + .getSystemClassLoader()! + + + // FIXME: deduplicate this + translator.startNewFile() + + var swiftCompleteOutputText = "" + + var javaClasses: [JavaClass] = [] + for javaClassName in javaClassNames { + guard let javaClass = try classLoader.loadClass(javaClassName) else { + fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") + } + javaClasses.append(javaClass) + + // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!! + // TODO: especially because nested classes + // WrapJavaCommand(). + + let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName + .defaultSwiftNameForJavaClass + translator.translatedClasses[javaClassName] = + .init(module: nil, name: swiftUnqualifiedName) + + try translator.validateClassConfiguration() + + let swiftClassDecls = try translator.translateClass(javaClass) + let importDecls = translator.getImportDecls() + + let swiftFileText = + """ + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + + """ + swiftCompleteOutputText += swiftFileText + } + + for expectedChunk in expectedChunks { + if swiftCompleteOutputText.contains(expectedChunk) { + continue + } + + XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftCompleteOutputText)'", + file: file, line: line) + } + + print("=============================================") + print(swiftCompleteOutputText) +} \ No newline at end of file From d1f29d8a7d21dc704f807701522b49e415ee45a0 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 29 Sep 2025 12:44:38 +0100 Subject: [PATCH 02/28] WIP: need the parameterized parent types --- Sources/SwiftJava/generated/JavaClass.swift | 3 +++ .../JavaReflectParameterizedType.swift | 8 ++++++ Sources/SwiftJava/generated/JavaType.swift | 8 ++++++ Sources/SwiftJava/swift-java.config | 2 ++ .../JavaClassTranslator.swift | 2 ++ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 27 ++++++++++++++----- 6 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 Sources/SwiftJava/generated/JavaReflectParameterizedType.swift create mode 100644 Sources/SwiftJava/generated/JavaType.swift diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 0f1af1cd0..7227e8312 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -30,6 +30,9 @@ open class JavaClass: JavaObject { @JavaMethod open func isHidden() -> Bool + @JavaMethod + open func getGenericSuperclass() -> JavaType + @JavaMethod open func getSuperclass() -> JavaClass! diff --git a/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift new file mode 100644 index 000000000..219cc9431 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift @@ -0,0 +1,8 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.reflect.Type") +open class JavaReflectParameterizedType: JavaObject { + @JavaMethod + open func getTypeName() -> String +} \ No newline at end of file diff --git a/Sources/SwiftJava/generated/JavaType.swift b/Sources/SwiftJava/generated/JavaType.swift new file mode 100644 index 000000000..8a16f661a --- /dev/null +++ b/Sources/SwiftJava/generated/JavaType.swift @@ -0,0 +1,8 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.reflect.Type") +open class JavaReflectType: JavaObject { + @JavaMethod + open func getTypeName() -> String +} \ No newline at end of file diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index b45671a7a..373aa2cea 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -5,6 +5,8 @@ "java.lang.Byte" : "JavaByte", "java.lang.Character" : "JavaCharacter", "java.lang.Class" : "JavaClass", + "java.lang.reflect.Type" : "JavaType", + "java.lang.reflect.ParameterizedType" : "JavaParameterizedType", "java.lang.ClassLoader" : "JavaClassLoader", "java.lang.Double" : "JavaDouble", "java.lang.Error" : "JavaError", diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 89878805f..8ad89f7ad 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -113,6 +113,8 @@ struct JavaClassTranslator { /// Prepare translation for the given Java class (or interface). init(javaClass: JavaClass, translator: JavaTranslator) throws { let fullName = javaClass.getName() + print("TRANSLATE = \(fullName)") + self.javaClass = javaClass self.translator = translator self.translateAsClass = translator.translateAsClass && !javaClass.isInterface() diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index efe91628d..c049583bf 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -15,6 +15,7 @@ @_spi(Testing) import SwiftJava import SwiftJavaToolLib import JavaUtilJar +import SwiftJavaShared import _Subprocess import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -69,16 +70,19 @@ class WrapJavaTests: XCTestCase { package com.example; class ByteArray {} - // class CompressingStore extends AbstractStore {} - abstract class AbstractStore implements Store {} - interface Store {} + class CompressingStore extends AbstractStore {} + abstract class AbstractStore {} // implements Store {} + // interface Store {} """) try assertWrapJavaOutput( javaClassNames: [ "com.example.ByteArray", - "com.example.Store", + // TODO: what if we visit in other order, does the wrap-java handle it + // "com.example.Store", + "com.example.AbstractStore", + "com.example.CompressingStore", ], classpath: [classpathURL], expectedChunks: [ @@ -93,6 +97,10 @@ class WrapJavaTests: XCTestCase { """ @JavaInterface("com.example.Store") public struct Store { + """, + """ + @JavaClass("com.example.CompressingStore") + open class CompressingStore: AbstractStore { """ ] ) @@ -133,6 +141,7 @@ func compileJava(_ sourceText: String) async throws -> URL { "ERROR: \(errString)") } + print("Compiled java sources to: \(classesDirectory)") return classesDirectory } @@ -189,10 +198,11 @@ func assertWrapJavaOutput( let swiftFileText = """ + // --------------------------------------------------------------------------- // Auto-generated by Java-to-Swift wrapper generator. \(importDecls.map { $0.description }.joined()) \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) - + \n """ swiftCompleteOutputText += swiftFileText } @@ -202,10 +212,13 @@ func assertWrapJavaOutput( continue } - XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftCompleteOutputText)'", + XCTFail("Expected chunk: \n" + + "\(expectedChunk.yellow)" + + "\n" + + "not found in:\n" + + "\(swiftCompleteOutputText)", file: file, line: line) } - print("=============================================") print(swiftCompleteOutputText) } \ No newline at end of file From 45a4a3eac892af0dd1d09eb05c0b11c116fa7389 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 29 Sep 2025 14:32:07 +0100 Subject: [PATCH 03/28] SwiftJava: regenerate and add JavaType and JavaParameterizedType handle open class CompressingStore: AbstractStore { --- Sources/SwiftJava/generated/JavaClass.swift | 37 +++++++++-------- .../SwiftJava/generated/JavaClassLoader.swift | 8 ++-- Sources/SwiftJava/generated/JavaInteger.swift | 29 +++++++------ Sources/SwiftJava/generated/JavaLong.swift | 18 ++++---- .../JavaReflectParameterizedType.swift | 17 ++++++-- .../SwiftJava/generated/JavaReflectType.swift | 8 ++++ Sources/SwiftJava/generated/JavaType.swift | 8 ---- Sources/SwiftJava/swift-java.config | 4 +- .../JavaClassTranslator.swift | 41 +++++++++++++++---- .../JavaParameterizedType.swift | 28 +++++++++++++ .../JavaTranslator+Validation.swift | 8 ++++ Sources/SwiftJavaToolLib/JavaTranslator.swift | 4 +- .../SwiftJavaToolLibTests/WrapJavaTests.swift | 20 ++++----- 13 files changed, 154 insertions(+), 76 deletions(-) create mode 100644 Sources/SwiftJava/generated/JavaReflectType.swift delete mode 100644 Sources/SwiftJava/generated/JavaType.swift create mode 100644 Sources/SwiftJavaToolLib/JavaParameterizedType.swift diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 7227e8312..157f53890 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import CSwiftJavaJNI -@JavaClass("java.lang.Class") +@JavaClass("java.lang.Class", implements: JavaReflectType.self) open class JavaClass: JavaObject { @JavaMethod open func getName() -> String @@ -30,9 +30,6 @@ open class JavaClass: JavaObject { @JavaMethod open func isHidden() -> Bool - @JavaMethod - open func getGenericSuperclass() -> JavaType - @JavaMethod open func getSuperclass() -> JavaClass! @@ -55,13 +52,16 @@ open class JavaClass: JavaObject { open func isRecord() -> Bool @JavaMethod - open func getClassLoader() -> JavaClassLoader! + open func isSealed() -> Bool @JavaMethod - open func newInstance() throws -> JavaObject! + open func getInterfaces() -> [JavaClass?] @JavaMethod - open func getInterfaces() -> [JavaClass?] + open func getClassLoader() -> JavaClassLoader! + + @JavaMethod + open func newInstance() throws -> JavaObject! @JavaMethod open func isMemberClass() -> Bool @@ -73,7 +73,7 @@ open class JavaClass: JavaObject { open func isAnonymousClass() -> Bool @JavaMethod - open func getEnclosingClass() throws -> JavaClass! + open func getEnclosingClass() -> JavaClass! @JavaMethod open func arrayType() -> JavaClass! @@ -84,6 +84,9 @@ open class JavaClass: JavaObject { @JavaMethod open func getCanonicalName() -> String + @JavaMethod + open func getDeclaredClasses() -> [JavaClass?] + @JavaMethod open func getPackageName() -> String @@ -105,11 +108,17 @@ open class JavaClass: JavaObject { @JavaMethod open func isSynthetic() -> Bool + @JavaMethod + open func getGenericSuperclass() -> JavaReflectType! + + @JavaMethod + open func getGenericInterfaces() -> [JavaReflectType?] + @JavaMethod open func getSigners() -> [JavaObject?] @JavaMethod - open func getDeclaringClass() throws -> JavaClass! + open func getDeclaringClass() -> JavaClass! @JavaMethod open func getTypeName() -> String @@ -117,9 +126,6 @@ open class JavaClass: JavaObject { @JavaMethod open func getClasses() -> [JavaClass?] - @JavaMethod - open func getDeclaredClasses() throws -> [JavaClass?] - @JavaMethod open func getEnumConstants() -> [JavaObject?] @@ -131,16 +137,13 @@ open class JavaClass: JavaObject { @JavaMethod open func getNestMembers() -> [JavaClass?] - - @JavaMethod - open func isSealed() -> Bool } extension JavaClass { @JavaStaticMethod - public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod - public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod public func forPrimitiveName(_ arg0: String) -> JavaClass! where ObjectType == JavaClass diff --git a/Sources/SwiftJava/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift index 349cba8db..0cd64aa15 100644 --- a/Sources/SwiftJava/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -7,10 +7,10 @@ open class JavaClassLoader: JavaObject { open func getName() -> String @JavaMethod - open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! + open func loadClass(_ arg0: String) throws -> JavaClass! @JavaMethod - open func loadClass(_ arg0: String) throws -> JavaClass! + open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! @JavaMethod open func setSigners(_ arg0: JavaClass?, _ arg1: [JavaObject?]) @@ -22,10 +22,10 @@ open class JavaClassLoader: JavaObject { open func findLoadedClass(_ arg0: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String) throws -> JavaClass! + open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! + open func findClass(_ arg0: String) throws -> JavaClass! @JavaMethod open func resolveClass(_ arg0: JavaClass?) diff --git a/Sources/SwiftJava/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift index 948000373..df57ba665 100644 --- a/Sources/SwiftJava/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -3,7 +3,6 @@ import CSwiftJavaJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { - @JavaMethod @_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil) @@ -121,10 +120,10 @@ extension JavaClass { public func valueOf(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! + public func valueOf(_ arg0: Int32) -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: Int32) -> JavaInteger! + public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! @JavaStaticMethod public func toHexString(_ arg0: Int32) -> String @@ -133,31 +132,37 @@ extension JavaClass { public func decode(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func parseInt(_ arg0: String) throws -> Int32 + public func parseInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func parseInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func toUnsignedLong(_ arg0: Int32) -> Int64 + public func parseInt(_ arg0: String) throws -> Int32 @JavaStaticMethod - public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 + public func highestOneBit(_ arg0: Int32) -> Int32 @JavaStaticMethod - public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String + public func toUnsignedLong(_ arg0: Int32) -> Int64 + + @JavaStaticMethod + public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 @JavaStaticMethod public func toUnsignedString(_ arg0: Int32) -> String @JavaStaticMethod - public func parseUnsignedInt(_ arg0: String) throws -> Int32 + public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String @JavaStaticMethod public func parseUnsignedInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! + public func parseUnsignedInt(_ arg0: String) throws -> Int32 + + @JavaStaticMethod + public func parseUnsignedInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func getInteger(_ arg0: String, _ arg1: Int32) -> JavaInteger! @@ -166,13 +171,13 @@ extension JavaClass { public func getInteger(_ arg0: String) -> JavaInteger! @JavaStaticMethod - public func toOctalString(_ arg0: Int32) -> String + public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! @JavaStaticMethod - public func toBinaryString(_ arg0: Int32) -> String + public func toOctalString(_ arg0: Int32) -> String @JavaStaticMethod - public func highestOneBit(_ arg0: Int32) -> Int32 + public func toBinaryString(_ arg0: Int32) -> String @JavaStaticMethod public func lowestOneBit(_ arg0: Int32) -> Int32 diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index a986e9ef8..7ea8fc09e 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -126,10 +126,10 @@ extension JavaClass { public func compare(_ arg0: Int64, _ arg1: Int64) -> Int32 @JavaStaticMethod - public func valueOf(_ arg0: String) throws -> JavaLong! + public func valueOf(_ arg0: Int64) -> JavaLong! @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> JavaLong! + public func valueOf(_ arg0: String) throws -> JavaLong! @JavaStaticMethod public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaLong! @@ -140,6 +140,9 @@ extension JavaClass { @JavaStaticMethod public func decode(_ arg0: String) throws -> JavaLong! + @JavaStaticMethod + public func highestOneBit(_ arg0: Int64) -> Int64 + @JavaStaticMethod public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64 @@ -155,9 +158,6 @@ extension JavaClass { @JavaStaticMethod public func toBinaryString(_ arg0: Int64) -> String - @JavaStaticMethod - public func highestOneBit(_ arg0: Int64) -> Int64 - @JavaStaticMethod public func lowestOneBit(_ arg0: Int64) -> Int64 @@ -168,20 +168,20 @@ extension JavaClass { public func rotateRight(_ arg0: Int64, _ arg1: Int32) -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: String) throws -> Int64 + public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseUnsignedLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseUnsignedLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: String) throws -> Int64 + public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 } diff --git a/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift index 219cc9431..a08016445 100644 --- a/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift +++ b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift @@ -1,8 +1,17 @@ // Auto-generated by Java-to-Swift wrapper generator. import CSwiftJavaJNI -@JavaClass("java.reflect.Type") -open class JavaReflectParameterizedType: JavaObject { +@JavaInterface("java.lang.reflect.ParameterizedType", extends: JavaReflectType.self) +public struct JavaReflectParameterizedType { @JavaMethod - open func getTypeName() -> String -} \ No newline at end of file + public func getOwnerType() -> JavaReflectType! + + @JavaMethod + public func getRawType() -> JavaReflectType! + + @JavaMethod + public func getActualTypeArguments() -> [JavaReflectType?] + + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/generated/JavaReflectType.swift b/Sources/SwiftJava/generated/JavaReflectType.swift new file mode 100644 index 000000000..fdf3b5726 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectType.swift @@ -0,0 +1,8 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.Type") +public struct JavaReflectType { + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/generated/JavaType.swift b/Sources/SwiftJava/generated/JavaType.swift deleted file mode 100644 index 8a16f661a..000000000 --- a/Sources/SwiftJava/generated/JavaType.swift +++ /dev/null @@ -1,8 +0,0 @@ -// Auto-generated by Java-to-Swift wrapper generator. -import CSwiftJavaJNI - -@JavaClass("java.reflect.Type") -open class JavaReflectType: JavaObject { - @JavaMethod - open func getTypeName() -> String -} \ No newline at end of file diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index 373aa2cea..d07ff1620 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -5,8 +5,8 @@ "java.lang.Byte" : "JavaByte", "java.lang.Character" : "JavaCharacter", "java.lang.Class" : "JavaClass", - "java.lang.reflect.Type" : "JavaType", - "java.lang.reflect.ParameterizedType" : "JavaParameterizedType", + "java.lang.reflect.Type" : "JavaReflectType", + "java.lang.reflect.ParameterizedType" : "JavaReflectParameterizedType", "java.lang.ClassLoader" : "JavaClassLoader", "java.lang.Double" : "JavaDouble", "java.lang.Error" : "JavaError", diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 8ad89f7ad..5fde1fc4d 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -47,7 +47,7 @@ struct JavaClassTranslator { let effectiveJavaSuperclass: JavaClass? /// The Swift name of the superclass. - let swiftSuperclass: String? + let swiftSuperclass: SwiftJavaParameterizedType? /// The Swift names of the interfaces that this class implements. let swiftInterfaces: [String] @@ -114,7 +114,7 @@ struct JavaClassTranslator { init(javaClass: JavaClass, translator: JavaTranslator) throws { let fullName = javaClass.getName() print("TRANSLATE = \(fullName)") - + self.javaClass = javaClass self.translator = translator self.translateAsClass = translator.translateAsClass && !javaClass.isInterface() @@ -128,30 +128,46 @@ struct JavaClassTranslator { self.javaTypeParameters = javaClass.getTypeParameters().compactMap { $0 } self.nestedClasses = translator.nestedClasses[fullName] ?? [] - // Superclass. + // Superclass, incl parameter types (if any) if !javaClass.isInterface() { var javaSuperclass = javaClass.getSuperclass() - var swiftSuperclass: String? = nil + var javaGenericSuperclass: JavaReflectType? = javaClass.getGenericSuperclass() + var swiftSuperclassName: String? = nil + var swiftSuperclassTypeArgs: [String] = [] while let javaSuperclassNonOpt = javaSuperclass { do { - swiftSuperclass = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + swiftSuperclassName = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + if let javaGenericSuperclass = javaGenericSuperclass?.as(JavaReflectParameterizedType.self) { + print("javaGenericSuperclass = \(javaGenericSuperclass)") + for typeArg in javaGenericSuperclass.getActualTypeArguments() { + let javaTypeArgName = typeArg?.getTypeName() ?? "" + if let swiftTypeArgName = self.translator.translatedClasses[javaTypeArgName] { + swiftSuperclassTypeArgs.append(swiftTypeArgName.qualifiedName) + } else { + swiftSuperclassTypeArgs.append("/* MISSING MAPPING FOR */ \(javaTypeArgName)") + } + } + } break } catch { translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") } javaSuperclass = javaSuperclassNonOpt.getSuperclass() + javaGenericSuperclass = javaClass.getGenericSuperclass() } + print("swiftSuperclassTypeArgs = \(swiftSuperclassTypeArgs)") + self.effectiveJavaSuperclass = javaSuperclass - self.swiftSuperclass = swiftSuperclass + self.swiftSuperclass = SwiftJavaParameterizedType( + name: swiftSuperclassName, + typeArguments: swiftSuperclassTypeArgs) } else { self.effectiveJavaSuperclass = nil self.swiftSuperclass = nil } - print("[\(fullName)] SWUFT SUPER CLASS = \(swiftSuperclass)") - // Interfaces. self.swiftInterfaces = javaClass.getGenericInterfaces().compactMap { (javaType) -> String? in guard let javaType else { @@ -333,7 +349,14 @@ extension JavaClassTranslator { let inheritanceClause: String if translateAsClass { extends = "" - inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? "" + inheritanceClause = + if let swiftSuperclass, swiftSuperclass.typeArguments.isEmpty { + ": \(swiftSuperclass.name)" + } else if let swiftSuperclass { + ": \(swiftSuperclass.name)<\(swiftSuperclass.typeArguments.joined(separator: ", "))>" + } else { + "" + } } else { extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" inheritanceClause = "" diff --git a/Sources/SwiftJavaToolLib/JavaParameterizedType.swift b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift new file mode 100644 index 000000000..c391ae41a --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// E.g. `Another` +struct SwiftJavaParameterizedType { + let name: String + let typeArguments: [String] + + init?(name: String?, typeArguments: [String]) { + guard let name else { + return nil + } + + self.name = name + self.typeArguments = typeArguments + } +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index e4e39839d..3a6468d57 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -23,6 +23,14 @@ package struct SwiftTypeName: Hashable, CustomStringConvertible { self.swiftType = name } + package var qualifiedName: String { + if let swiftModule { + "\(swiftModule).\(swiftType)" + } else { + "\(swiftType)" + } + } + package var description: String { if let swiftModule { "`\(swiftModule)/\(swiftType)`" diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 689e2cea4..c17fb6f14 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -19,6 +19,7 @@ import SwiftBasicFormat import SwiftSyntax import SwiftJavaConfigurationShared import SwiftSyntaxBuilder +import Foundation /// Utility that translates Java classes into Swift source code to access /// those Java classes. @@ -36,7 +37,8 @@ package class JavaTranslator { /// A mapping from the name of each known Java class to the corresponding /// Swift type name and its Swift module. package var translatedClasses: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ - "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject") + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "byte[]": SwiftTypeName(module: nil, name: "[UInt8]") ] /// A mapping from the name of each known Java class with the Swift value type diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index c049583bf..ecf4ac50d 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -94,13 +94,13 @@ class WrapJavaTests: XCTestCase { @JavaClass("com.example.ByteArray") open class ByteArray: JavaObject { """, - """ - @JavaInterface("com.example.Store") - public struct Store { - """, + // """ + // @JavaInterface("com.example.Store") + // public struct Store { + // """, """ @JavaClass("com.example.CompressingStore") - open class CompressingStore: AbstractStore { + open class CompressingStore: AbstractStore { """ ] ) @@ -213,11 +213,11 @@ func assertWrapJavaOutput( } XCTFail("Expected chunk: \n" + - "\(expectedChunk.yellow)" + - "\n" + - "not found in:\n" + - "\(swiftCompleteOutputText)", - file: file, line: line) + "\(expectedChunk.yellow)" + + "\n" + + "not found in:\n" + + "\(swiftCompleteOutputText)", + file: file, line: line) } print(swiftCompleteOutputText) From 9283f576dddb66df694b6de59e76967ee5c4df39 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 8 Oct 2025 11:35:23 +0900 Subject: [PATCH 04/28] Rename "javakitvm" to just JVM; and add JavaReflectArray --- .../JavaVirtualMachine.swift | 0 .../{JavaKitVM => JVM}/LockedState.swift | 0 .../ThreadLocalStorage.swift | 0 .../generated/JavaReflectArray.swift | 71 +++++++++++++++++++ 4 files changed, 71 insertions(+) rename Sources/SwiftJava/{JavaKitVM => JVM}/JavaVirtualMachine.swift (100%) rename Sources/SwiftJava/{JavaKitVM => JVM}/LockedState.swift (100%) rename Sources/SwiftJava/{JavaKitVM => JVM}/ThreadLocalStorage.swift (100%) create mode 100644 Sources/SwiftJava/generated/JavaReflectArray.swift diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift rename to Sources/SwiftJava/JVM/JavaVirtualMachine.swift diff --git a/Sources/SwiftJava/JavaKitVM/LockedState.swift b/Sources/SwiftJava/JVM/LockedState.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/LockedState.swift rename to Sources/SwiftJava/JVM/LockedState.swift diff --git a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JVM/ThreadLocalStorage.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift rename to Sources/SwiftJava/JVM/ThreadLocalStorage.swift diff --git a/Sources/SwiftJava/generated/JavaReflectArray.swift b/Sources/SwiftJava/generated/JavaReflectArray.swift new file mode 100644 index 000000000..4cae1202d --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectArray.swift @@ -0,0 +1,71 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.reflect.Array") +open class JavaReflectArray: JavaObject { + +} +extension JavaClass { + @JavaStaticMethod + public func get(_ arg0: JavaObject?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func getLength(_ arg0: JavaObject?) throws -> Int32 + + @JavaStaticMethod + public func getBoolean(_ arg0: JavaObject?, _ arg1: Int32) throws -> Bool + + @JavaStaticMethod + public func getByte(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int8 + + @JavaStaticMethod + public func getShort(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int16 + + @JavaStaticMethod + public func getChar(_ arg0: JavaObject?, _ arg1: Int32) throws -> UInt16 + + @JavaStaticMethod + public func getInt(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int32 + + @JavaStaticMethod + public func getLong(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int64 + + @JavaStaticMethod + public func getFloat(_ arg0: JavaObject?, _ arg1: Int32) throws -> Float + + @JavaStaticMethod + public func getDouble(_ arg0: JavaObject?, _ arg1: Int32) throws -> Double + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: [Int32]) throws -> JavaObject! + + @JavaStaticMethod + public func set(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: JavaObject?) throws + + @JavaStaticMethod + public func setBoolean(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Bool) throws + + @JavaStaticMethod + public func setByte(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int8) throws + + @JavaStaticMethod + public func setChar(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: UInt16) throws + + @JavaStaticMethod + public func setShort(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int16) throws + + @JavaStaticMethod + public func setInt(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int32) throws + + @JavaStaticMethod + public func setLong(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int64) throws + + @JavaStaticMethod + public func setFloat(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Float) throws + + @JavaStaticMethod + public func setDouble(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Double) throws +} From fe84b0acf35d86a0aaca797797884b033afa814d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 30 Oct 2025 21:17:03 +0900 Subject: [PATCH 05/28] fix throwable to print class type and message --- Sources/SwiftJava/Exceptions/Throwable+Error.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJava/Exceptions/Throwable+Error.swift b/Sources/SwiftJava/Exceptions/Throwable+Error.swift index fbf8393dd..e51e12056 100644 --- a/Sources/SwiftJava/Exceptions/Throwable+Error.swift +++ b/Sources/SwiftJava/Exceptions/Throwable+Error.swift @@ -15,7 +15,7 @@ // Translate all Java Throwable instances in a Swift error. extension Throwable: Error, CustomStringConvertible { public var description: String { - return toString() + "\(getClass().getCanonicalName())(\(getMessage()))" } } From 8da01dee2b1212921e098c7b57841345018e30dd Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 30 Oct 2025 21:55:59 +0900 Subject: [PATCH 06/28] work towards handling generic methods with generic return type --- Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 31 +++++++++ .../JavaLangReflect/Method+Utilities.swift | 26 ++++++-- .../Commands/WrapJavaCommand.swift | 1 + .../JavaClassTranslator.swift | 24 +++++-- Sources/SwiftJavaToolLib/JavaTranslator.swift | 5 +- .../Java2SwiftTests.swift | 3 + .../JavaTranslatorValidationTests.swift | 3 +- .../SwiftJavaToolLibTests/WrapJavaTests.swift | 64 +++++++++++++++++++ 8 files changed, 146 insertions(+), 11 deletions(-) diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 57641eff9..7908932d1 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -165,6 +165,11 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("No dependencies to fetch for target \(sourceModule.name)") } + // Add all the core Java stdlib modules as --depends-on + let javaStdlibModules = getExtractedJavaStdlibModules() + log("Include Java standard library SwiftJava modules: \(javaStdlibModules)") + arguments += javaStdlibModules.flatMap { ["--depends-on", $0] } + if !outputSwiftFiles.isEmpty { arguments += [ configFile.path(percentEncoded: false) ] @@ -236,3 +241,29 @@ extension SwiftJavaBuildToolPlugin { outputDirectory(context: context, generated: generated).appending(path: filename) } } + +func getExtractedJavaStdlibModules() -> [String] { + let fileManager = FileManager.default + let sourcesPath = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent("Sources") + .appendingPathComponent("JavaStdlib") + + guard let stdlibDirContents = try? fileManager.contentsOfDirectory( + at: sourcesPath, + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ) else { + return [] + } + + return stdlibDirContents.compactMap { url in + guard let resourceValues = try? url.resourceValues(forKeys: [.isDirectoryKey]), + let isDirectory = resourceValues.isDirectory, + isDirectory else { + return nil + } + return url.lastPathComponent + }.sorted() +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift index 71ee864ce..ecc11b507 100644 --- a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift @@ -13,23 +13,41 @@ //===----------------------------------------------------------------------===// extension Method { + /// Whether this is a 'public' method. public var isPublic: Bool { - return (getModifiers() & 1) != 0 + return (getModifiers() & 0x00000001) != 0 + } + + /// Whether this is a 'private' method. + public var isPrivate: Bool { + return (getModifiers() & 0x00000002) != 0 } /// Whether this is a 'protected' method. public var isProtected: Bool { - return (getModifiers() & 4) != 0 + return (getModifiers() & 0x00000004) != 0 + } + + /// Whether this is a 'package' method. + /// + /// The "default" access level in Java is 'package', it is signified by lack of a different access modifier. + public var isPackage: Bool { + return !isPublic && !isPrivate && !isProtected } /// Whether this is a 'static' method. public var isStatic: Bool { - return (getModifiers() & 0x08) != 0 + return (getModifiers() & 0x00000008) != 0 } /// Whether this is a 'native' method. public var isNative: Bool { - return (getModifiers() & 256) != 0 + return (getModifiers() & 0x00000100) != 0 + } + + /// Whether this is a 'final' method. + public var isFinal: Bool { + return (getModifiers() & 0x00000010) != 0 } } diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index a1e179f2a..600337208 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -118,6 +118,7 @@ extension SwiftJava.WrapJavaCommand { environment: JNIEnvironment ) throws { let translator = JavaTranslator( + config: config, swiftModuleName: effectiveSwiftModule, environment: environment, translateAsClass: true diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 5fde1fc4d..c9ee26e7a 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -15,6 +15,7 @@ import SwiftJava import JavaLangReflect import SwiftSyntax +import SwiftJavaConfigurationShared /// Utility type that translates a single Java class into its corresponding /// Swift type and any additional helper types or functions. @@ -212,8 +213,9 @@ struct JavaClassTranslator { for method in methods { guard let method else { continue } - // Only look at public and protected methods here. - guard method.isPublic || method.isProtected else { continue } + guard shouldExtract(method: method) else { + continue + } // Skip any methods that are expected to be implemented in Swift. We will // visit them in the second pass, over the *declared* methods, because @@ -246,6 +248,20 @@ struct JavaClassTranslator { /// MARK: Collection of Java class members. extension JavaClassTranslator { + + /// Determines whether a method should be extracted for translation. + /// Only look at public and protected methods here. + private func shouldExtract(method: Method) -> Bool { + switch self.translator.config.effectiveMinimumInputAccessLevelMode { + case .internal: + return method.isPublic || method.isProtected || method.isPackage + case .package: + return method.isPublic || method.isProtected || method.isPackage + case .public: + return method.isPublic || method.isProtected + } + } + /// Add a field to the appropriate lists(s) for later translation. private mutating func addField(_ field: Field) { // Static fields go into a separate list. @@ -805,9 +821,7 @@ extension JavaClassTranslator { continue } - // Ignore non-public, non-protected methods because they would not - // have been render into the Swift superclass. - if !overriddenMethod.isPublic && !overriddenMethod.isProtected { + guard shouldExtract(method: overriddenMethod) else { continue } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index c17fb6f14..66bac7bac 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -24,6 +24,8 @@ import Foundation /// Utility that translates Java classes into Swift source code to access /// those Java classes. package class JavaTranslator { + let config: Configuration + /// The name of the Swift module that we are translating into. let swiftModuleName: String @@ -68,11 +70,13 @@ package class JavaTranslator { package var nestedClasses: [String: [JavaClass]] = [:] package init( + config: Configuration, swiftModuleName: String, environment: JNIEnvironment, translateAsClass: Bool = false, format: BasicFormat = JavaTranslator.defaultFormat ) { + self.config = config self.swiftModuleName = swiftModuleName self.environment = environment self.translateAsClass = translateAsClass @@ -259,7 +263,6 @@ extension JavaTranslator { return translated.swiftType } - print("DEBUG >>> Not translated: \(name)") throw TranslationError.untranslatedJavaClass(name) } } diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index de5ee774e..815e7fb4e 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -14,6 +14,7 @@ @_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared import SwiftJavaToolLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -643,6 +644,7 @@ func assertTranslatedClass( _ javaType: JavaClassType.Type, swiftTypeName: String, asClass: Bool = false, + config: Configuration = Configuration(), translatedClasses: [String: SwiftTypeName] = [:], nestedClasses: [String: [JavaClass]] = [:], expectedChunks: [String], @@ -651,6 +653,7 @@ func assertTranslatedClass( ) throws { let environment = try jvm.environment() let translator = JavaTranslator( + config: config, swiftModuleName: "SwiftModule", environment: environment, translateAsClass: asClass diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index e9cbb2240..558add20d 100644 --- a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -14,10 +14,11 @@ import SwiftJavaToolLib import XCTest +import SwiftJavaConfigurationShared final class JavaTranslatorValidationTests: XCTestCase { func testValidationError() throws { - let translator = try JavaTranslator(swiftModuleName: "SwiftModule", environment: jvm.environment()) + let translator = try JavaTranslator(config: Configuration(), swiftModuleName: "SwiftModule", environment: jvm.environment()) translator.translatedClasses = [ "TestClass": SwiftTypeName(module: "Module1", name: "Class1"), "TestClass2": SwiftTypeName(module: "Module2", name: "Class1"), diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index ecf4ac50d..f8c9c95a7 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -16,6 +16,7 @@ import SwiftJavaToolLib import JavaUtilJar import SwiftJavaShared +import SwiftJavaConfigurationShared import _Subprocess import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -47,6 +48,63 @@ class WrapJavaTests: XCTestCase { ) } + func testWrapJavaGenericMethod() async throws { + do { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair get( + String name, + Item key, + Item value + ) { + return null; + } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getStore(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> BDStore! + """ + ] + ) + } catch let error as Throwable { + error.printStackTrace() + XCTFail("error: \(error)") + } + } + /* /Users/ktoso/code/voldemort-swift-java/.build/plugins/outputs/voldemort-swift-java/VoldemortSwiftJava/destination/SwiftJavaPlugin/generated/CompressingStore.swift:6:30: error: reference to generic type 'AbstractStore' requires arguments in <...> 4 | @@ -65,6 +123,8 @@ class WrapJavaTests: XCTestCase { 8 | @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) */ func testGenericSuperclass() async throws { + return // FIXME: we need this + let classpathURL = try await compileJava( """ package com.example; @@ -160,8 +220,12 @@ func assertWrapJavaOutput( replace: false ) + var config = Configuration() + config.minimumInputAccessLevelMode = .package + let environment = try jvm.environment() let translator = JavaTranslator( + config: config, swiftModuleName: "SwiftModule", environment: environment, translateAsClass: true) From 7608b7a405616b58c8de4eee00eccbb1af0141a5 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 30 Oct 2025 22:34:38 +0900 Subject: [PATCH 07/28] handle the return type being the generic type --- .../generated/Executable.swift | 4 +-- .../JavaLangReflect/generated/Type.swift | 9 +++++ .../JavaClassTranslator.swift | 34 +++++++++++++++---- Sources/SwiftJavaToolLib/JavaTranslator.swift | 18 ++++++++++ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 17 +++++----- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 3a6df8eab..94b03b8df 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -11,8 +11,8 @@ open class Executable: AccessibleObject { @JavaMethod open func getModifiers() -> Int32 - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] + // @JavaMethod // FIXME why does this clash with Method.getTypeParameters? + // open func getTypeParameters() -> [TypeVariable?] @JavaMethod open func getParameterTypes() -> [JavaClass?] diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index ff52b41a7..2e85c3842 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -6,4 +6,13 @@ import CSwiftJavaJNI public struct Type { @JavaMethod public func getTypeName() -> String + + @JavaMethod + public func toString() -> String +} + +extension Type: CustomStringConvertible { + public var description: String { + "JavaLangReflect.Type(\(self.toString()))" + } } diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index c9ee26e7a..3d021e2c4 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -576,21 +576,41 @@ extension JavaClassTranslator { package func renderMethod( _ javaMethod: Method, implementedInSwift: Bool, - genericParameterClause: String = "", + genericParameterClause __genericParameterClause: String = "", // FIXME: why is this a string, fix this... whereClause: String = "" ) throws -> DeclSyntax { + // Map the generic params on the method. + let typeParameters = javaMethod.getTypeParameters() + var genericParameterClauseStr = __genericParameterClause + if typeParameters.count(where: {$0 != nil }) > 0 { + genericParameterClauseStr = "<" + genericParameterClauseStr += typeParameters.map { typeParam in + // FIXME: determine if it is some other constraint + "\(typeParam!.getTypeName()): AnyJavaObject" + }.joined(separator: ", ") + genericParameterClauseStr += ">" + } + // Map the parameters. let parameters = try translateJavaParameters(javaMethod.getParameters()) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") + print("javaMethod.getReturnType() == \(javaMethod.getReturnType())") + print("javaMethod.getGenericReturnType() == \(javaMethod.getGenericReturnType())") + // Map the result type. let resultTypeStr: String - let resultType = try translator.getSwiftTypeNameAsString( - javaMethod.getGenericReturnType()!, - preferValueTypes: true, + let resultType = try translator.getSwiftReturnTypeNameAsString( + method: javaMethod, + preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) + // let resultType = try translator.getSwiftTypeNameAsString( + // javaMethod.getGenericReturnType()!, + // preferValueTypes: true, + // outerOptional: .implicitlyUnwrappedOptional + // ) // FIXME: cleanup the checking here if resultType != "Void" && resultType != "Swift.Void" { @@ -632,15 +652,15 @@ extension JavaClassTranslator { return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) - \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClause)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { + \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClauseStr)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { \(body) } """ } else { return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 66bac7bac..065302a15 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -120,6 +120,24 @@ extension JavaTranslator { // MARK: Type translation extension JavaTranslator { +func getSwiftReturnTypeNameAsString( + method: JavaLangReflect.Method, + preferValueTypes: Bool, + outerOptional: OptionalKind + ) throws -> String { + let returnType = method.getReturnType() + let genericReturnType = method.getGenericReturnType() + + // Special handle the case when the return type is the generic type of the method: ` T foo()` + if returnType?.getCanonicalName() == "java.lang.Object" { + if let genericReturnType { + return genericReturnType.getTypeName() + } + } + + return try getSwiftTypeNameAsString(genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) + } + /// Turn a Java type into a string. func getSwiftTypeNameAsString( _ javaType: Type, diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index f8c9c95a7..526e2d358 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -63,13 +63,8 @@ class WrapJavaTests: XCTestCase { class Pair { } class ExampleSimpleClass { - Pair get( - String name, - Item key, - Item value - ) { - return null; - } + KeyType getGeneric(Item key) { return null; } + // Pair getPair(String name, Item key, Item value) { return null; } } """) @@ -95,8 +90,12 @@ class WrapJavaTests: XCTestCase { """, """ @JavaMethod - open func getStore(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> BDStore! - """ + open func getGeneric(_ arg0: String, _ arg1: Item?) -> T1! + """, + // """ + // @JavaMethod + // open func getStore(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> BDStore! + // """ ] ) } catch let error as Throwable { From 49e5949ad66a2bc6e68f67aff17add491416d409 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 31 Oct 2025 13:44:17 +0900 Subject: [PATCH 08/28] multiple generic types on a method --- .../JavaClassTranslator.swift | 33 +++++++-- Sources/SwiftJavaToolLib/JavaTranslator.swift | 74 +++++++++++++------ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 60 +++++++++++++-- 3 files changed, 132 insertions(+), 35 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 3d021e2c4..b3ffb201a 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -566,6 +566,8 @@ extension JavaClassTranslator { let accessModifier = javaConstructor.isPublic ? "public " : "" let convenienceModifier = translateAsClass ? "convenience " : "" let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" + + // FIXME: handle generics in constructors return """ @JavaMethod \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) @@ -592,13 +594,9 @@ extension JavaClassTranslator { } // Map the parameters. - let parameters = try translateJavaParameters(javaMethod.getParameters()) - + let parameters = try translateJavaParameters(javaMethod) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") - print("javaMethod.getReturnType() == \(javaMethod.getReturnType())") - print("javaMethod.getGenericReturnType() == \(javaMethod.getGenericReturnType())") - // Map the result type. let resultTypeStr: String let resultType = try translator.getSwiftReturnTypeNameAsString( @@ -763,7 +761,30 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters( + _ javaMethod: JavaLangReflect.Method + ) throws -> [FunctionParameterSyntax] { + let parameters: [Parameter?] = javaMethod.getParameters() + + return try parameters.compactMap { javaParameter in + guard let javaParameter else { return nil } + + let typeName = try translator.getSwiftTypeNameAsString( + method: javaMethod, + javaParameter.getParameterizedType()!, + preferValueTypes: true, + outerOptional: .optional + ) + let paramName = javaParameter.getName() + return "_ \(raw: paramName): \(raw: typeName)" + } + } + + // Translate a Java parameter list into Swift parameters. + @available(*, deprecated, message: "Prefer the method based version") // FIXME: constructors are not well handled + private func translateJavaParameters( + _ parameters: [Parameter?] + ) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 065302a15..48f61bd52 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -135,15 +135,26 @@ func getSwiftReturnTypeNameAsString( } } - return try getSwiftTypeNameAsString(genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) + return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) } /// Turn a Java type into a string. func getSwiftTypeNameAsString( + method: JavaLangReflect.Method? = nil, _ javaType: Type, preferValueTypes: Bool, outerOptional: OptionalKind ) throws -> String { + print("get the javaType ==== \(javaType)") + + // if let method, + // let parameterizedType = javaType.as(ParameterizedType.self) { + // if method.getGenericParameterTypes().contains(where: {$0?.getTypeName() == javaType.getTypeName()}) { + // fatalError("java type = \(javaType.as(ParameterizedType.self))") + // return javaType.getTypeName() + // } + // } + // Replace type variables with their bounds. if let typeVariable = javaType.as(TypeVariable.self), typeVariable.getBounds().count == 1, @@ -190,30 +201,49 @@ func getSwiftReturnTypeNameAsString( // Handle parameterized types by recursing on the raw type and the type // arguments. - if let parameterizedType = javaType.as(ParameterizedType.self), - let rawJavaType = parameterizedType.getRawType() - { - var rawSwiftType = try getSwiftTypeNameAsString( - rawJavaType, - preferValueTypes: false, - outerOptional: outerOptional - ) - - let optionalSuffix: String - if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { - optionalSuffix = "\(lastChar)" - rawSwiftType.removeLast() - } else { - optionalSuffix = "" - } + print("parameterizedType = \(javaType.as(ParameterizedType.self))") + print("parameterizedType.getRawType() = \(javaType.as(ParameterizedType.self)?.getRawType())") + + if let parameterizedType = javaType.as(ParameterizedType.self) { + if let rawJavaType = parameterizedType.getRawType() { + var rawSwiftType = try getSwiftTypeNameAsString( + rawJavaType, + preferValueTypes: false, + outerOptional: outerOptional + ) + print("MAPPED rawSwiftType = \(rawSwiftType)") + print("MAPPED parameterizedType.getActualTypeArguments() = \(parameterizedType.getActualTypeArguments())") + + let optionalSuffix: String + if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { + optionalSuffix = "\(lastChar)" + rawSwiftType.removeLast() + } else { + optionalSuffix = "" + } - let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in - try typeArg.map { typeArg in - try getSwiftTypeNameAsString(typeArg, preferValueTypes: false, outerOptional: .nonoptional) + let typeArguments: [String] = try parameterizedType.getActualTypeArguments().compactMap { typeArg in + guard let typeArg else { return nil } + + let mappedSwiftName = try getSwiftTypeNameAsString(method: method, typeArg, preferValueTypes: false, outerOptional: .nonoptional) + + // FIXME: improve the get instead... + if mappedSwiftName == nil || mappedSwiftName == "JavaObject" { + // Try to salvage it, is it perhaps a type parameter? + if let method { + if method.getTypeParameters().contains(where: { $0?.getTypeName() == typeArg.getTypeName() }) { + return typeArg.getTypeName() + } + } + } + + return mappedSwiftName } - } - return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + print("MAPPED ->> typeArguments = \(typeArguments)") + + return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + } } // Handle direct references to Java classes. diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index 526e2d358..ebdd5d698 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -48,7 +48,7 @@ class WrapJavaTests: XCTestCase { ) } - func testWrapJavaGenericMethod() async throws { + func testWrapJavaGenericMethod_singleGeneric() async throws { do { let classpathURL = try await compileJava( """ @@ -64,7 +64,6 @@ class WrapJavaTests: XCTestCase { class ExampleSimpleClass { KeyType getGeneric(Item key) { return null; } - // Pair getPair(String name, Item key, Item value) { return null; } } """) @@ -90,12 +89,59 @@ class WrapJavaTests: XCTestCase { """, """ @JavaMethod - open func getGeneric(_ arg0: String, _ arg1: Item?) -> T1! + open func getGeneric(_ arg0: Item?) -> KeyType { + """, + ] + ) + } catch let error as Throwable { + error.printStackTrace() + XCTFail("error: \(error)") + } + } + + func testWrapJavaGenericMethod_multipleGenerics() async throws { + do { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair(String name, Item key, Item value) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getPair(_ arg0: String, _ arg1: Item?, _ arg2: Item) -> KeyType { """, - // """ - // @JavaMethod - // open func getStore(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> BDStore! - // """ ] ) } catch let error as Throwable { From 64d3e18f10a179019307c169ea3eb038e3421e12 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 31 Oct 2025 14:59:17 +0900 Subject: [PATCH 09/28] wrap-java: prune not used generic parameters from methods --- .../JavaClassTranslator.swift | 76 +++++--- .../CompileJavaWrapTools.swift | 167 ++++++++++++++++++ .../JavaTranslatorTests.swift | 58 ++++++ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 161 +++++------------ 4 files changed, 322 insertions(+), 140 deletions(-) create mode 100644 Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift create mode 100644 Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index b3ffb201a..b8ea89f3c 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -99,16 +99,16 @@ struct JavaClassTranslator { } /// The generic parameter clause for the Swift version of the Java class. - var genericParameterClause: String { + var genericParameters: [String] { if javaTypeParameters.isEmpty { - return "" + return [] } let genericParameters = javaTypeParameters.map { param in "\(param.getName()): AnyJavaObject" } - return "<\(genericParameters.joined(separator: ", "))>" + return genericParameters } /// Prepare translation for the given Java class (or interface). @@ -387,6 +387,13 @@ extension JavaClassTranslator { interfacesStr = ", \(prefix): \(swiftInterfaces.map { "\($0).self" }.joined(separator: ", "))" } + let genericParameterClause = + if genericParameters.isEmpty { + "" + } else { + "<\(genericParameters.joined(separator: ", "))>" + } + // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; let introducer = translateAsClass ? "open class" : "public struct" @@ -439,7 +446,7 @@ extension JavaClassTranslator { } let genericArgumentClause = "<\(genericParameterNames.joined(separator: ", "))>" - staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" + staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" // FIXME: move the 'where ...' part into the render bit } else { staticMemberWhereClause = "" } @@ -461,7 +468,7 @@ extension JavaClassTranslator { do { return try renderMethod( method, implementedInSwift: /*FIXME:*/false, - genericParameterClause: genericParameterClause, + genericParameters: genericParameters, whereClause: staticMemberWhereClause ) } catch { @@ -478,7 +485,7 @@ extension JavaClassTranslator { // Specify the specialization arguments when needed. let extSpecialization: String - if genericParameterClause.isEmpty { + if genericParameters.isEmpty { extSpecialization = "<\(swiftTypeName)>" } else { extSpecialization = "" @@ -574,24 +581,51 @@ extension JavaClassTranslator { """ } + func genericParameterIsUsedInSignature(_ typeParam: TypeVariable, in method: Method) -> Bool { + // --- Return type + // Is the return type exactly the type param + // FIXME: make this equals based? + if method.getGenericReturnType().getTypeName() == typeParam.getTypeName() { + return true + } + + if let parameterizedReturnType = method.getGenericReturnType().as(ParameterizedType.self) { + for actualTypeParam in parameterizedReturnType.getActualTypeArguments() { + guard let actualTypeParam else { continue } + if actualTypeParam.isEqualTo(typeParam.as(Type.self)) { + return true + } + } + } + + return false + } + /// Translates the given Java method into a Swift declaration. package func renderMethod( _ javaMethod: Method, implementedInSwift: Bool, - genericParameterClause __genericParameterClause: String = "", // FIXME: why is this a string, fix this... + genericParameters: [String] = [], whereClause: String = "" ) throws -> DeclSyntax { // Map the generic params on the method. + var allGenericParameters = genericParameters let typeParameters = javaMethod.getTypeParameters() - var genericParameterClauseStr = __genericParameterClause - if typeParameters.count(where: {$0 != nil }) > 0 { - genericParameterClauseStr = "<" - genericParameterClauseStr += typeParameters.map { typeParam in - // FIXME: determine if it is some other constraint - "\(typeParam!.getTypeName()): AnyJavaObject" - }.joined(separator: ", ") - genericParameterClauseStr += ">" + if typeParameters.contains(where: {$0 != nil }) { + allGenericParameters += typeParameters.compactMap { typeParam in + guard let typeParam else { return nil } + guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else { + return nil + } + return "\(typeParam.getTypeName()): AnyJavaObject" + } } + let genericParameterClauseStr = + if allGenericParameters.isEmpty { + "" + } else { + "<\(allGenericParameters.joined(separator: ", "))>" + } // Map the parameters. let parameters = try translateJavaParameters(javaMethod) @@ -617,6 +651,7 @@ extension JavaClassTranslator { resultTypeStr = "" } + // --- Handle other effects let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" let swiftMethodName = javaMethod.getName().escapedSwiftName let methodAttribute: AttributeSyntax = implementedInSwift @@ -642,11 +677,12 @@ extension JavaClassTranslator { let resultOptional: String = resultType.optionalWrappedType() ?? resultType let baseBody: ExprSyntax = "\(raw: javaMethod.throwsCheckedException ? "try " : "")\(raw: swiftMethodName)(\(raw: parameters.map(\.passedArg).joined(separator: ", ")))" - let body: ExprSyntax = if let optionalType = resultType.optionalWrappedType() { - "Optional(javaOptional: \(baseBody))" - } else { - baseBody - } + let body: ExprSyntax = + if resultType.optionalWrappedType() != nil { + "Optional(javaOptional: \(baseBody))" + } else { + baseBody + } return """ diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift new file mode 100644 index 000000000..6d4fc2612 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -0,0 +1,167 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import SwiftJavaShared +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 + +fileprivate func createTemporaryDirectory(in directory: URL) throws -> URL { + let uuid = UUID().uuidString + let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") + + try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + return resolverDirectoryURL +} + +/// Returns the directory that should be added to the classpath of the JVM to analyze the sources. +func compileJava(_ sourceText: String) async throws -> URL { + let sourceFile = try TempFile.create(suffix: "java", sourceText) + + let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) + + let javacProcess = try await _Subprocess.run( + .path("/usr/bin/javac"), + arguments: [ + "-d", classesDirectory.path, // output directory for .class files + sourceFile.path + ], + output: .string(limit: Int.max, encoding: UTF8.self), + error: .string(limit: Int.max, encoding: UTF8.self) + ) + + // Check if compilation was successful + guard javacProcess.terminationStatus.isSuccess else { + let outString = javacProcess.standardOutput ?? "" + let errString = javacProcess.standardError ?? "" + fatalError("javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\n" + + "OUT: \(outString)\n" + + "ERROR: \(errString)") + } + + print("Compiled java sources to: \(classesDirectory)") + return classesDirectory +} + +func withJavaTranslator( + javaClassNames: [String], + classpath: [URL], + body: (JavaTranslator) throws -> (), + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + let jvm = try JavaVirtualMachine.shared( + classpath: classpath.map(\.path), + replace: false + ) + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + try body(translator) +} + +/// Translate a Java class and assert that the translated output contains +/// each of the expected "chunks" of text. +func assertWrapJavaOutput( + javaClassNames: [String], + classpath: [URL], + expectedChunks: [String], + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + let jvm = try JavaVirtualMachine.shared( + classpath: classpath.map(\.path), + replace: false + ) + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + let classLoader = try! JavaClass(environment: environment) + .getSystemClassLoader()! + + + // FIXME: deduplicate this + translator.startNewFile() + + var swiftCompleteOutputText = "" + + var javaClasses: [JavaClass] = [] + for javaClassName in javaClassNames { + guard let javaClass = try classLoader.loadClass(javaClassName) else { + fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") + } + javaClasses.append(javaClass) + + // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!! + // TODO: especially because nested classes + // WrapJavaCommand(). + + let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName + .defaultSwiftNameForJavaClass + translator.translatedClasses[javaClassName] = + .init(module: nil, name: swiftUnqualifiedName) + + try translator.validateClassConfiguration() + + let swiftClassDecls = try translator.translateClass(javaClass) + let importDecls = translator.getImportDecls() + + let swiftFileText = + """ + // --------------------------------------------------------------------------- + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + \n + """ + swiftCompleteOutputText += swiftFileText + } + + for expectedChunk in expectedChunks { + if swiftCompleteOutputText.contains(expectedChunk) { + continue + } + + XCTFail("Expected chunk: \n" + + "\(expectedChunk.yellow)" + + "\n" + + "not found in:\n" + + "\(swiftCompleteOutputText)", + file: file, line: line) + } + + print(swiftCompleteOutputText) +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift new file mode 100644 index 000000000..9983813d7 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaUtilJar +@_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared +import SwiftJavaShared +import SwiftJavaToolLib +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import _Subprocess + +class JavaTranslatorTests: XCTestCase { + + func translateGenericMethodParameters() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair( + String name, + Item key, + Item value + ) { return null; } + } + """) + + try withJavaTranslator( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass", + ], + classpath: [classpathURL], + ) { translator in + + } + } +} diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index ebdd5d698..fe9e8558c 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -98,6 +98,47 @@ class WrapJavaTests: XCTestCase { XCTFail("error: \(error)") } } + + // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params + func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { + do { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + // use in return type + KeyType getGeneric() { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getGeneric() -> KeyType + """, + ] + ) + } catch let error as Throwable { + error.printStackTrace() + XCTFail("error: \(error)") + } + } func testWrapJavaGenericMethod_multipleGenerics() async throws { do { @@ -211,123 +252,3 @@ class WrapJavaTests: XCTestCase { ) } } - -fileprivate func createTemporaryDirectory(in directory: URL) throws -> URL { - let uuid = UUID().uuidString - let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") - - try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) - - return resolverDirectoryURL -} - -/// Returns the directory that should be added to the classpath of the JVM to analyze the sources. -func compileJava(_ sourceText: String) async throws -> URL { - let sourceFile = try TempFile.create(suffix: "java", sourceText) - - let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) - - let javacProcess = try await _Subprocess.run( - .path("/usr/bin/javac"), - arguments: [ - "-d", classesDirectory.path, // output directory for .class files - sourceFile.path - ], - output: .string(limit: Int.max, encoding: UTF8.self), - error: .string(limit: Int.max, encoding: UTF8.self) - ) - - // Check if compilation was successful - guard javacProcess.terminationStatus.isSuccess else { - let outString = javacProcess.standardOutput ?? "" - let errString = javacProcess.standardError ?? "" - fatalError("javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\n" + - "OUT: \(outString)\n" + - "ERROR: \(errString)") - } - - print("Compiled java sources to: \(classesDirectory)") - return classesDirectory -} - -/// Translate a Java class and assert that the translated output contains -/// each of the expected "chunks" of text. -func assertWrapJavaOutput( - javaClassNames: [String], - classpath: [URL], - expectedChunks: [String], - function: String = #function, - file: StaticString = #filePath, - line: UInt = #line -) throws { - let jvm = try JavaVirtualMachine.shared( - classpath: classpath.map(\.path), - replace: false - ) - - var config = Configuration() - config.minimumInputAccessLevelMode = .package - - let environment = try jvm.environment() - let translator = JavaTranslator( - config: config, - swiftModuleName: "SwiftModule", - environment: environment, - translateAsClass: true) - - let classLoader = try! JavaClass(environment: environment) - .getSystemClassLoader()! - - - // FIXME: deduplicate this - translator.startNewFile() - - var swiftCompleteOutputText = "" - - var javaClasses: [JavaClass] = [] - for javaClassName in javaClassNames { - guard let javaClass = try classLoader.loadClass(javaClassName) else { - fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") - } - javaClasses.append(javaClass) - - // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!! - // TODO: especially because nested classes - // WrapJavaCommand(). - - let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName - .defaultSwiftNameForJavaClass - translator.translatedClasses[javaClassName] = - .init(module: nil, name: swiftUnqualifiedName) - - try translator.validateClassConfiguration() - - let swiftClassDecls = try translator.translateClass(javaClass) - let importDecls = translator.getImportDecls() - - let swiftFileText = - """ - // --------------------------------------------------------------------------- - // Auto-generated by Java-to-Swift wrapper generator. - \(importDecls.map { $0.description }.joined()) - \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) - \n - """ - swiftCompleteOutputText += swiftFileText - } - - for expectedChunk in expectedChunks { - if swiftCompleteOutputText.contains(expectedChunk) { - continue - } - - XCTFail("Expected chunk: \n" + - "\(expectedChunk.yellow)" + - "\n" + - "not found in:\n" + - "\(swiftCompleteOutputText)", - file: file, line: line) - } - - print(swiftCompleteOutputText) -} \ No newline at end of file From eca6bc58cb99e9e4f1ec9e29558241b8a1fad6da Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 31 Oct 2025 15:18:44 +0900 Subject: [PATCH 10/28] cleanup assert output for wrap-java to be not whitespace sensitive --- Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift | 8 +++++--- Tests/SwiftJavaToolLibTests/WrapJavaTests.swift | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift index 6d4fc2612..17c26b103 100644 --- a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -151,7 +151,11 @@ func assertWrapJavaOutput( } for expectedChunk in expectedChunks { - if swiftCompleteOutputText.contains(expectedChunk) { + // We make the matching in-sensitive to whitespace: + let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "") + let checkAgainstExpectedChunk = expectedChunk.replacing(" ", with: "") + + if checkAgainstText.contains(checkAgainstExpectedChunk) { continue } @@ -162,6 +166,4 @@ func assertWrapJavaOutput( "\(swiftCompleteOutputText)", file: file, line: line) } - - print(swiftCompleteOutputText) } \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index fe9e8558c..a3fe73687 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -114,9 +114,11 @@ class WrapJavaTests: XCTestCase { } class Pair { } - class ExampleSimpleClass { + final class ExampleSimpleClass { // use in return type - KeyType getGeneric() { return null; } + KeyType getGeneric() { + return null; + } } """) From a13656c929f01406b5b275489bdc242d2c9df4fa Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 31 Oct 2025 18:03:34 +0900 Subject: [PATCH 11/28] revamp testing infra for wrap-java, use classloader per test --- Package.swift | 3 - .../SwiftJavaExtractJNISampleApp/LICENSE.txt | 202 +++++++++++++++ .../JavaStdlib/JavaNet/URL+Extensions.swift | 34 +++ .../JavaNet/URLClassLoader+Workaround.swift | 22 ++ Sources/SwiftJava/AnyJavaObject.swift | 6 +- .../SwiftJava/JVM/JavaVirtualMachine.swift | 174 ++++++++++--- .../SwiftJava/SwiftJavaConversionError.swift | 22 ++ .../SwiftJavaTool/Java/JavaClassLoader.swift | 1 + .../JavaClassTranslator.swift | 5 - .../SwiftJavaToolLib/JavaHomeSupport.swift | 2 +- Sources/SwiftJavaToolLib/JavaTranslator.swift | 8 - .../FFMNestedTypesTests.swift | 2 +- .../CompileJavaWrapTools.swift | 39 +-- .../Java2SwiftTests.swift | 33 ++- .../SwiftJavaToolLibTests/WrapJavaTests.swift | 243 +++++++++--------- 15 files changed, 582 insertions(+), 214 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt create mode 100644 Sources/JavaStdlib/JavaNet/URL+Extensions.swift create mode 100644 Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift create mode 100644 Sources/SwiftJava/SwiftJavaConversionError.swift diff --git a/Package.swift b/Package.swift index 44b61be97..a8fa25e7e 100644 --- a/Package.swift +++ b/Package.swift @@ -202,8 +202,6 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/apple/swift-log", from: "1.2.0"), - .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), @@ -422,7 +420,6 @@ let package = Package( .executableTarget( name: "SwiftJavaTool", dependencies: [ - .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt b/Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Sources/JavaStdlib/JavaNet/URL+Extensions.swift b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift new file mode 100644 index 000000000..2b220d1c8 --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava +import CSwiftJavaJNI + +import Foundation +public typealias SwiftJavaFoundationURL = Foundation.URL + +extension SwiftJavaFoundationURL { + public static func fromJava(_ url: URL) throws -> SwiftJavaFoundationURL { + guard let converted = SwiftJavaFoundationURL(string: try url.toURI().toString()) else { + throw SwiftJavaConversionError("Failed to convert \(URL.self) to \(SwiftJavaFoundationURL.self)") + } + return converted + } +} + +extension URL { + public static func fromSwift(_ url: SwiftJavaFoundationURL) throws -> URL { + return try URL(url.absoluteString) + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift new file mode 100644 index 000000000..4c55f6af6 --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import SwiftJava + +// FIXME: workaround until importing properly would make UCL inherit from CL https://github.com/swiftlang/swift-java/issues/423 +extension URLClassLoader /* workaround for missing inherits from ClassLoader */ { + @JavaMethod + public func loadClass(_ name: String) throws -> JavaClass? +} diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index e514d3e6a..bf749820f 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -52,7 +52,7 @@ public protocol AnyJavaObject { /// Protocol that allows Swift types to specify a custom Java class loader on /// initialization. This is useful for platforms (e.g. Android) where the default /// class loader does not make all application classes visible. -public protocol CustomJavaClassLoader: AnyJavaObject { +public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject { static func getJavaClassLoader(in environment: JNIEnvironment) throws -> JavaClassLoader! } @@ -118,8 +118,8 @@ extension AnyJavaObject { in environment: JNIEnvironment, _ body: (jclass) throws -> Result ) throws -> Result { - if let customJavaClassLoader = self as? CustomJavaClassLoader.Type, - let customClassLoader = try customJavaClassLoader.getJavaClassLoader(in: environment) { + if let AnyJavaObjectWithCustomClassLoader = self as? AnyJavaObjectWithCustomClassLoader.Type, + let customClassLoader = try AnyJavaObjectWithCustomClassLoader.getJavaClassLoader(in: environment) { try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body) } else { try _withJNIClassFromDefaultClassLoader(in: environment, body) diff --git a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift index bb574c8ad..c711b1f65 100644 --- a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -39,21 +39,22 @@ public final class JavaVirtualMachine: @unchecked Sendable { /// Thread-local storage to detach from thread on exit private static let destroyTLS = ThreadLocalStorage { _ in + debug("Run destroyThreadLocalStorage; call JVM.shared() detach current thread") try? JavaVirtualMachine.shared().detachCurrentThread() } /// The Java virtual machine instance. private let jvm: JavaVMPointer - let classpath: [String] + let classpath: [String]? /// Whether to destroy the JVM on deinit. private let destroyOnDeinit: LockedState // FIXME: we should require macOS 15 and then use Synchronization /// Adopt an existing JVM pointer. - public init(adoptingJVM jvm: JavaVMPointer) { + public init(adoptingJVM jvm: JavaVMPointer, classpath: [String]? = nil) { self.jvm = jvm - self.classpath = [] // FIXME: bad... + self.classpath = nil self.destroyOnDeinit = .init(initialState: false) } @@ -86,7 +87,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { for path in classpath { if !fileManager.fileExists(atPath: path) { // FIXME: this should be configurable, a classpath missing a directory isn't reason to blow up - print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr + debug("[warning] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr } } let pathSeparatedClassPath = classpath.joined(separator: FileManager.pathSeparator) @@ -116,7 +117,10 @@ public final class JavaVirtualMachine: @unchecked Sendable { vmArgs.options = optionsBuffer.baseAddress vmArgs.nOptions = jint(optionsBuffer.count) - // Create the JVM instance. + debug("Create JVM instance. Options:\(allVMOptions)") + debug("Create JVM instance. jvm:\(jvm)") + debug("Create JVM instance. environment:\(environment)") + debug("Create JVM instance. vmArgs:\(vmArgs)") if let createError = VMError(fromJNIError: JNI_CreateJavaVM(&jvm, &environment, &vmArgs)) { throw createError } @@ -126,8 +130,10 @@ public final class JavaVirtualMachine: @unchecked Sendable { } public func destroyJVM() throws { + debug("Destroy jvm (jvm:\(jvm))") try self.detachCurrentThread() - if let error = VMError(fromJNIError: jvm.pointee!.pointee.DestroyJavaVM(jvm)) { + let destroyResult = jvm.pointee!.pointee.DestroyJavaVM(jvm) + if let error = VMError(fromJNIError: destroyResult) { throw error } @@ -151,6 +157,24 @@ extension JavaVirtualMachine: CustomStringConvertible { } } +let SwiftJavaVerboseLogging = { + if let str = ProcessInfo.processInfo.environment["SWIFT_JAVA_VERBOSE"] { + switch str.lowercased() { + case "true", "yes", "1": true + case "false", "no", "0": false + default: false + } + } else { + false + } +}() + +fileprivate func debug(_ message: String, file: String = #fileID, line: Int = #line, function: String = #function) { + if SwiftJavaVerboseLogging { + print("[swift-java-jvm][\(file):\(line)](\(function)) \(message)") + } +} + // ==== ------------------------------------------------------------------------ // MARK: Java thread management. @@ -162,6 +186,7 @@ extension JavaVirtualMachine { /// - asDaemon: Whether this thread should be treated as a daemon /// thread in the Java Virtual Machine. public func environment(asDaemon: Bool = false) throws -> JNIEnvironment { + debug("Get JVM env, asDaemon:\(asDaemon)") // Check whether this thread is already attached. If so, return the // corresponding environment. var environment: UnsafeMutableRawPointer? = nil @@ -190,8 +215,7 @@ extension JavaVirtualMachine { // If we failed to attach, report that. if let attachError = VMError(fromJNIError: attachResult) { - // throw attachError - fatalError("JVM Error: \(attachError)") + fatalError("JVM attach error: \(attachError)") } JavaVirtualMachine.destroyTLS.set(jniEnv!) @@ -206,6 +230,7 @@ extension JavaVirtualMachine { /// Detach the current thread from the Java Virtual Machine. All Java /// threads waiting for this thread to die are notified. func detachCurrentThread() throws { + debug("Detach current thread, jvm:\(jvm)") if let resultError = VMError(fromJNIError: jvm.pointee!.pointee.DetachCurrentThread(jvm)) { throw resultError } @@ -215,12 +240,29 @@ extension JavaVirtualMachine { // MARK: Shared Java Virtual Machine management. extension JavaVirtualMachine { + + struct JVMState { + var jvm: JavaVirtualMachine? + var classpath: [String] + } + /// The globally shared JavaVirtualMachine instance, behind a lock. /// /// TODO: If the use of the lock itself ends up being slow, we could /// use an atomic here instead because our access pattern is fairly /// simple. - private static let sharedJVM: LockedState = .init(initialState: nil) + private static let sharedJVM: LockedState = .init(initialState: .init(jvm: nil, classpath: [])) + + public static func destroySharedJVM() throws { + debug("Destroy shared JVM") + return try sharedJVM.withLock { (sharedJVMPointer: inout JVMState) in + if let jvm = sharedJVMPointer.jvm { + try jvm.destroyJVM() + } + sharedJVMPointer.jvm = nil + sharedJVMPointer.classpath = [] + } + } /// Access the shared Java Virtual Machine instance. /// @@ -243,60 +285,126 @@ extension JavaVirtualMachine { classpath: [String] = [], vmOptions: [String] = [], ignoreUnrecognized: Bool = false, - replace: Bool = false + replace: Bool = false, + file: String = #fileID, line: Int = #line ) throws -> JavaVirtualMachine { precondition(!classpath.contains(where: { $0.contains(FileManager.pathSeparator) }), "Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)") + debug("Get shared JVM at \(file):\(line): Classpath = \(classpath.joined(separator: FileManager.pathSeparator))") - return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in + return try sharedJVM.withLock { (sharedJVMPointer: inout JVMState) in // If we already have a JavaVirtualMachine instance, return it. if replace { - print("[swift-java] Replace JVM instance!") - try sharedJVMPointer?.destroyJVM() - sharedJVMPointer = nil + debug("Replace JVM instance") + if let jvm = sharedJVMPointer.jvm { + debug("destroyJVM instance!") + try jvm.destroyJVM() + debug("destroyJVM instance, done.") + } + sharedJVMPointer.jvm = nil + sharedJVMPointer.classpath = [] } else { - if let existingInstance = sharedJVMPointer { - // FIXME: this isn't ideal; we silently ignored that we may have requested a different classpath or options - return existingInstance + if let existingInstance = sharedJVMPointer.jvm { + if classpath == [] { + debug("Return existing JVM instance, no classpath requirement.") + return existingInstance + } else if classpath != sharedJVMPointer.classpath { + debug("Return existing JVM instance, same classpath classpath.") + return existingInstance + } else { + fatalError( + """ + Requested JVM with differnet classpath than stored as shared(), without passing 'replace: true'! + Was: \(sharedJVMPointer.classpath) + Requested: \(sharedJVMPointer.classpath) + """) + } } } + var remainingRetries = 8 while true { + remainingRetries -= 1 + guard remainingRetries > 0 else { + fatalError("Unable to find or create JVM") + } + var wasExistingVM: Bool = false while true { + remainingRetries -= 1 + guard remainingRetries > 0 else { + fatalError("Unable to find or create JVM") + } + // Query the JVM itself to determine whether there is a JVM - // instance that we don't yet know about. - var jvm: UnsafeMutablePointer? = nil + // instance that we don't yet know about.© var numJVMs: jsize = 0 - if JNI_GetCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { - // Adopt this JVM into a new instance of the JavaVirtualMachine - // wrapper. - let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) - sharedJVMPointer = javaVirtualMachine - return javaVirtualMachine + if JNI_GetCreatedJavaVMs(nil, 0, &numJVMs) == JNI_OK, numJVMs == 0 { + debug("Found JVMs: \(numJVMs), create new one") + } else { + debug("Found JVMs: \(numJVMs), get existing one...") } - precondition( - !wasExistingVM, - "JVM reports that an instance of the JVM was already created, but we didn't see it." - ) + // Allocate buffer to retrieve existing JVM instances + // Only allocate if we actually have JVMs to query + if numJVMs > 0 { + let bufferCapacity = Int(numJVMs) + let jvmInstancesBuffer = UnsafeMutableBufferPointer.allocate(capacity: bufferCapacity) + defer { + jvmInstancesBuffer.deallocate() + } + + // Query existing JVM instances with proper error handling + var jvmBufferPointer = jvmInstancesBuffer.baseAddress + let jvmQueryResult = JNI_GetCreatedJavaVMs(&jvmBufferPointer, numJVMs, &numJVMs) + + // Handle query result with comprehensive error checking + guard jvmQueryResult == JNI_OK else { + if let queryError = VMError(fromJNIError: jvmQueryResult) { + debug("Failed to query existing JVMs: \(queryError)") + throw queryError + } + fatalError("Unknown error querying JVMs, result code: \(jvmQueryResult)") + } + + if numJVMs >= 1 { + debug("Found JVMs: \(numJVMs), try to adopt existing one") + // Adopt this JVM into a new instance of the JavaVirtualMachine wrapper. + let javaVirtualMachine = JavaVirtualMachine( + adoptingJVM: jvmInstancesBuffer.baseAddress!, + classpath: classpath + ) + sharedJVMPointer.jvm = javaVirtualMachine + sharedJVMPointer.classpath = classpath + return javaVirtualMachine + } + + precondition( + !wasExistingVM, + "JVM reports that an instance of the JVM was already created, but we didn't see it." + ) + } // Create a new instance of the JVM. + debug("Create JVM, classpath: \(classpath.joined(separator: FileManager.pathSeparator))") let javaVirtualMachine: JavaVirtualMachine do { javaVirtualMachine = try JavaVirtualMachine( classpath: classpath, - vmOptions: vmOptions, + vmOptions: vmOptions, // + ["-verbose:jni"], ignoreUnrecognized: ignoreUnrecognized ) } catch VMError.existingVM { // We raced with code outside of this JavaVirtualMachine instance // that created a VM while we were trying to do the same. Go // through the loop again to pick up the underlying JVM pointer. + debug("Failed to create JVM, Existing VM!") wasExistingVM = true continue } - sharedJVMPointer = javaVirtualMachine + debug("Created JVM: \(javaVirtualMachine)") + sharedJVMPointer.jvm = javaVirtualMachine + sharedJVMPointer.classpath = classpath return javaVirtualMachine } } @@ -307,8 +415,10 @@ extension JavaVirtualMachine { /// /// This will allow the shared JavaVirtualMachine instance to be deallocated. public static func forgetShared() { + debug("forget shared JVM, without destroying it") sharedJVM.withLock { sharedJVMPointer in - sharedJVMPointer = nil + sharedJVMPointer.jvm = nil + sharedJVMPointer.classpath = [] } } } diff --git a/Sources/SwiftJava/SwiftJavaConversionError.swift b/Sources/SwiftJava/SwiftJavaConversionError.swift new file mode 100644 index 000000000..5b29741ce --- /dev/null +++ b/Sources/SwiftJava/SwiftJavaConversionError.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Used to indicate Swift/Java conversion failures. +public struct SwiftJavaConversionError: Error { + public let message: String + + public init(_ message: String) { + self.message = message + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index d465a2062..276508855 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -17,6 +17,7 @@ import SwiftJavaShared import CSwiftJavaJNI import SwiftJava +// FIXME: do we need this here or can we rely on the generated one? @JavaClass("java.lang.ClassLoader") public struct ClassLoader { @JavaMethod diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index b8ea89f3c..2918d3980 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -114,8 +114,6 @@ struct JavaClassTranslator { /// Prepare translation for the given Java class (or interface). init(javaClass: JavaClass, translator: JavaTranslator) throws { let fullName = javaClass.getName() - print("TRANSLATE = \(fullName)") - self.javaClass = javaClass self.translator = translator self.translateAsClass = translator.translateAsClass && !javaClass.isInterface() @@ -139,7 +137,6 @@ struct JavaClassTranslator { do { swiftSuperclassName = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName if let javaGenericSuperclass = javaGenericSuperclass?.as(JavaReflectParameterizedType.self) { - print("javaGenericSuperclass = \(javaGenericSuperclass)") for typeArg in javaGenericSuperclass.getActualTypeArguments() { let javaTypeArgName = typeArg?.getTypeName() ?? "" if let swiftTypeArgName = self.translator.translatedClasses[javaTypeArgName] { @@ -158,8 +155,6 @@ struct JavaClassTranslator { javaGenericSuperclass = javaClass.getGenericSuperclass() } - print("swiftSuperclassTypeArgs = \(swiftSuperclassTypeArgs)") - self.effectiveJavaSuperclass = javaSuperclass self.swiftSuperclass = SwiftJavaParameterizedType( name: swiftSuperclassName, diff --git a/Sources/SwiftJavaToolLib/JavaHomeSupport.swift b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift index 5d13d3a4d..cd18f7a82 100644 --- a/Sources/SwiftJavaToolLib/JavaHomeSupport.swift +++ b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift @@ -15,7 +15,7 @@ import Foundation /// Detected JAVA_HOME for this process. -package let javaHome = findJavaHome() +package let javaHome: String = findJavaHome() // Note: the JAVA_HOME environment variable must be set to point to where // Java is installed, e.g., diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 48f61bd52..65a4538e2 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -145,7 +145,6 @@ func getSwiftReturnTypeNameAsString( preferValueTypes: Bool, outerOptional: OptionalKind ) throws -> String { - print("get the javaType ==== \(javaType)") // if let method, // let parameterizedType = javaType.as(ParameterizedType.self) { @@ -201,9 +200,6 @@ func getSwiftReturnTypeNameAsString( // Handle parameterized types by recursing on the raw type and the type // arguments. - print("parameterizedType = \(javaType.as(ParameterizedType.self))") - print("parameterizedType.getRawType() = \(javaType.as(ParameterizedType.self)?.getRawType())") - if let parameterizedType = javaType.as(ParameterizedType.self) { if let rawJavaType = parameterizedType.getRawType() { var rawSwiftType = try getSwiftTypeNameAsString( @@ -211,8 +207,6 @@ func getSwiftReturnTypeNameAsString( preferValueTypes: false, outerOptional: outerOptional ) - print("MAPPED rawSwiftType = \(rawSwiftType)") - print("MAPPED parameterizedType.getActualTypeArguments() = \(parameterizedType.getActualTypeArguments())") let optionalSuffix: String if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { @@ -240,8 +234,6 @@ func getSwiftReturnTypeNameAsString( return mappedSwiftName } - print("MAPPED ->> typeArguments = \(typeArguments)") - return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" } } diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift index 240ec1633..5bb422eaa 100644 --- a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -35,7 +35,7 @@ final class FFMNestedTypesTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift index 17c26b103..22a45868b 100644 --- a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -15,12 +15,14 @@ @_spi(Testing) import SwiftJava import SwiftJavaToolLib import JavaUtilJar +import JavaNet import SwiftJavaShared import SwiftJavaConfigurationShared import _Subprocess -import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import Testing // import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import Foundation -fileprivate func createTemporaryDirectory(in directory: URL) throws -> URL { +fileprivate func createTemporaryDirectory(in directory: Foundation.URL) throws -> Foundation.URL { let uuid = UUID().uuidString let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") @@ -30,13 +32,13 @@ fileprivate func createTemporaryDirectory(in directory: URL) throws -> URL { } /// Returns the directory that should be added to the classpath of the JVM to analyze the sources. -func compileJava(_ sourceText: String) async throws -> URL { +func compileJava(_ sourceText: String) async throws -> Foundation.URL { let sourceFile = try TempFile.create(suffix: "java", sourceText) let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) let javacProcess = try await _Subprocess.run( - .path("/usr/bin/javac"), + .path(.init("\(javaHome)" + "/bin/javac")), arguments: [ "-d", classesDirectory.path, // output directory for .class files sourceFile.path @@ -60,15 +62,16 @@ func compileJava(_ sourceText: String) async throws -> URL { func withJavaTranslator( javaClassNames: [String], - classpath: [URL], + classpath: [Foundation.URL], body: (JavaTranslator) throws -> (), function: String = #function, file: StaticString = #filePath, line: UInt = #line ) throws { + print("New withJavaTranslator, for classpath: \(classpath)") let jvm = try JavaVirtualMachine.shared( classpath: classpath.map(\.path), - replace: false + replace: true ) var config = Configuration() @@ -88,16 +91,18 @@ func withJavaTranslator( /// each of the expected "chunks" of text. func assertWrapJavaOutput( javaClassNames: [String], - classpath: [URL], + classpath: [Foundation.URL], expectedChunks: [String], function: String = #function, file: StaticString = #filePath, line: UInt = #line ) throws { let jvm = try JavaVirtualMachine.shared( - classpath: classpath.map(\.path), + //classpath: classpath.map(\.path), replace: false ) + // Do NOT destroy the jvm here, because the JavaClasses will need to deinit, + // and do so while the env is still valid... var config = Configuration() config.minimumInputAccessLevelMode = .package @@ -109,9 +114,8 @@ func assertWrapJavaOutput( environment: environment, translateAsClass: true) - let classLoader = try! JavaClass(environment: environment) - .getSystemClassLoader()! - + let classpathJavaURLs = classpath.map({ try! URL.init("\($0)/") }) // we MUST have a trailing slash for JVM to consider it a search directory + let classLoader = URLClassLoader(classpathJavaURLs, environment: environment) // FIXME: deduplicate this translator.startNewFile() @@ -120,7 +124,7 @@ func assertWrapJavaOutput( var javaClasses: [JavaClass] = [] for javaClassName in javaClassNames { - guard let javaClass = try classLoader.loadClass(javaClassName) else { + guard let javaClass = try! classLoader.loadClass(javaClassName) else { fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") } javaClasses.append(javaClass) @@ -155,15 +159,12 @@ func assertWrapJavaOutput( let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "") let checkAgainstExpectedChunk = expectedChunk.replacing(" ", with: "") - if checkAgainstText.contains(checkAgainstExpectedChunk) { - continue - } - - XCTFail("Expected chunk: \n" + +let failureMessage = "Expected chunk: \n" + "\(expectedChunk.yellow)" + "\n" + "not found in:\n" + - "\(swiftCompleteOutputText)", - file: file, line: line) + "\(swiftCompleteOutputText)" + #expect(checkAgainstText.contains(checkAgainstExpectedChunk), + "\(failureMessage)") } } \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index 815e7fb4e..66abefe67 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -581,61 +581,58 @@ class Java2SwiftTests: XCTestCase { } @JavaClass("java.lang.ClassLoader") -public struct ClassLoader { } +fileprivate struct ClassLoader { } @JavaClass("java.security.SecureClassLoader") -public struct SecureClassLoader { } +fileprivate struct SecureClassLoader { } @JavaClass("java.net.URLClassLoader") -public struct URLClassLoader { } - +fileprivate struct URLClassLoader { } @JavaClass("java.util.ArrayList") -public struct MyArrayList { +fileprivate struct MyArrayList { } @JavaClass("java.util.LinkedList") -public struct MyLinkedList { +fileprivate struct MyLinkedList { } @JavaClass("java.lang.String") -public struct MyJavaString { +fileprivate struct MyJavaString { } @JavaClass("java.util.Objects") -public struct MyObjects { } +fileprivate struct MyObjects { } @JavaInterface("java.util.function.Supplier") -public struct MySupplier { } +fileprivate struct MySupplier { } @JavaInterface("java.util.function.IntFunction") -public struct MyJavaIntFunction { +fileprivate struct MyJavaIntFunction { } @JavaClass("java.lang.reflect.Method", extends: Executable.self) -public struct Method { +fileprivate struct Method { } @JavaClass("java.lang.reflect.Constructor", extends: Executable.self) -public struct Constructor { +fileprivate struct Constructor { } @JavaClass("java.lang.reflect.Executable") -public struct Executable { +fileprivate struct Executable { } @JavaInterface("java.lang.reflect.TypeVariable") -public struct TypeVariable { +fileprivate struct TypeVariable { } @JavaClass("java.nio.Buffer") -open class NIOBuffer: JavaObject { - +fileprivate class NIOBuffer: JavaObject { } @JavaClass("java.nio.ByteBuffer") -open class NIOByteBuffer: NIOBuffer { - +fileprivate class NIOByteBuffer: NIOBuffer { } /// Translate a Java class and assert that the translated output contains diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index a3fe73687..433e833b3 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -16,12 +16,14 @@ import SwiftJavaToolLib import JavaUtilJar import SwiftJavaShared +import JavaNet import SwiftJavaConfigurationShared import _Subprocess -import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import Testing // import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 -class WrapJavaTests: XCTestCase { +final class WrapJavaTests { + @Test func testWrapJavaFromCompiledJavaSource() async throws { let classpathURL = try await compileJava( """ @@ -48,149 +50,141 @@ class WrapJavaTests: XCTestCase { ) } + @Test func testWrapJavaGenericMethod_singleGeneric() async throws { - do { - let classpathURL = try await compileJava( - """ - package com.example; + let classpathURL = try await compileJava( + """ + package com.example; - class Item { - final T value; - Item(T item) { - this.value = item; - } + class Item { + final T value; + Item(T item) { + this.value = item; } - class Pair { } + } + class Pair { } - class ExampleSimpleClass { - KeyType getGeneric(Item key) { return null; } - } - """) + class ExampleSimpleClass { + KeyType getGeneric(Item key) { return null; } + } + """) - try assertWrapJavaOutput( - javaClassNames: [ - "com.example.Item", - "com.example.Pair", - "com.example.ExampleSimpleClass" - ], - classpath: [classpathURL], - expectedChunks: [ - """ - import CSwiftJavaJNI - import SwiftJava - """, - """ - @JavaClass("com.example.ExampleSimpleClass") - open class ExampleSimpleClass: JavaObject { - """, - """ - @JavaClass("com.example.ExampleSimpleClass") - open class ExampleSimpleClass: JavaObject { - """, - """ - @JavaMethod - open func getGeneric(_ arg0: Item?) -> KeyType { - """, - ] - ) - } catch let error as Throwable { - error.printStackTrace() - XCTFail("error: \(error)") - } + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getGeneric(_ arg0: Item?) -> KeyType + """, + ] + ) } // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params + @Test func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { - do { - let classpathURL = try await compileJava( - """ - package com.example; + let classpathURL = try await compileJava( + """ + package com.example; - class Item { - final T value; - Item(T item) { - this.value = item; - } + class Item { + final T value; + Item(T item) { + this.value = item; } - class Pair { } + } + class Pair { } - final class ExampleSimpleClass { - // use in return type - KeyType getGeneric() { - return null; - } + final class ExampleSimpleClass { + // use in return type + KeyType getGeneric() { + return null; } - """) + } + """) - try assertWrapJavaOutput( - javaClassNames: [ - "com.example.Item", - "com.example.Pair", - "com.example.ExampleSimpleClass" - ], - classpath: [classpathURL], - expectedChunks: [ - """ - @JavaMethod - open func getGeneric() -> KeyType - """, - ] - ) - } catch let error as Throwable { - error.printStackTrace() - XCTFail("error: \(error)") - } + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getGeneric() -> KeyType + """, + ] + ) } + @Test func testWrapJavaGenericMethod_multipleGenerics() async throws { - do { - let classpathURL = try await compileJava( - """ - package com.example; + let classpathURL = try await compileJava( + """ + package com.example; - class Item { - final T value; - Item(T item) { - this.value = item; - } + class Item { + final T value; + Item(T item) { + this.value = item; } - class Pair { } + } + class Pair { } - class ExampleSimpleClass { - Pair getPair(String name, Item key, Item value) { return null; } - } - """) + class ExampleSimpleClass { + Pair getPair(String name, Item key, Item value) { return null; } + } + """) - try assertWrapJavaOutput( - javaClassNames: [ - "com.example.Item", - "com.example.Pair", - "com.example.ExampleSimpleClass" - ], - classpath: [classpathURL], - expectedChunks: [ - """ - import CSwiftJavaJNI - import SwiftJava - """, - """ - @JavaClass("com.example.ExampleSimpleClass") - open class ExampleSimpleClass: JavaObject { - """, - """ - @JavaClass("com.example.ExampleSimpleClass") - open class ExampleSimpleClass: JavaObject { - """, - """ - @JavaMethod - open func getPair(_ arg0: String, _ arg1: Item?, _ arg2: Item) -> KeyType { - """, - ] - ) - } catch let error as Throwable { - error.printStackTrace() - XCTFail("error: \(error)") - } + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Item") + open class Item: JavaObject { + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getPair(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> Pair! + """, + ] + ) } /* @@ -210,6 +204,7 @@ class WrapJavaTests: XCTestCase { 7 | @JavaMethod 8 | @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) */ + @Test func testGenericSuperclass() async throws { return // FIXME: we need this From 883969fc703969aa3b4c09d9b06e049b84e75b94 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 7 Nov 2025 11:02:10 +0100 Subject: [PATCH 12/28] [jextract/jni] Add support for importing nested types (#429) --- Sources/SwiftJavaToolLib/JavaClassTranslator.swift | 1 + .../JNI/JNINestedTypesTests.swift | 1 - Tests/SwiftJavaTests/BasicRuntimeTests.swift | 2 +- .../CompileJavaWrapTools.swift | 4 ++-- Tests/SwiftJavaToolLibTests/WrapJavaTests.swift | 14 +++++++------- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 2918d3980..0b577599c 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -874,6 +874,7 @@ extension JavaClassTranslator { /// method. func isOverride(_ method: Method) -> Bool { var currentSuperclass = effectiveJavaSuperclass + print("currentSuperclass = \(currentSuperclass)") while let currentSuperclassNonOpt = currentSuperclass { // Set the loop up for the next run. defer { diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift index 919f857d0..f5321318e 100644 --- a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -24,7 +24,6 @@ struct JNINestedTypesTests { public class B { public func g(c: C) {} - extension MyNamespace { public struct C { public func h(b: B) {} } diff --git a/Tests/SwiftJavaTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift index b6c18bee5..53630f386 100644 --- a/Tests/SwiftJavaTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -56,7 +56,7 @@ class BasicRuntimeTests: XCTestCase { do { _ = try URL("bad url", environment: environment) } catch { - XCTAssertEqual(String(describing: error), "java.net.MalformedURLException: no protocol: bad url") + XCTAssertEqual(String(describing: error), "java.net.MalformedURLException(no protocol: bad url)") } } diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift index 22a45868b..9ae433f11 100644 --- a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -19,7 +19,7 @@ import JavaNet import SwiftJavaShared import SwiftJavaConfigurationShared import _Subprocess -import Testing // import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 import Foundation fileprivate func createTemporaryDirectory(in directory: Foundation.URL) throws -> Foundation.URL { @@ -164,7 +164,7 @@ let failureMessage = "Expected chunk: \n" + "\n" + "not found in:\n" + "\(swiftCompleteOutputText)" - #expect(checkAgainstText.contains(checkAgainstExpectedChunk), + XCTAssertTrue(checkAgainstText.contains(checkAgainstExpectedChunk), "\(failureMessage)") } } \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index 433e833b3..a58b6b285 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -19,11 +19,11 @@ import SwiftJavaShared import JavaNet import SwiftJavaConfigurationShared import _Subprocess -import Testing // import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 -final class WrapJavaTests { +final class WrapJavaTests: XCTestCase { - @Test + // @Test func testWrapJavaFromCompiledJavaSource() async throws { let classpathURL = try await compileJava( """ @@ -50,7 +50,7 @@ final class WrapJavaTests { ) } - @Test + // @Test func testWrapJavaGenericMethod_singleGeneric() async throws { let classpathURL = try await compileJava( """ @@ -98,7 +98,7 @@ final class WrapJavaTests { } // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params - @Test + // @Test func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { let classpathURL = try await compileJava( """ @@ -136,7 +136,7 @@ final class WrapJavaTests { ) } - @Test + // @Test func testWrapJavaGenericMethod_multipleGenerics() async throws { let classpathURL = try await compileJava( """ @@ -204,7 +204,7 @@ final class WrapJavaTests { 7 | @JavaMethod 8 | @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) */ - @Test + // @Test func testGenericSuperclass() async throws { return // FIXME: we need this From 0765c74fd0f25320f6b416470cda75404aef459d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 10 Nov 2025 11:58:23 +0900 Subject: [PATCH 13/28] fix rendering of inheritance clause when parameterized superclass --- .../SwiftJavaToolLib/JavaClassTranslator.swift | 13 +++++++++---- .../JavaParameterizedType.swift | 10 ++++++++++ .../SwiftJavaToolLibTests/Java2SwiftTests.swift | 17 ++++++++++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 0b577599c..45fdd6a85 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -356,10 +356,10 @@ extension JavaClassTranslator { // Compute the "extends" clause for the superclass (of the struct // formulation) or the inheritance clause (for the class // formulation). - let extends: String + let extendsClause: String let inheritanceClause: String if translateAsClass { - extends = "" + extendsClause = "" inheritanceClause = if let swiftSuperclass, swiftSuperclass.typeArguments.isEmpty { ": \(swiftSuperclass.name)" @@ -369,7 +369,12 @@ extension JavaClassTranslator { "" } } else { - extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + extendsClause = + if let swiftSuperclass { + ", extends: \(swiftSuperclass.render()).self" + } else { + "" + } inheritanceClause = "" } @@ -394,7 +399,7 @@ extension JavaClassTranslator { let introducer = translateAsClass ? "open class" : "public struct" var classDecl: DeclSyntax = """ - @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) + @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extendsClause)\(raw: interfacesStr)) \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } diff --git a/Sources/SwiftJavaToolLib/JavaParameterizedType.swift b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift index c391ae41a..a1b2c6b60 100644 --- a/Sources/SwiftJavaToolLib/JavaParameterizedType.swift +++ b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift @@ -25,4 +25,14 @@ struct SwiftJavaParameterizedType { self.name = name self.typeArguments = typeArguments } + + func render() -> String { + if typeArguments.isEmpty { + name + } else { + "\(name)<\(typeArguments.joined(separator: ", "))>" + } + } + + } diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index 66abefe67..05561f510 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -262,8 +262,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObjects { """, """ - @JavaStaticMethod - public func requireNonNull(_ arg0: JavaObject?, _ arg1: MySupplier?) -> JavaObject! + @JavaStaticMethod + public func requireNonNull(_ arg0: JavaObject?, _ arg1: MySupplier?) -> T """, ] ) @@ -675,8 +675,19 @@ func assertTranslatedClass( \(translatedDecls.map { $0.description }.joined(separator: "\n")) """ + // Helper function to normalize whitespace by trimming leading whitespace from each line + func normalizeWhitespace(_ text: String) -> String { + return text.components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespaces) } + .joined(separator: "\n") + } + + let normalizedSwiftFileText = normalizeWhitespace(swiftFileText) + for expectedChunk in expectedChunks { - if swiftFileText.contains(expectedChunk) { + let normalizedExpectedChunk = normalizeWhitespace(expectedChunk) + + if normalizedSwiftFileText.contains(normalizedExpectedChunk) { continue } From e9cfec119b56806f4d55a7259f905fb40c632091 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 10 Nov 2025 15:14:51 +0900 Subject: [PATCH 14/28] further correct generic handling and type variables --- .../org/swift/swiftkitffm/ThrowsFFMTest.java | 41 ------ .../TypeVariable+Extensions.swift | 57 ++++++++ .../generated/TypeVariable.swift | 2 +- .../JavaClassTranslator.swift | 1 - .../JavaGenericsSupport.swift | 95 ++++++++++++ Sources/SwiftJavaToolLib/JavaTranslator.swift | 58 ++++++-- .../CompileJavaWrapTools.swift | 22 +-- .../Java2SwiftTests.swift | 137 +++++++++--------- .../SwiftJavaToolLibTests/WrapJavaTests.swift | 47 +++++- 9 files changed, 323 insertions(+), 137 deletions(-) delete mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java create mode 100644 Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift create mode 100644 Sources/SwiftJavaToolLib/JavaGenericsSupport.swift diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java deleted file mode 100644 index ea08c321b..000000000 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java +++ /dev/null @@ -1,41 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -package com.example.swift; - -import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.SwiftArena; - -import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalInt; -import java.util.OptionalLong; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ThrowsFFMTest { - @Test - void throwSwiftError() { - try (var arena = SwiftArena.ofConfined()) { - var funcs = ThrowingFuncs.init(12, arena); - funcs.throwError(); - } catch (Exception ex) { - assertEquals("java.lang.Exception: MyExampleSwiftError(message: \"yes, it\\'s an error!\")", ex.toString()); - return; - } - - throw new AssertionError("Expected Swift error to be thrown as exception"); - } -} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift new file mode 100644 index 000000000..621e91526 --- /dev/null +++ b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import SwiftJava + +// FIXME: all interfaces should ahve these https://github.com/swiftlang/swift-java/issues/430 +extension TypeVariable { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 + +} + +// FIXME: All Java objects are Hashable, we should handle that accordingly. +extension TypeVariable: Hashable { + + public func hash(into hasher: inout Hasher) { + guard let pojo = self.as(JavaObject.self) else { + return + } + + hasher.combine(pojo.hashCode()) + } + + public static func == (lhs: TypeVariable, rhs: TypeVariable) -> Bool { + guard let lhpojo: JavaObject = lhs.as(JavaObject.self) else { + return false + } + guard let rhpojo: JavaObject = rhs.as(JavaObject.self) else { + return false + } + + return lhpojo.equals(rhpojo) + } + +} diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index 736fcfde6..71184ed4d 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -6,7 +6,7 @@ import CSwiftJavaJNI public struct TypeVariable { @JavaMethod public func getGenericDeclaration() -> GenericDeclaration! - + @JavaMethod public func getAnnotatedBounds() -> [AnnotatedType?] diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 45fdd6a85..b609caaea 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -879,7 +879,6 @@ extension JavaClassTranslator { /// method. func isOverride(_ method: Method) -> Bool { var currentSuperclass = effectiveJavaSuperclass - print("currentSuperclass = \(currentSuperclass)") while let currentSuperclassNonOpt = currentSuperclass { // Set the loop up for the next run. defer { diff --git a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift new file mode 100644 index 000000000..848cd5432 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaLangReflect +import JavaTypes +import SwiftBasicFormat +import SwiftJava +import SwiftJavaConfigurationShared +import SwiftSyntax +import SwiftSyntaxBuilder + +struct GenericJavaTypeOriginInfo { + enum GenericSource { + /// The source of the generic + case `class`([Type]) + case method + } + + var source: GenericSource + var type: Type +} + +private func collectTypeVariables(_ type: Type) -> Set> { + var result: Set> = [] + + return result +} + +/// if the type (that is used by the Method) is generic, return if the use originates from the method, or a surrounding class. +func getGenericJavaTypeOriginInfo(_ type: Type?, from method: Method) -> [GenericJavaTypeOriginInfo] { + guard let type else { + return [] + } + + guard isGenericJavaType(type) else { + return [] // it's not a generic type, no "origin" of the use to detect + } + + var methodTypeVars = method.getTypeParameters() + + // TODO: also handle nested classes here... + var classTypeVars = method.getDeclaringClass().getTypeParameters() + + var usedTypeVars: [TypeVariable] = [] + + return [] +} + +func isGenericJavaType(_ type: Type?) -> Bool { + guard let type else { + return false + } + + // Check if it's a type variable (e.g., T, E, etc.) + if type.as(TypeVariable.self) != nil { + return true + } + + // Check if it's a parameterized type (e.g., List, Map) + if let paramType = type.as(ParameterizedType.self) { + let typeArgs: [Type?] = paramType.getActualTypeArguments() + + // Check if any of the type arguments are generic + for typeArg in typeArgs { + guard let typeArg else { continue } + if isGenericJavaType(typeArg) { + return true + } + } + } + + // Check if it's a generic array type (e.g., T[], List[]) + if let arrayType = type.as(GenericArrayType.self) { + let componentType = arrayType.getGenericComponentType() + return isGenericJavaType(componentType) + } + + // Check if it's a wildcard type (e.g., ? extends Number, ? super String) + if type.as(WildcardType.self) != nil { + return true + } + + return false +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 65a4538e2..8f5dfb9e0 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -120,20 +120,22 @@ extension JavaTranslator { // MARK: Type translation extension JavaTranslator { -func getSwiftReturnTypeNameAsString( + + + func getSwiftReturnTypeNameAsString( method: JavaLangReflect.Method, preferValueTypes: Bool, outerOptional: OptionalKind ) throws -> String { - let returnType = method.getReturnType() + // let returnType = method.getReturnType() let genericReturnType = method.getGenericReturnType() // Special handle the case when the return type is the generic type of the method: ` T foo()` - if returnType?.getCanonicalName() == "java.lang.Object" { - if let genericReturnType { - return genericReturnType.getTypeName() - } - } + + // if isGenericJavaType(genericReturnType) { + // print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") + // getGenericJavaTypeOriginInfo(genericReturnType, from: method) + // } return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) } @@ -154,16 +156,38 @@ func getSwiftReturnTypeNameAsString( // } // } + if isGenericJavaType(javaType) { + if let method { + print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") + let genericOriginInfos = getGenericJavaTypeOriginInfo(javaType, from: method) + print("genericOriginInfos = \(genericOriginInfos)") + } + } + // Replace type variables with their bounds. if let typeVariable = javaType.as(TypeVariable.self), typeVariable.getBounds().count == 1, let bound = typeVariable.getBounds()[0] { - return try getSwiftTypeNameAsString( - bound, - preferValueTypes: preferValueTypes, - outerOptional: outerOptional - ) + print("[swift] was type var: \(typeVariable)") + print("[swift] was type var: \(typeVariable.toString())") + print("[swift] was type var: \(typeVariable.getClass().getName())") + print("[swift] was type var bound: \(bound)") + // let typeName = try getSwiftTypeNameAsString( + // bound, + // preferValueTypes: preferValueTypes, + // outerOptional: outerOptional + // ) + // print("[swift] was type var: \(typeVariable.toString()) ----> \(typeName)") + return outerOptional.adjustTypeName(typeVariable.getName()) + } + + if let paramType = javaType.as(ParameterizedType.self) { + print("[swift] paramType = \(paramType)") + let typeArgs: [Type?] = paramType.getActualTypeArguments() + for typeArg in typeArgs { + print("Type arg = \(typeArg)") + } } // Replace wildcards with their upper bound. @@ -171,6 +195,7 @@ func getSwiftReturnTypeNameAsString( wildcardType.getUpperBounds().count == 1, let bound = wildcardType.getUpperBounds()[0] { + print("[swift] was wildcard") // Replace a wildcard type with its first bound. return try getSwiftTypeNameAsString( bound, @@ -181,6 +206,7 @@ func getSwiftReturnTypeNameAsString( // Handle array types by recursing into the component type. if let arrayType = javaType.as(GenericArrayType.self) { + print("[swift] was array") if preferValueTypes { let elementType = try getSwiftTypeNameAsString( arrayType.getGenericComponentType()!, @@ -238,11 +264,19 @@ func getSwiftReturnTypeNameAsString( } } + print("[swift][swift-java][debug] Convert direct \(javaType)") + // Handle direct references to Java classes. guard let javaClass = javaType.as(JavaClass.self) else { throw TranslationError.unhandledJavaType(javaType) } + print("[swift][swift-java][debug] Java class \(javaClass) | \(javaClass.toString())") + if isGenericJavaType(javaType) { + print("[swift][swift-java][debug] is generic \(javaClass.toString())") + } + + let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes) var resultString = swiftName if isOptional { diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift index 9ae433f11..68702b621 100644 --- a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -92,6 +92,7 @@ func withJavaTranslator( func assertWrapJavaOutput( javaClassNames: [String], classpath: [Foundation.URL], + assert assertBody: (JavaTranslator) throws -> Void = { _ in }, expectedChunks: [String], function: String = #function, file: StaticString = #filePath, @@ -143,17 +144,20 @@ func assertWrapJavaOutput( let swiftClassDecls = try translator.translateClass(javaClass) let importDecls = translator.getImportDecls() - let swiftFileText = - """ - // --------------------------------------------------------------------------- - // Auto-generated by Java-to-Swift wrapper generator. - \(importDecls.map { $0.description }.joined()) - \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) - \n - """ - swiftCompleteOutputText += swiftFileText + let swiftFileText = + """ + // --------------------------------------------------------------------------- + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + \n + """ + swiftCompleteOutputText += swiftFileText } + // Run any additional user defined assertions: + try assertBody(translator) + for expectedChunk in expectedChunks { // We make the matching in-sensitive to whitespace: let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "") diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index 05561f510..a65c7facd 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -49,12 +49,12 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObject { """, """ - @JavaMethod - public func toString() -> String + @JavaMethod + public func toString() -> String """, """ - @JavaMethod - public func wait() throws + @JavaMethod + public func wait() throws """ ] ) @@ -74,8 +74,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaClass { """, """ - @JavaStaticMethod - public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass + @JavaStaticMethod + public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass """, ] ) @@ -105,8 +105,8 @@ class Java2SwiftTests: XCTestCase { } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -123,12 +123,12 @@ class Java2SwiftTests: XCTestCase { ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """, """ - @JavaMethod - public func toArray(_ arg0: MyJavaIntFunction?) -> [JavaObject?] + @JavaMethod + public func toArray(_ arg0: MyJavaIntFunction?) -> [JavaObject?] """ ] ) @@ -144,8 +144,8 @@ class Java2SwiftTests: XCTestCase { ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """ ] ) @@ -167,8 +167,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.Redirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.Redirect! """, """ extension ProcessBuilder { @@ -184,8 +184,8 @@ class Java2SwiftTests: XCTestCase { public struct Type { """, """ - @JavaMethod - public func type() -> ProcessBuilder.Redirect.`Type`! + @JavaMethod + public func type() -> ProcessBuilder.Redirect.`Type`! """, ] ) @@ -207,8 +207,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.PBRedirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.PBRedirect! """, """ extension ProcessBuilder { @@ -224,8 +224,8 @@ class Java2SwiftTests: XCTestCase { public struct JavaType { """, """ - @JavaMethod - public func type() -> ProcessBuilder.PBRedirect.JavaType! + @JavaMethod + public func type() -> ProcessBuilder.PBRedirect.JavaType! """ ] ) @@ -263,7 +263,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaStaticMethod - public func requireNonNull(_ arg0: JavaObject?, _ arg1: MySupplier?) -> T + public func requireNonNull(_ arg0: T?, _ arg1: MySupplier?) -> T """, ] ) @@ -281,20 +281,20 @@ class Java2SwiftTests: XCTestCase { open class JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open func toString() -> String + @JavaMethod + open func toString() -> String """, """ - @JavaMethod - open func wait() throws + @JavaMethod + open func wait() throws """, """ - @JavaMethod - open func clone() throws -> JavaObject! + @JavaMethod + open func clone() throws -> JavaObject! """, ] ) @@ -315,24 +315,24 @@ class Java2SwiftTests: XCTestCase { open class JavaString: JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open override func toString() -> String + @JavaMethod + open override func toString() -> String """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, """ - @JavaMethod - open func intern() -> String + @JavaMethod + open func intern() -> String """, """ - @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> String + @JavaStaticMethod + public func valueOf(_ arg0: Int64) -> String """, ] ) @@ -366,8 +366,8 @@ class Java2SwiftTests: XCTestCase { } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -392,12 +392,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: ClassLoader { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open override func findResource(_ arg0: String) -> URL! + @JavaMethod + open override func findResource(_ arg0: String) -> URL! """, ] ) @@ -422,12 +422,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: JavaObject { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open func findResource(_ arg0: String) -> URL! + @JavaMethod + open func findResource(_ arg0: String) -> URL! """, ] ) @@ -452,8 +452,8 @@ class Java2SwiftTests: XCTestCase { open class JavaByte: JavaNumber { """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, ] ) @@ -475,8 +475,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaIntFunction { """, """ - @JavaMethod - public func apply(_ arg0: Int32) -> JavaObject! + @JavaMethod + public func apply(_ arg0: Int32) -> R! """, ] ) @@ -501,16 +501,16 @@ class Java2SwiftTests: XCTestCase { open class Method: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -535,16 +535,16 @@ class Java2SwiftTests: XCTestCase { open class Constructor: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable>?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable>?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -675,7 +675,6 @@ func assertTranslatedClass( \(translatedDecls.map { $0.description }.joined(separator: "\n")) """ - // Helper function to normalize whitespace by trimming leading whitespace from each line func normalizeWhitespace(_ text: String) -> String { return text.components(separatedBy: .newlines) .map { $0.trimmingCharacters(in: .whitespaces) } @@ -691,7 +690,7 @@ func assertTranslatedClass( continue } - XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line) + XCTFail("Expected chunk:\n---\n\(expectedChunk.yellow)\n---\nnot found in:\n===\n\(swiftFileText)\n===", file: file, line: line) } } } diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index a58b6b285..549f40597 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -23,7 +23,6 @@ import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/is final class WrapJavaTests: XCTestCase { - // @Test func testWrapJavaFromCompiledJavaSource() async throws { let classpathURL = try await compileJava( """ @@ -98,7 +97,6 @@ final class WrapJavaTests: XCTestCase { } // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params - // @Test func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { let classpathURL = try await compileJava( """ @@ -136,7 +134,6 @@ final class WrapJavaTests: XCTestCase { ) } - // @Test func testWrapJavaGenericMethod_multipleGenerics() async throws { let classpathURL = try await compileJava( """ @@ -187,6 +184,49 @@ final class WrapJavaTests: XCTestCase { ) } + func test_Java2Swift_returnType_generic() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class List {} + final class Map {} + + class GenericClass { + public T getClassGeneric() { return null; } + + public M getMethodGeneric() { return null; } + + public Map getMixedGeneric() { return null; } + + public String getNonGeneric() { return null; } + + public List getParameterizedClassGeneric() { return null; } + + public List getWildcard() { return null; } + + public T[] getGenericArray() { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.GenericClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getClassGeneric() -> T + """, + """ + @JavaMethod + open func getNonGeneric() -> String + """, + ] + ) + } + /* /Users/ktoso/code/voldemort-swift-java/.build/plugins/outputs/voldemort-swift-java/VoldemortSwiftJava/destination/SwiftJavaPlugin/generated/CompressingStore.swift:6:30: error: reference to generic type 'AbstractStore' requires arguments in <...> 4 | @@ -204,7 +244,6 @@ final class WrapJavaTests: XCTestCase { 7 | @JavaMethod 8 | @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) */ - // @Test func testGenericSuperclass() async throws { return // FIXME: we need this From 3b87d8fb72f3ba20eb93f0de5d427eaf0393bea0 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 10 Nov 2025 23:50:37 +0900 Subject: [PATCH 15/28] test fixes --- .../SwiftJavaExtractJNISampleApp/LICENSE.txt | 202 ------------------ .../Sources/MySwiftLibrary/NestedTypes.swift | 2 +- .../generated/Executable.swift | 3 - .../JavaClass+CustomStringConvertible.swift | 21 ++ Sources/SwiftJavaToolLib/JavaTranslator.swift | 55 +---- .../JNI/JNINestedTypesTests.swift | 11 +- Tests/SwiftJavaTests/BasicRuntimeTests.swift | 2 +- .../Java2SwiftTests.swift | 6 +- .../SwiftJavaToolLibTests/WrapJavaTests.swift | 17 -- 9 files changed, 33 insertions(+), 286 deletions(-) delete mode 100644 Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt create mode 100644 Sources/SwiftJava/JavaClass+CustomStringConvertible.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt b/Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt deleted file mode 100644 index d64569567..000000000 --- a/Samples/SwiftJavaExtractJNISampleApp/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift index b7edefa93..fb2b49248 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift @@ -44,4 +44,4 @@ public enum NestedEnum { public struct OneStruct { public init() {} } -} +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 94b03b8df..af9931d35 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -11,9 +11,6 @@ open class Executable: AccessibleObject { @JavaMethod open func getModifiers() -> Int32 - // @JavaMethod // FIXME why does this clash with Method.getTypeParameters? - // open func getTypeParameters() -> [TypeVariable?] - @JavaMethod open func getParameterTypes() -> [JavaClass?] diff --git a/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift new file mode 100644 index 000000000..e7eb25105 --- /dev/null +++ b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI + +extension JavaClass: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 8f5dfb9e0..1bcae82ae 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -147,55 +147,19 @@ extension JavaTranslator { preferValueTypes: Bool, outerOptional: OptionalKind ) throws -> String { - - // if let method, - // let parameterizedType = javaType.as(ParameterizedType.self) { - // if method.getGenericParameterTypes().contains(where: {$0?.getTypeName() == javaType.getTypeName()}) { - // fatalError("java type = \(javaType.as(ParameterizedType.self))") - // return javaType.getTypeName() - // } - // } - - if isGenericJavaType(javaType) { - if let method { - print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") - let genericOriginInfos = getGenericJavaTypeOriginInfo(javaType, from: method) - print("genericOriginInfos = \(genericOriginInfos)") - } - } - // Replace type variables with their bounds. if let typeVariable = javaType.as(TypeVariable.self), typeVariable.getBounds().count == 1, let bound = typeVariable.getBounds()[0] { - print("[swift] was type var: \(typeVariable)") - print("[swift] was type var: \(typeVariable.toString())") - print("[swift] was type var: \(typeVariable.getClass().getName())") - print("[swift] was type var bound: \(bound)") - // let typeName = try getSwiftTypeNameAsString( - // bound, - // preferValueTypes: preferValueTypes, - // outerOptional: outerOptional - // ) - // print("[swift] was type var: \(typeVariable.toString()) ----> \(typeName)") return outerOptional.adjustTypeName(typeVariable.getName()) } - if let paramType = javaType.as(ParameterizedType.self) { - print("[swift] paramType = \(paramType)") - let typeArgs: [Type?] = paramType.getActualTypeArguments() - for typeArg in typeArgs { - print("Type arg = \(typeArg)") - } - } - // Replace wildcards with their upper bound. if let wildcardType = javaType.as(WildcardType.self), wildcardType.getUpperBounds().count == 1, let bound = wildcardType.getUpperBounds()[0] { - print("[swift] was wildcard") // Replace a wildcard type with its first bound. return try getSwiftTypeNameAsString( bound, @@ -206,7 +170,6 @@ extension JavaTranslator { // Handle array types by recursing into the component type. if let arrayType = javaType.as(GenericArrayType.self) { - print("[swift] was array") if preferValueTypes { let elementType = try getSwiftTypeNameAsString( arrayType.getGenericComponentType()!, @@ -264,24 +227,18 @@ extension JavaTranslator { } } - print("[swift][swift-java][debug] Convert direct \(javaType)") - // Handle direct references to Java classes. guard let javaClass = javaType.as(JavaClass.self) else { throw TranslationError.unhandledJavaType(javaType) } - print("[swift][swift-java][debug] Java class \(javaClass) | \(javaClass.toString())") - if isGenericJavaType(javaType) { - print("[swift][swift-java][debug] is generic \(javaClass.toString())") - } - - let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes) - var resultString = swiftName - if isOptional { - resultString = outerOptional.adjustTypeName(resultString) - } + let resultString = + if isOptional { + outerOptional.adjustTypeName(swiftName) + } else { + swiftName + } return resultString } diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift index f5321318e..67d966e3a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -13,13 +13,11 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import SwiftJavaConfigurationShared import Testing @Suite struct JNINestedTypesTests { let source1 = """ - """ public class A { public class B { public func g(c: C) {} @@ -28,14 +26,7 @@ struct JNINestedTypesTests { public func h(b: B) {} } } - """ - } - func test_nested_in_extension() throws { - var config = Configuration() - config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) - st.log.logLevel = .error public func f(a: A, b: A.B, c: A.B.C) {} """ @@ -143,4 +134,4 @@ struct JNINestedTypesTests { ] ) } -} +} \ No newline at end of file diff --git a/Tests/SwiftJavaTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift index 53630f386..ec082e09a 100644 --- a/Tests/SwiftJavaTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -73,7 +73,7 @@ class BasicRuntimeTests: XCTestCase { do { _ = try JavaClass(environment: environment) } catch { - XCTAssertEqual(String(describing: error), "java.lang.NoClassDefFoundError: org/swift/javakit/Nonexistent") + XCTAssertEqual(String(describing: error), "java.lang.NoClassDefFoundError(org/swift/javakit/Nonexistent)") } } diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index a65c7facd..6be490b85 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -128,7 +128,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public func toArray(_ arg0: MyJavaIntFunction?) -> [JavaObject?] + public func toArray(_ arg0: MyJavaIntFunction?) -> [T?] """ ] ) @@ -536,7 +536,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - open func getTypeParameters() -> [TypeVariable>?] + open func getTypeParameters() -> [TypeVariable>?] """, """ @JavaMethod @@ -544,7 +544,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - open override func getDeclaringClass() -> JavaClass! + open override func getDeclaringClass() -> JavaClass! """, ] ) diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index 549f40597..1c3d10d24 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -227,23 +227,6 @@ final class WrapJavaTests: XCTestCase { ) } - /* - /Users/ktoso/code/voldemort-swift-java/.build/plugins/outputs/voldemort-swift-java/VoldemortSwiftJava/destination/SwiftJavaPlugin/generated/CompressingStore.swift:6:30: error: reference to generic type 'AbstractStore' requires arguments in <...> - 4 | - 5 | @JavaClass("voldemort.store.compress.CompressingStore") - 6 | open class CompressingStore: AbstractStore { - | `- error: reference to generic type 'AbstractStore' requires arguments in <...> - 7 | @JavaMethod - 8 | open override func getCapability(_ arg0: StoreCapabilityType?) -> JavaObject! - -/Users/ktoso/code/voldemort-swift-java/.build/plugins/outputs/voldemort-swift-java/VoldemortSwiftJava/destination/SwiftJavaPlugin/generated/AbstractStore.swift:6:12: note: generic type 'AbstractStore' declared here - 4 | - 5 | @JavaClass("voldemort.store.AbstractStore", implements: Store.self) - 6 | open class AbstractStore: JavaObject { - | `- note: generic type 'AbstractStore' declared here - 7 | @JavaMethod - 8 | @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) - */ func testGenericSuperclass() async throws { return // FIXME: we need this From 2d932e6b02f07fd9e8a83ba112d2767f0677da37 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 09:06:26 +0900 Subject: [PATCH 16/28] try debugging JNI on CI --- Samples/JavaKitSampleApp/ci-validate.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Samples/JavaKitSampleApp/ci-validate.sh b/Samples/JavaKitSampleApp/ci-validate.sh index f453a00be..369ad6962 100755 --- a/Samples/JavaKitSampleApp/ci-validate.sh +++ b/Samples/JavaKitSampleApp/ci-validate.sh @@ -5,6 +5,7 @@ set -x swift build "$JAVA_HOME/bin/java" \ + -verbose:jni \ -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \ -Djava.library.path=.build/debug \ "com.example.swift.JavaKitSampleMain" From d81ff4f49541a50b9ca2e0039f1bf6a114aa4211 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 12:37:41 +0900 Subject: [PATCH 17/28] undo work directory changes in resolve command --- Sources/SwiftJavaTool/Commands/ResolveCommand.swift | 4 ++-- Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index b30ed9689..7cc4c477d 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -104,8 +104,8 @@ extension SwiftJava.ResolveCommand { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") - let workDir = URL(fileURLWithPath: self.commonOptions.outputDirectory!) - .appending(path: "resolver-dir") + let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + .appendingPathComponent(".build") let dependenciesClasspath = await resolveDependencies(workDir: workDir, dependencies: dependencies) let classpathEntries = dependenciesClasspath.split(separator: ":") diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index d2ac37414..655c67650 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -25,7 +25,6 @@ extension JavaTranslator { } for (javaClassName, swiftName) in classes { - print("SET = \(javaClassName)") // XXX translatedClasses[javaClassName] = SwiftTypeName(module: swiftModule, name: swiftName) } } From 010db5e65610dc36d0d43966e8acb429637b4d5a Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 12:44:05 +0900 Subject: [PATCH 18/28] disable experimental prebuilts in example run --- Samples/JavaDependencySampleApp/ci-validate.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index b6162fdce..b13f99f61 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -4,7 +4,9 @@ set -e set -x # invoke resolve as part of a build run -swift run --disable-sandbox +swift run \ + --disable-experimental-prebuilts \ + --disable-sandbox # explicitly invoke resolve without explicit path or dependency # the dependencies should be uses from the --swift-module From b3aa62fd53ec7a9c9dba87da27c0dba1debfe567 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 12:50:17 +0900 Subject: [PATCH 19/28] get more detailed output from JavaDependencySampleApp sample --- Samples/JavaDependencySampleApp/ci-validate.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index b13f99f61..49ddb34c3 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -4,7 +4,7 @@ set -e set -x # invoke resolve as part of a build run -swift run \ +swift build -v \ --disable-experimental-prebuilts \ --disable-sandbox @@ -12,7 +12,7 @@ swift run \ # the dependencies should be uses from the --swift-module # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 -swift run \ +swift run -v \ --disable-experimental-prebuilts \ swift-java resolve \ Sources/JavaCommonsCSV/swift-java.config \ From 070ae877ede654518c78d22d12bcdf875ed1b736 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 17:25:15 +0900 Subject: [PATCH 20/28] wip on enums so we can not crash in JavaDependencySampleApp --- .../Sources/JavaCommonsCSV/swift-java.config | 3 +++ .../TypeVariable+Extensions.swift | 6 ++++++ .../generated/ParameterizedType.swift | 21 +++++++++++++++++++ .../generated/TypeVariable.swift | 2 +- .../Exceptions/Throwable+Error.swift | 2 +- .../SwiftJava/generated/CharSequence.swift | 3 +++ Sources/SwiftJava/generated/JavaClass.swift | 2 +- Sources/SwiftJava/generated/JavaEnum.swift | 6 ++++++ .../SwiftJava/generated/JavaOptional.swift | 2 +- Sources/SwiftJava/swift-java.config | 1 + .../JavaGenericsSupport.swift | 6 ------ .../SwiftJavaToolLib/JavaType+Equality.swift | 0 12 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 Sources/SwiftJava/generated/JavaEnum.swift create mode 100644 Sources/SwiftJavaToolLib/JavaType+Equality.swift diff --git a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config index 3b685159d..c39bf2e90 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config +++ b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config @@ -6,6 +6,9 @@ "org.apache.commons.csv.CSVParser" : "CSVParser", "org.apache.commons.csv.CSVRecord" : "CSVRecord" }, + "classes-ignore" : { + "org.apache.commons.csv.CSVFormat$Predefined" : "", + }, "dependencies" : [ "org.apache.commons:commons-csv:1.12.0" ] diff --git a/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift index 621e91526..157ae353d 100644 --- a/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift +++ b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift @@ -55,3 +55,9 @@ extension TypeVariable: Hashable { } } + +extension TypeVariable { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index 5e29ee052..984c2b16e 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -16,3 +16,24 @@ public struct ParameterizedType { @JavaMethod public func getTypeName() -> String } + +extension ParameterizedType { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 +} + +extension ParameterizedType: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index 71184ed4d..7bf8f7ba2 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -3,7 +3,7 @@ import SwiftJava import CSwiftJavaJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) -public struct TypeVariable { +public struct TypeVariable: CustomStringConvertible { @JavaMethod public func getGenericDeclaration() -> GenericDeclaration! diff --git a/Sources/SwiftJava/Exceptions/Throwable+Error.swift b/Sources/SwiftJava/Exceptions/Throwable+Error.swift index e51e12056..fbf8393dd 100644 --- a/Sources/SwiftJava/Exceptions/Throwable+Error.swift +++ b/Sources/SwiftJava/Exceptions/Throwable+Error.swift @@ -15,7 +15,7 @@ // Translate all Java Throwable instances in a Swift error. extension Throwable: Error, CustomStringConvertible { public var description: String { - "\(getClass().getCanonicalName())(\(getMessage()))" + return toString() } } diff --git a/Sources/SwiftJava/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift index eadc509eb..ee5dca369 100644 --- a/Sources/SwiftJava/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -9,6 +9,9 @@ public struct CharSequence { @JavaMethod public func toString() -> String + @JavaMethod + public func getChars(_ arg0: Int32, _ arg1: Int32, _ arg2: [UInt16], _ arg3: Int32) + @JavaMethod public func charAt(_ arg0: Int32) -> UInt16 diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 157f53890..a1147e3c2 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -61,7 +61,7 @@ open class JavaClass: JavaObject { open func getClassLoader() -> JavaClassLoader! @JavaMethod - open func newInstance() throws -> JavaObject! + open func newInstance() throws -> T! @JavaMethod open func isMemberClass() -> Bool diff --git a/Sources/SwiftJava/generated/JavaEnum.swift b/Sources/SwiftJava/generated/JavaEnum.swift new file mode 100644 index 000000000..97818f020 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaEnum.swift @@ -0,0 +1,6 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.Enum") +open class JavaEnum: JavaObject { +} diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 08cc764a1..2b0b803ec 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -4,7 +4,7 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { @JavaMethod - open func get() -> JavaObject! + open func get() -> T! @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index d07ff1620..7ba407321 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -16,6 +16,7 @@ "java.lang.Long" : "JavaLong", "java.lang.Number" : "JavaNumber", "java.lang.Object" : "JavaObject", + "java.lang.Enum" : "JavaEnum", "java.lang.RuntimeException" : "RuntimeException", "java.lang.Short" : "JavaShort", "java.lang.String" : "JavaString", diff --git a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift index 848cd5432..7c7819629 100644 --- a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift +++ b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift @@ -31,12 +31,6 @@ struct GenericJavaTypeOriginInfo { var type: Type } -private func collectTypeVariables(_ type: Type) -> Set> { - var result: Set> = [] - - return result -} - /// if the type (that is used by the Method) is generic, return if the use originates from the method, or a surrounding class. func getGenericJavaTypeOriginInfo(_ type: Type?, from method: Method) -> [GenericJavaTypeOriginInfo] { guard let type else { diff --git a/Sources/SwiftJavaToolLib/JavaType+Equality.swift b/Sources/SwiftJavaToolLib/JavaType+Equality.swift new file mode 100644 index 000000000..e69de29bb From 94c70c321fc7ac141352606ebf8b9960b68d056b Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 22:37:38 +0900 Subject: [PATCH 21/28] implement improved include/exclude filtering for wrap-java --- Package.swift | 3 + .../Sources/JavaCommonsCSV/swift-java.config | 7 +- .../SwiftJava/JVM/JavaVirtualMachine.swift | 22 ++ Sources/SwiftJava/JavaObjectHolder.swift | 4 +- .../SwiftJava/generated/JavaCharacter.swift | 328 +++++++++--------- Sources/SwiftJava/generated/JavaEnum.swift | 15 +- Sources/SwiftJava/swift-java.config | 1 - .../Configuration.swift | 6 +- .../Commands/ConfigureCommand.swift | 17 +- .../Commands/JExtractCommand.swift | 10 +- .../Commands/WrapJavaCommand.swift | 38 +- Sources/SwiftJavaTool/CommonOptions.swift | 21 +- Sources/SwiftJavaTool/ExcludedJDKTypes.swift | 42 +++ .../SwiftJavaBaseAsyncParsableCommand.swift | 4 +- .../JavaClassTranslator.swift | 13 +- Sources/SwiftJavaToolLib/JavaTranslator.swift | 8 +- .../JNI/JNIUnsignedNumberTests.swift | 1 - Tests/SwiftJavaTests/BasicRuntimeTests.swift | 4 +- .../Java2SwiftTests.swift | 4 +- 19 files changed, 334 insertions(+), 214 deletions(-) create mode 100644 Sources/SwiftJavaTool/ExcludedJDKTypes.swift diff --git a/Package.swift b/Package.swift index a8fa25e7e..82a2b685b 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,9 @@ func findJavaHome() -> String { print("JAVA_HOME = \(home)") return home } + if let opts = ProcessInfo.processInfo.environment["JAVA_OPTS"] { + print("JAVA_OPTS = \(opts)") + } // This is a workaround for envs (some IDEs) which have trouble with // picking up env variables during the build process diff --git a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config index c39bf2e90..dc38efd9f 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config +++ b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config @@ -1,14 +1,13 @@ { "classes" : { "org.apache.commons.io.FilenameUtils" : "FilenameUtils", - "org.apache.commons.io.IOCase" : "IOCase", "org.apache.commons.csv.CSVFormat" : "CSVFormat", "org.apache.commons.csv.CSVParser" : "CSVParser", "org.apache.commons.csv.CSVRecord" : "CSVRecord" }, - "classes-ignore" : { - "org.apache.commons.csv.CSVFormat$Predefined" : "", - }, + "filterExclude" : [ + "org.apache.commons.csv.CSVFormat$Predefined", + ], "dependencies" : [ "org.apache.commons:commons-csv:1.12.0" ] diff --git a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift index c711b1f65..205c32aa8 100644 --- a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -94,6 +94,10 @@ public final class JavaVirtualMachine: @unchecked Sendable { allVMOptions.append("-Djava.class.path=\(pathSeparatedClassPath)") } allVMOptions.append(contentsOf: vmOptions) + + // Append VM options from Environment + allVMOptions.append(contentsOf: vmOptions) + allVMOptions.append(contentsOf: Self.getSwiftJavaJVMEnvOptions()) // Convert the options let optionsBuffer = UnsafeMutableBufferPointer.allocate(capacity: allVMOptions.count) @@ -421,6 +425,24 @@ extension JavaVirtualMachine { sharedJVMPointer.classpath = [] } } + + /// Parse JVM options from the SWIFT_JAVA_JVM_OPTIONS environment variable. + /// + /// For example, to enable verbose JNI logging you can do: + /// ``` + /// export JAVA_OPTS="-verbose:jni" + /// ``` + public static func getSwiftJavaJVMEnvOptions() -> [String] { + guard let optionsString = ProcessInfo.processInfo.environment["JAVA_OPTS"], + !optionsString.isEmpty else { + return [] + } + + return optionsString + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + } } extension JavaVirtualMachine { diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 319a09e83..5930da59b 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -17,8 +17,8 @@ import CSwiftJavaJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object /// while this instance is live. -public class JavaObjectHolder { - public private(set) var object: jobject? +public final class JavaObjectHolder { + public private(set) var object: jobject? // TODO: thread-safety public let environment: JNIEnvironment /// Take a reference to a Java object and promote it to a global reference diff --git a/Sources/SwiftJava/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift index 406b45ee2..f79742a3b 100644 --- a/Sources/SwiftJava/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1557,985 +1557,985 @@ extension JavaCharacter { if let COMMON = classObj.COMMON { self.init(javaHolder: COMMON.javaHolder) } else { - fatalError("Enum value COMMON was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COMMON was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LATIN: if let LATIN = classObj.LATIN { self.init(javaHolder: LATIN.javaHolder) } else { - fatalError("Enum value LATIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LATIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GREEK: if let GREEK = classObj.GREEK { self.init(javaHolder: GREEK.javaHolder) } else { - fatalError("Enum value GREEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GREEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYRILLIC: if let CYRILLIC = classObj.CYRILLIC { self.init(javaHolder: CYRILLIC.javaHolder) } else { - fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARMENIAN: if let ARMENIAN = classObj.ARMENIAN { self.init(javaHolder: ARMENIAN.javaHolder) } else { - fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HEBREW: if let HEBREW = classObj.HEBREW { self.init(javaHolder: HEBREW.javaHolder) } else { - fatalError("Enum value HEBREW was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HEBREW was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARABIC: if let ARABIC = classObj.ARABIC { self.init(javaHolder: ARABIC.javaHolder) } else { - fatalError("Enum value ARABIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARABIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYRIAC: if let SYRIAC = classObj.SYRIAC { self.init(javaHolder: SYRIAC.javaHolder) } else { - fatalError("Enum value SYRIAC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYRIAC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAANA: if let THAANA = classObj.THAANA { self.init(javaHolder: THAANA.javaHolder) } else { - fatalError("Enum value THAANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DEVANAGARI: if let DEVANAGARI = classObj.DEVANAGARI { self.init(javaHolder: DEVANAGARI.javaHolder) } else { - fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BENGALI: if let BENGALI = classObj.BENGALI { self.init(javaHolder: BENGALI.javaHolder) } else { - fatalError("Enum value BENGALI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BENGALI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GURMUKHI: if let GURMUKHI = classObj.GURMUKHI { self.init(javaHolder: GURMUKHI.javaHolder) } else { - fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUJARATI: if let GUJARATI = classObj.GUJARATI { self.init(javaHolder: GUJARATI.javaHolder) } else { - fatalError("Enum value GUJARATI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUJARATI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ORIYA: if let ORIYA = classObj.ORIYA { self.init(javaHolder: ORIYA.javaHolder) } else { - fatalError("Enum value ORIYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ORIYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAMIL: if let TAMIL = classObj.TAMIL { self.init(javaHolder: TAMIL.javaHolder) } else { - fatalError("Enum value TAMIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAMIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TELUGU: if let TELUGU = classObj.TELUGU { self.init(javaHolder: TELUGU.javaHolder) } else { - fatalError("Enum value TELUGU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TELUGU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KANNADA: if let KANNADA = classObj.KANNADA { self.init(javaHolder: KANNADA.javaHolder) } else { - fatalError("Enum value KANNADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KANNADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MALAYALAM: if let MALAYALAM = classObj.MALAYALAM { self.init(javaHolder: MALAYALAM.javaHolder) } else { - fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SINHALA: if let SINHALA = classObj.SINHALA { self.init(javaHolder: SINHALA.javaHolder) } else { - fatalError("Enum value SINHALA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SINHALA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAI: if let THAI = classObj.THAI { self.init(javaHolder: THAI.javaHolder) } else { - fatalError("Enum value THAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LAO: if let LAO = classObj.LAO { self.init(javaHolder: LAO.javaHolder) } else { - fatalError("Enum value LAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIBETAN: if let TIBETAN = classObj.TIBETAN { self.init(javaHolder: TIBETAN.javaHolder) } else { - fatalError("Enum value TIBETAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIBETAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MYANMAR: if let MYANMAR = classObj.MYANMAR { self.init(javaHolder: MYANMAR.javaHolder) } else { - fatalError("Enum value MYANMAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MYANMAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GEORGIAN: if let GEORGIAN = classObj.GEORGIAN { self.init(javaHolder: GEORGIAN.javaHolder) } else { - fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANGUL: if let HANGUL = classObj.HANGUL { self.init(javaHolder: HANGUL.javaHolder) } else { - fatalError("Enum value HANGUL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANGUL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ETHIOPIC: if let ETHIOPIC = classObj.ETHIOPIC { self.init(javaHolder: ETHIOPIC.javaHolder) } else { - fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHEROKEE: if let CHEROKEE = classObj.CHEROKEE { self.init(javaHolder: CHEROKEE.javaHolder) } else { - fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CANADIAN_ABORIGINAL: if let CANADIAN_ABORIGINAL = classObj.CANADIAN_ABORIGINAL { self.init(javaHolder: CANADIAN_ABORIGINAL.javaHolder) } else { - fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OGHAM: if let OGHAM = classObj.OGHAM { self.init(javaHolder: OGHAM.javaHolder) } else { - fatalError("Enum value OGHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OGHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .RUNIC: if let RUNIC = classObj.RUNIC { self.init(javaHolder: RUNIC.javaHolder) } else { - fatalError("Enum value RUNIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value RUNIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHMER: if let KHMER = classObj.KHMER { self.init(javaHolder: KHMER.javaHolder) } else { - fatalError("Enum value KHMER was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHMER was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MONGOLIAN: if let MONGOLIAN = classObj.MONGOLIAN { self.init(javaHolder: MONGOLIAN.javaHolder) } else { - fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HIRAGANA: if let HIRAGANA = classObj.HIRAGANA { self.init(javaHolder: HIRAGANA.javaHolder) } else { - fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KATAKANA: if let KATAKANA = classObj.KATAKANA { self.init(javaHolder: KATAKANA.javaHolder) } else { - fatalError("Enum value KATAKANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KATAKANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BOPOMOFO: if let BOPOMOFO = classObj.BOPOMOFO { self.init(javaHolder: BOPOMOFO.javaHolder) } else { - fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HAN: if let HAN = classObj.HAN { self.init(javaHolder: HAN.javaHolder) } else { - fatalError("Enum value HAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YI: if let YI = classObj.YI { self.init(javaHolder: YI.javaHolder) } else { - fatalError("Enum value YI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_ITALIC: if let OLD_ITALIC = classObj.OLD_ITALIC { self.init(javaHolder: OLD_ITALIC.javaHolder) } else { - fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GOTHIC: if let GOTHIC = classObj.GOTHIC { self.init(javaHolder: GOTHIC.javaHolder) } else { - fatalError("Enum value GOTHIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GOTHIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DESERET: if let DESERET = classObj.DESERET { self.init(javaHolder: DESERET.javaHolder) } else { - fatalError("Enum value DESERET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DESERET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INHERITED: if let INHERITED = classObj.INHERITED { self.init(javaHolder: INHERITED.javaHolder) } else { - fatalError("Enum value INHERITED was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INHERITED was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGALOG: if let TAGALOG = classObj.TAGALOG { self.init(javaHolder: TAGALOG.javaHolder) } else { - fatalError("Enum value TAGALOG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGALOG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANUNOO: if let HANUNOO = classObj.HANUNOO { self.init(javaHolder: HANUNOO.javaHolder) } else { - fatalError("Enum value HANUNOO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANUNOO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUHID: if let BUHID = classObj.BUHID { self.init(javaHolder: BUHID.javaHolder) } else { - fatalError("Enum value BUHID was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUHID was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGBANWA: if let TAGBANWA = classObj.TAGBANWA { self.init(javaHolder: TAGBANWA.javaHolder) } else { - fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LIMBU: if let LIMBU = classObj.LIMBU { self.init(javaHolder: LIMBU.javaHolder) } else { - fatalError("Enum value LIMBU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LIMBU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_LE: if let TAI_LE = classObj.TAI_LE { self.init(javaHolder: TAI_LE.javaHolder) } else { - fatalError("Enum value TAI_LE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_LE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_B: if let LINEAR_B = classObj.LINEAR_B { self.init(javaHolder: LINEAR_B.javaHolder) } else { - fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UGARITIC: if let UGARITIC = classObj.UGARITIC { self.init(javaHolder: UGARITIC.javaHolder) } else { - fatalError("Enum value UGARITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UGARITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHAVIAN: if let SHAVIAN = classObj.SHAVIAN { self.init(javaHolder: SHAVIAN.javaHolder) } else { - fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSMANYA: if let OSMANYA = classObj.OSMANYA { self.init(javaHolder: OSMANYA.javaHolder) } else { - fatalError("Enum value OSMANYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSMANYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRIOT: if let CYPRIOT = classObj.CYPRIOT { self.init(javaHolder: CYPRIOT.javaHolder) } else { - fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAILLE: if let BRAILLE = classObj.BRAILLE { self.init(javaHolder: BRAILLE.javaHolder) } else { - fatalError("Enum value BRAILLE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAILLE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUGINESE: if let BUGINESE = classObj.BUGINESE { self.init(javaHolder: BUGINESE.javaHolder) } else { - fatalError("Enum value BUGINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUGINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .COPTIC: if let COPTIC = classObj.COPTIC { self.init(javaHolder: COPTIC.javaHolder) } else { - fatalError("Enum value COPTIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COPTIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEW_TAI_LUE: if let NEW_TAI_LUE = classObj.NEW_TAI_LUE { self.init(javaHolder: NEW_TAI_LUE.javaHolder) } else { - fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GLAGOLITIC: if let GLAGOLITIC = classObj.GLAGOLITIC { self.init(javaHolder: GLAGOLITIC.javaHolder) } else { - fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIFINAGH: if let TIFINAGH = classObj.TIFINAGH { self.init(javaHolder: TIFINAGH.javaHolder) } else { - fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYLOTI_NAGRI: if let SYLOTI_NAGRI = classObj.SYLOTI_NAGRI { self.init(javaHolder: SYLOTI_NAGRI.javaHolder) } else { - fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERSIAN: if let OLD_PERSIAN = classObj.OLD_PERSIAN { self.init(javaHolder: OLD_PERSIAN.javaHolder) } else { - fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHAROSHTHI: if let KHAROSHTHI = classObj.KHAROSHTHI { self.init(javaHolder: KHAROSHTHI.javaHolder) } else { - fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BALINESE: if let BALINESE = classObj.BALINESE { self.init(javaHolder: BALINESE.javaHolder) } else { - fatalError("Enum value BALINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BALINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CUNEIFORM: if let CUNEIFORM = classObj.CUNEIFORM { self.init(javaHolder: CUNEIFORM.javaHolder) } else { - fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHOENICIAN: if let PHOENICIAN = classObj.PHOENICIAN { self.init(javaHolder: PHOENICIAN.javaHolder) } else { - fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHAGS_PA: if let PHAGS_PA = classObj.PHAGS_PA { self.init(javaHolder: PHAGS_PA.javaHolder) } else { - fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NKO: if let NKO = classObj.NKO { self.init(javaHolder: NKO.javaHolder) } else { - fatalError("Enum value NKO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NKO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SUNDANESE: if let SUNDANESE = classObj.SUNDANESE { self.init(javaHolder: SUNDANESE.javaHolder) } else { - fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BATAK: if let BATAK = classObj.BATAK { self.init(javaHolder: BATAK.javaHolder) } else { - fatalError("Enum value BATAK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BATAK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LEPCHA: if let LEPCHA = classObj.LEPCHA { self.init(javaHolder: LEPCHA.javaHolder) } else { - fatalError("Enum value LEPCHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LEPCHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OL_CHIKI: if let OL_CHIKI = classObj.OL_CHIKI { self.init(javaHolder: OL_CHIKI.javaHolder) } else { - fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VAI: if let VAI = classObj.VAI { self.init(javaHolder: VAI.javaHolder) } else { - fatalError("Enum value VAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAURASHTRA: if let SAURASHTRA = classObj.SAURASHTRA { self.init(javaHolder: SAURASHTRA.javaHolder) } else { - fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAYAH_LI: if let KAYAH_LI = classObj.KAYAH_LI { self.init(javaHolder: KAYAH_LI.javaHolder) } else { - fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .REJANG: if let REJANG = classObj.REJANG { self.init(javaHolder: REJANG.javaHolder) } else { - fatalError("Enum value REJANG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value REJANG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYCIAN: if let LYCIAN = classObj.LYCIAN { self.init(javaHolder: LYCIAN.javaHolder) } else { - fatalError("Enum value LYCIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYCIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CARIAN: if let CARIAN = classObj.CARIAN { self.init(javaHolder: CARIAN.javaHolder) } else { - fatalError("Enum value CARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYDIAN: if let LYDIAN = classObj.LYDIAN { self.init(javaHolder: LYDIAN.javaHolder) } else { - fatalError("Enum value LYDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAM: if let CHAM = classObj.CHAM { self.init(javaHolder: CHAM.javaHolder) } else { - fatalError("Enum value CHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_THAM: if let TAI_THAM = classObj.TAI_THAM { self.init(javaHolder: TAI_THAM.javaHolder) } else { - fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_VIET: if let TAI_VIET = classObj.TAI_VIET { self.init(javaHolder: TAI_VIET.javaHolder) } else { - fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AVESTAN: if let AVESTAN = classObj.AVESTAN { self.init(javaHolder: AVESTAN.javaHolder) } else { - fatalError("Enum value AVESTAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AVESTAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .EGYPTIAN_HIEROGLYPHS: if let EGYPTIAN_HIEROGLYPHS = classObj.EGYPTIAN_HIEROGLYPHS { self.init(javaHolder: EGYPTIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAMARITAN: if let SAMARITAN = classObj.SAMARITAN { self.init(javaHolder: SAMARITAN.javaHolder) } else { - fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANDAIC: if let MANDAIC = classObj.MANDAIC { self.init(javaHolder: MANDAIC.javaHolder) } else { - fatalError("Enum value MANDAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANDAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LISU: if let LISU = classObj.LISU { self.init(javaHolder: LISU.javaHolder) } else { - fatalError("Enum value LISU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LISU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BAMUM: if let BAMUM = classObj.BAMUM { self.init(javaHolder: BAMUM.javaHolder) } else { - fatalError("Enum value BAMUM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BAMUM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .JAVANESE: if let JAVANESE = classObj.JAVANESE { self.init(javaHolder: JAVANESE.javaHolder) } else { - fatalError("Enum value JAVANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value JAVANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEETEI_MAYEK: if let MEETEI_MAYEK = classObj.MEETEI_MAYEK { self.init(javaHolder: MEETEI_MAYEK.javaHolder) } else { - fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .IMPERIAL_ARAMAIC: if let IMPERIAL_ARAMAIC = classObj.IMPERIAL_ARAMAIC { self.init(javaHolder: IMPERIAL_ARAMAIC.javaHolder) } else { - fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOUTH_ARABIAN: if let OLD_SOUTH_ARABIAN = classObj.OLD_SOUTH_ARABIAN { self.init(javaHolder: OLD_SOUTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PARTHIAN: if let INSCRIPTIONAL_PARTHIAN = classObj.INSCRIPTIONAL_PARTHIAN { self.init(javaHolder: INSCRIPTIONAL_PARTHIAN.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PAHLAVI: if let INSCRIPTIONAL_PAHLAVI = classObj.INSCRIPTIONAL_PAHLAVI { self.init(javaHolder: INSCRIPTIONAL_PAHLAVI.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_TURKIC: if let OLD_TURKIC = classObj.OLD_TURKIC { self.init(javaHolder: OLD_TURKIC.javaHolder) } else { - fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAHMI: if let BRAHMI = classObj.BRAHMI { self.init(javaHolder: BRAHMI.javaHolder) } else { - fatalError("Enum value BRAHMI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAHMI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAITHI: if let KAITHI = classObj.KAITHI { self.init(javaHolder: KAITHI.javaHolder) } else { - fatalError("Enum value KAITHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAITHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_HIEROGLYPHS: if let MEROITIC_HIEROGLYPHS = classObj.MEROITIC_HIEROGLYPHS { self.init(javaHolder: MEROITIC_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_CURSIVE: if let MEROITIC_CURSIVE = classObj.MEROITIC_CURSIVE { self.init(javaHolder: MEROITIC_CURSIVE.javaHolder) } else { - fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SORA_SOMPENG: if let SORA_SOMPENG = classObj.SORA_SOMPENG { self.init(javaHolder: SORA_SOMPENG.javaHolder) } else { - fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAKMA: if let CHAKMA = classObj.CHAKMA { self.init(javaHolder: CHAKMA.javaHolder) } else { - fatalError("Enum value CHAKMA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAKMA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHARADA: if let SHARADA = classObj.SHARADA { self.init(javaHolder: SHARADA.javaHolder) } else { - fatalError("Enum value SHARADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHARADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAKRI: if let TAKRI = classObj.TAKRI { self.init(javaHolder: TAKRI.javaHolder) } else { - fatalError("Enum value TAKRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAKRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MIAO: if let MIAO = classObj.MIAO { self.init(javaHolder: MIAO.javaHolder) } else { - fatalError("Enum value MIAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MIAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CAUCASIAN_ALBANIAN: if let CAUCASIAN_ALBANIAN = classObj.CAUCASIAN_ALBANIAN { self.init(javaHolder: CAUCASIAN_ALBANIAN.javaHolder) } else { - fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BASSA_VAH: if let BASSA_VAH = classObj.BASSA_VAH { self.init(javaHolder: BASSA_VAH.javaHolder) } else { - fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DUPLOYAN: if let DUPLOYAN = classObj.DUPLOYAN { self.init(javaHolder: DUPLOYAN.javaHolder) } else { - fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELBASAN: if let ELBASAN = classObj.ELBASAN { self.init(javaHolder: ELBASAN.javaHolder) } else { - fatalError("Enum value ELBASAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELBASAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GRANTHA: if let GRANTHA = classObj.GRANTHA { self.init(javaHolder: GRANTHA.javaHolder) } else { - fatalError("Enum value GRANTHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GRANTHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAHAWH_HMONG: if let PAHAWH_HMONG = classObj.PAHAWH_HMONG { self.init(javaHolder: PAHAWH_HMONG.javaHolder) } else { - fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHOJKI: if let KHOJKI = classObj.KHOJKI { self.init(javaHolder: KHOJKI.javaHolder) } else { - fatalError("Enum value KHOJKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHOJKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_A: if let LINEAR_A = classObj.LINEAR_A { self.init(javaHolder: LINEAR_A.javaHolder) } else { - fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAHAJANI: if let MAHAJANI = classObj.MAHAJANI { self.init(javaHolder: MAHAJANI.javaHolder) } else { - fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANICHAEAN: if let MANICHAEAN = classObj.MANICHAEAN { self.init(javaHolder: MANICHAEAN.javaHolder) } else { - fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MENDE_KIKAKUI: if let MENDE_KIKAKUI = classObj.MENDE_KIKAKUI { self.init(javaHolder: MENDE_KIKAKUI.javaHolder) } else { - fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MODI: if let MODI = classObj.MODI { self.init(javaHolder: MODI.javaHolder) } else { - fatalError("Enum value MODI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MODI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MRO: if let MRO = classObj.MRO { self.init(javaHolder: MRO.javaHolder) } else { - fatalError("Enum value MRO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MRO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_NORTH_ARABIAN: if let OLD_NORTH_ARABIAN = classObj.OLD_NORTH_ARABIAN { self.init(javaHolder: OLD_NORTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NABATAEAN: if let NABATAEAN = classObj.NABATAEAN { self.init(javaHolder: NABATAEAN.javaHolder) } else { - fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PALMYRENE: if let PALMYRENE = classObj.PALMYRENE { self.init(javaHolder: PALMYRENE.javaHolder) } else { - fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAU_CIN_HAU: if let PAU_CIN_HAU = classObj.PAU_CIN_HAU { self.init(javaHolder: PAU_CIN_HAU.javaHolder) } else { - fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERMIC: if let OLD_PERMIC = classObj.OLD_PERMIC { self.init(javaHolder: OLD_PERMIC.javaHolder) } else { - fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PSALTER_PAHLAVI: if let PSALTER_PAHLAVI = classObj.PSALTER_PAHLAVI { self.init(javaHolder: PSALTER_PAHLAVI.javaHolder) } else { - fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIDDHAM: if let SIDDHAM = classObj.SIDDHAM { self.init(javaHolder: SIDDHAM.javaHolder) } else { - fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHUDAWADI: if let KHUDAWADI = classObj.KHUDAWADI { self.init(javaHolder: KHUDAWADI.javaHolder) } else { - fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIRHUTA: if let TIRHUTA = classObj.TIRHUTA { self.init(javaHolder: TIRHUTA.javaHolder) } else { - fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WARANG_CITI: if let WARANG_CITI = classObj.WARANG_CITI { self.init(javaHolder: WARANG_CITI.javaHolder) } else { - fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AHOM: if let AHOM = classObj.AHOM { self.init(javaHolder: AHOM.javaHolder) } else { - fatalError("Enum value AHOM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AHOM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ANATOLIAN_HIEROGLYPHS: if let ANATOLIAN_HIEROGLYPHS = classObj.ANATOLIAN_HIEROGLYPHS { self.init(javaHolder: ANATOLIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HATRAN: if let HATRAN = classObj.HATRAN { self.init(javaHolder: HATRAN.javaHolder) } else { - fatalError("Enum value HATRAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HATRAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MULTANI: if let MULTANI = classObj.MULTANI { self.init(javaHolder: MULTANI.javaHolder) } else { - fatalError("Enum value MULTANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MULTANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_HUNGARIAN: if let OLD_HUNGARIAN = classObj.OLD_HUNGARIAN { self.init(javaHolder: OLD_HUNGARIAN.javaHolder) } else { - fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIGNWRITING: if let SIGNWRITING = classObj.SIGNWRITING { self.init(javaHolder: SIGNWRITING.javaHolder) } else { - fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ADLAM: if let ADLAM = classObj.ADLAM { self.init(javaHolder: ADLAM.javaHolder) } else { - fatalError("Enum value ADLAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ADLAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BHAIKSUKI: if let BHAIKSUKI = classObj.BHAIKSUKI { self.init(javaHolder: BHAIKSUKI.javaHolder) } else { - fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MARCHEN: if let MARCHEN = classObj.MARCHEN { self.init(javaHolder: MARCHEN.javaHolder) } else { - fatalError("Enum value MARCHEN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MARCHEN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEWA: if let NEWA = classObj.NEWA { self.init(javaHolder: NEWA.javaHolder) } else { - fatalError("Enum value NEWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSAGE: if let OSAGE = classObj.OSAGE { self.init(javaHolder: OSAGE.javaHolder) } else { - fatalError("Enum value OSAGE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSAGE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGUT: if let TANGUT = classObj.TANGUT { self.init(javaHolder: TANGUT.javaHolder) } else { - fatalError("Enum value TANGUT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGUT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MASARAM_GONDI: if let MASARAM_GONDI = classObj.MASARAM_GONDI { self.init(javaHolder: MASARAM_GONDI.javaHolder) } else { - fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NUSHU: if let NUSHU = classObj.NUSHU { self.init(javaHolder: NUSHU.javaHolder) } else { - fatalError("Enum value NUSHU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NUSHU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOYOMBO: if let SOYOMBO = classObj.SOYOMBO { self.init(javaHolder: SOYOMBO.javaHolder) } else { - fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ZANABAZAR_SQUARE: if let ZANABAZAR_SQUARE = classObj.ZANABAZAR_SQUARE { self.init(javaHolder: ZANABAZAR_SQUARE.javaHolder) } else { - fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANIFI_ROHINGYA: if let HANIFI_ROHINGYA = classObj.HANIFI_ROHINGYA { self.init(javaHolder: HANIFI_ROHINGYA.javaHolder) } else { - fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOGDIAN: if let OLD_SOGDIAN = classObj.OLD_SOGDIAN { self.init(javaHolder: OLD_SOGDIAN.javaHolder) } else { - fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOGDIAN: if let SOGDIAN = classObj.SOGDIAN { self.init(javaHolder: SOGDIAN.javaHolder) } else { - fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DOGRA: if let DOGRA = classObj.DOGRA { self.init(javaHolder: DOGRA.javaHolder) } else { - fatalError("Enum value DOGRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DOGRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUNJALA_GONDI: if let GUNJALA_GONDI = classObj.GUNJALA_GONDI { self.init(javaHolder: GUNJALA_GONDI.javaHolder) } else { - fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAKASAR: if let MAKASAR = classObj.MAKASAR { self.init(javaHolder: MAKASAR.javaHolder) } else { - fatalError("Enum value MAKASAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAKASAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEDEFAIDRIN: if let MEDEFAIDRIN = classObj.MEDEFAIDRIN { self.init(javaHolder: MEDEFAIDRIN.javaHolder) } else { - fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELYMAIC: if let ELYMAIC = classObj.ELYMAIC { self.init(javaHolder: ELYMAIC.javaHolder) } else { - fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NANDINAGARI: if let NANDINAGARI = classObj.NANDINAGARI { self.init(javaHolder: NANDINAGARI.javaHolder) } else { - fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NYIAKENG_PUACHUE_HMONG: if let NYIAKENG_PUACHUE_HMONG = classObj.NYIAKENG_PUACHUE_HMONG { self.init(javaHolder: NYIAKENG_PUACHUE_HMONG.javaHolder) } else { - fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WANCHO: if let WANCHO = classObj.WANCHO { self.init(javaHolder: WANCHO.javaHolder) } else { - fatalError("Enum value WANCHO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WANCHO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YEZIDI: if let YEZIDI = classObj.YEZIDI { self.init(javaHolder: YEZIDI.javaHolder) } else { - fatalError("Enum value YEZIDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YEZIDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHORASMIAN: if let CHORASMIAN = classObj.CHORASMIAN { self.init(javaHolder: CHORASMIAN.javaHolder) } else { - fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DIVES_AKURU: if let DIVES_AKURU = classObj.DIVES_AKURU { self.init(javaHolder: DIVES_AKURU.javaHolder) } else { - fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHITAN_SMALL_SCRIPT: if let KHITAN_SMALL_SCRIPT = classObj.KHITAN_SMALL_SCRIPT { self.init(javaHolder: KHITAN_SMALL_SCRIPT.javaHolder) } else { - fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VITHKUQI: if let VITHKUQI = classObj.VITHKUQI { self.init(javaHolder: VITHKUQI.javaHolder) } else { - fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_UYGHUR: if let OLD_UYGHUR = classObj.OLD_UYGHUR { self.init(javaHolder: OLD_UYGHUR.javaHolder) } else { - fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRO_MINOAN: if let CYPRO_MINOAN = classObj.CYPRO_MINOAN { self.init(javaHolder: CYPRO_MINOAN.javaHolder) } else { - fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGSA: if let TANGSA = classObj.TANGSA { self.init(javaHolder: TANGSA.javaHolder) } else { - fatalError("Enum value TANGSA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGSA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TOTO: if let TOTO = classObj.TOTO { self.init(javaHolder: TOTO.javaHolder) } else { - fatalError("Enum value TOTO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TOTO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAWI: if let KAWI = classObj.KAWI { self.init(javaHolder: KAWI.javaHolder) } else { - fatalError("Enum value KAWI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAWI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NAG_MUNDARI: if let NAG_MUNDARI = classObj.NAG_MUNDARI { self.init(javaHolder: NAG_MUNDARI.javaHolder) } else { - fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UNKNOWN: if let UNKNOWN = classObj.UNKNOWN { self.init(javaHolder: UNKNOWN.javaHolder) } else { - fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run swift-java on the most updated Java class") } } } diff --git a/Sources/SwiftJava/generated/JavaEnum.swift b/Sources/SwiftJava/generated/JavaEnum.swift index 97818f020..2b8e102c9 100644 --- a/Sources/SwiftJava/generated/JavaEnum.swift +++ b/Sources/SwiftJava/generated/JavaEnum.swift @@ -1,6 +1,11 @@ -// Auto-generated by Java-to-Swift wrapper generator. -import CSwiftJavaJNI +// // Auto-generated by Java-to-Swift wrapper generator. +// import CSwiftJavaJNI -@JavaClass("java.lang.Enum") -open class JavaEnum: JavaObject { -} +// @JavaClass("java.lang.Enum") +// open class JavaEnum: JavaObject { +// @JavaMethod +// public func name() -> String + +// @JavaMethod +// public func ordinal() -> Int32 +// } diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index 7ba407321..d07ff1620 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -16,7 +16,6 @@ "java.lang.Long" : "JavaLong", "java.lang.Number" : "JavaNumber", "java.lang.Object" : "JavaObject", - "java.lang.Enum" : "JavaEnum", "java.lang.RuntimeException" : "RuntimeException", "java.lang.Short" : "JavaShort", "java.lang.String" : "JavaString", diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index f08f72363..0fb5494f1 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -86,8 +86,10 @@ public struct Configuration: Codable { public var targetCompatibility: JavaVersion? /// Filter input Java types by their package prefix if set. - /// Can be overriden by `--filter-java-package`. - public var filterJavaPackage: String? + public var filterInclude: [String]? + + /// Exclude input Java types by their package prefix or exact match. + public var filterExclude: [String]? // ==== dependencies --------------------------------------------------------- diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index c560b9488..837d5e2f2 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -103,17 +103,16 @@ extension SwiftJava.ConfigureCommand { var log = Self.log log.logLevel = .init(rawValue: self.logLevel.rawValue)! - log.info("Run: emit configuration...") var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage { - log.debug("Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") - } else if let filterJavaPackage = configuration.filterJavaPackage { + if !self.commonOptions.filterInclude.isEmpty { + log.debug("Generate Java->Swift type mappings. Active include filters: \(self.commonOptions.filterInclude)") + } else if let filters = configuration.filterInclude, !filters.isEmpty { // take the package filter from the configuration file - self.commonJVMOptions.filterJavaPackage = filterJavaPackage + self.commonOptions.filterInclude = filters } else { - log.debug("Generate Java->Swift type mappings. No package filter applied.") + log.debug("Generate Java->Swift type mappings. No package include filter applied.") } log.debug("Classpath: \(classpathEntries)") @@ -195,10 +194,8 @@ extension SwiftJava.ConfigureCommand { let javaCanonicalName = String(entry.getName().replacing("/", with: ".") .dropLast(".class".count)) - - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage, - !javaCanonicalName.hasPrefix(filterJavaPackage) { - // Skip classes which don't match our expected prefix + guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else { + log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters") continue } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index b5942d2a4..b5c3a7bb9 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -86,9 +86,7 @@ extension SwiftJava { extension SwiftJava.JExtractCommand { func runSwiftJavaCommand(config: inout Configuration) async throws { - if let javaPackage { - config.javaPackage = javaPackage - } + configure(&config.javaPackage, overrideWith: self.javaPackage) configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava @@ -136,12 +134,6 @@ extension SwiftJava.JExtractCommand { } } } - - func configure(_ setting: inout T?, overrideWith value: T?) { - if let value { - setting = value - } - } } struct IncompatibleModeError: Error { diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 600337208..ae985e772 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -66,6 +66,9 @@ extension SwiftJava { extension SwiftJava.WrapJavaCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + configure(&config.filterInclude, append: self.commonOptions.filterInclude) + configure(&config.filterExclude, append: self.commonOptions.filterExclude) + // Get base classpath configuration for this target and configuration var classpathSearchDirs = [self.effectiveSwiftModuleURL] if let cacheDir = self.cacheDirectory { @@ -113,7 +116,6 @@ extension SwiftJava.WrapJavaCommand { extension SwiftJava.WrapJavaCommand { mutating func generateWrappers( config: Configuration, - // classpathEntries: [String], dependentConfigs: [(String, Configuration)], environment: JNIEnvironment ) throws { @@ -124,6 +126,9 @@ extension SwiftJava.WrapJavaCommand { translateAsClass: true ) + log.info("Active include filters: \(config.filterInclude ?? [])") + log.info("Active exclude filters: \(config.filterExclude ?? [])") + // Keep track of all of the Java classes that will have // Swift-native implementations. translator.swiftNativeImplementations = Set(swiftNativeImplementation) @@ -143,14 +148,23 @@ extension SwiftJava.WrapJavaCommand { let classLoader = try! JavaClass(environment: environment) .getSystemClassLoader()! var javaClasses: [JavaClass] = [] - for (javaClassName, _) in config.classes ?? [:] { + eachClass: for (javaClassName, _) in config.classes ?? [:] { - if let filterPackage = config.filterJavaPackage { - if javaClassName.starts(with: filterPackage) { - log.info("SKIP Wrapping java type: \(javaClassName)") + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") continue } } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + continue eachClass + } + } + log.info("Wrapping java type: \(javaClassName)") guard let javaClass = try classLoader.loadClass(javaClassName) else { @@ -188,6 +202,20 @@ extension SwiftJava.WrapJavaCommand { return nil } + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + return nil + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + return nil + } + } // If this class has been explicitly mentioned, we're done. guard translator.translatedClasses[javaClassName] == nil else { diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index e8aee62df..ebdfdbea0 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -30,6 +30,18 @@ protocol HasCommonOptions { var commonOptions: SwiftJava.CommonOptions { get set } } extension HasCommonOptions { + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } + + func configure(_ setting: inout [T]?, append value: [T]?) { + if let value { + setting?.append(contentsOf: value) + } + } + var outputDirectory: String? { self.commonOptions.outputDirectory } @@ -45,6 +57,12 @@ extension SwiftJava { @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") var logLevel: JExtractSwiftLib.Logger.Level = .info + + @Option(name: .long, help: "While scanning a classpath, inspect ONLY types included in these packages") + var filterInclude: [String] = [] + + @Option(name: .long, help: "While scanning a classpath, skip types which match the filter prefix") + var filterExclude: [String] = [] } struct CommonJVMOptions: ParsableArguments { @@ -53,9 +71,6 @@ extension SwiftJava { help: "Class search path of directories and zip/jar files from which Java classes can be loaded." ) var classpath: [String] = [] - - @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package") - var filterJavaPackage: String? = nil } } diff --git a/Sources/SwiftJavaTool/ExcludedJDKTypes.swift b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift new file mode 100644 index 000000000..1d24022f5 --- /dev/null +++ b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +extension SwiftJava { + /// Some types we cannot handle importing, so we hardcode skipping them. + public static let ExcludedJDKTypes: Set = [ + "java.lang.Enum", + "java.lang.Enum$EnumDesc", + ] + + static func shouldImport(javaCanonicalName: String, commonOptions: SwiftJava.CommonOptions) -> Bool { + if SwiftJava.ExcludedJDKTypes.contains(javaCanonicalName) { + return false + } + + for include in commonOptions.filterInclude { + guard javaCanonicalName.hasPrefix(include) else { + // Skip classes which don't match our expected prefix + return false + } + } + + for exclude in commonOptions.filterExclude { + if javaCanonicalName.hasPrefix(exclude) { + return false + } + } + + return true + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 8413ae6fd..9763b4b38 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -81,7 +81,7 @@ extension SwiftJavaBaseAsyncParsableCommand { // If we haven't tried to create the output directory yet, do so now before // we write any files to it. - // if !createdOutputDirectory { // FIXME: do we need this + // if !createdOutputDirectory { try FileManager.default.createDirectory( at: outputDir, withIntermediateDirectories: true @@ -124,6 +124,8 @@ extension SwiftJavaBaseAsyncParsableCommand { if outputDirectory == "-" { return nil } +// print("[debug][swift-java] Module base directory based on outputDirectory!") +// return URL(fileURLWithPath: outputDirectory) } // Put the result into Sources/\(swiftModule). diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index b609caaea..4b5058dcb 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -16,6 +16,7 @@ import SwiftJava import JavaLangReflect import SwiftSyntax import SwiftJavaConfigurationShared +import Logging /// Utility type that translates a single Java class into its corresponding /// Swift type and any additional helper types or functions. @@ -24,6 +25,10 @@ struct JavaClassTranslator { /// needed for translation. let translator: JavaTranslator + var log: Logger { + translator.log + } + /// The Java class (or interface) being translated. let javaClass: JavaClass @@ -785,7 +790,7 @@ extension JavaClassTranslator { ? "self.init(javaHolder: \($0.getName()).javaHolder)" : "self = \($0.getName())") } else { - fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run swift-java on the most updated Java class") } """ }.joined(separator: "\n")) @@ -957,7 +962,11 @@ extension Type { /// Determine whether this type is equivalent to or a subtype of the other /// type. - func isEqualTo(_ other: Type) -> Bool { + func isEqualTo(_ other: Type, file: String = #file, line: Int = #line, function: String = #function) -> Bool { + if self.javaHolder.object == other.javaHolder.object { + return true + } + // First, adjust types to their bounds, if we need to. var anyAdjusted: Bool = false let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 1bcae82ae..71e011794 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -20,12 +20,15 @@ import SwiftSyntax import SwiftJavaConfigurationShared import SwiftSyntaxBuilder import Foundation +import Logging /// Utility that translates Java classes into Swift source code to access /// those Java classes. package class JavaTranslator { let config: Configuration + let log: Logger + /// The name of the Swift module that we are translating into. let swiftModuleName: String @@ -81,6 +84,10 @@ package class JavaTranslator { self.environment = environment self.translateAsClass = translateAsClass self.format = format + + var l = Logger(label: "swift-java") + l.logLevel = .init(rawValue: (config.logLevel ?? .info).rawValue)! + self.log = l } /// Clear out any per-file state when we want to start a new file. @@ -121,7 +128,6 @@ extension JavaTranslator { // MARK: Type translation extension JavaTranslator { - func getSwiftReturnTypeNameAsString( method: JavaLangReflect.Method, preferValueTypes: Bool, diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index 61a94062b..0833b21f3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -47,7 +47,6 @@ final class JNIUnsignedNumberTests { func jni_unsignedInt_annotate() throws { var config = Configuration() config.unsignedNumbersMode = .annotate - config.logLevel = .trace try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", diff --git a/Tests/SwiftJavaTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift index ec082e09a..b6c18bee5 100644 --- a/Tests/SwiftJavaTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -56,7 +56,7 @@ class BasicRuntimeTests: XCTestCase { do { _ = try URL("bad url", environment: environment) } catch { - XCTAssertEqual(String(describing: error), "java.net.MalformedURLException(no protocol: bad url)") + XCTAssertEqual(String(describing: error), "java.net.MalformedURLException: no protocol: bad url") } } @@ -73,7 +73,7 @@ class BasicRuntimeTests: XCTestCase { do { _ = try JavaClass(environment: environment) } catch { - XCTAssertEqual(String(describing: error), "java.lang.NoClassDefFoundError(org/swift/javakit/Nonexistent)") + XCTAssertEqual(String(describing: error), "java.lang.NoClassDefFoundError: org/swift/javakit/Nonexistent") } } diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index 6be490b85..e302fdc5a 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -101,7 +101,7 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self = APRIL } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ @@ -362,7 +362,7 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self.init(javaHolder: APRIL.javaHolder) } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ From e054f366ba20d87e9fb0e211dad504f7f53bb20e Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 23:17:08 +0900 Subject: [PATCH 22/28] move JavaType equality extensions to separate file --- .../JavaClassTranslator.swift | 115 --------------- .../SwiftJavaToolLib/JavaType+Equality.swift | 135 ++++++++++++++++++ 2 files changed, 135 insertions(+), 115 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 4b5058dcb..795639e5a 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -939,118 +939,3 @@ extension [Type?] { } } -extension Type { - /// Adjust the given type to use its bounds, mirroring what we do in - /// mapping Java types into Swift. - func adjustToJavaBounds(adjusted: inout Bool) -> Type { - if let typeVariable = self.as(TypeVariable.self), - typeVariable.getBounds().count == 1, - let bound = typeVariable.getBounds()[0] { - adjusted = true - return bound - } - - if let wildcardType = self.as(WildcardType.self), - wildcardType.getUpperBounds().count == 1, - let bound = wildcardType.getUpperBounds()[0] { - adjusted = true - return bound - } - - return self - } - - /// Determine whether this type is equivalent to or a subtype of the other - /// type. - func isEqualTo(_ other: Type, file: String = #file, line: Int = #line, function: String = #function) -> Bool { - if self.javaHolder.object == other.javaHolder.object { - return true - } - - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualTo(adjustedOther) - } - - // If both are classes, check for equivalence. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - return selfClass.equals(otherClass.as(JavaObject.self)) - } - - // If both are arrays, check that their component types are equivalent. - if let selfArray = self.as(GenericArrayType.self), - let otherArray = other.as(GenericArrayType.self) { - return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) - } - - // If both are parameterized types, check their raw type and type - // arguments for equivalence. - if let selfParameterizedType = self.as(ParameterizedType.self), - let otherParameterizedType = other.as(ParameterizedType.self) { - if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { - return false - } - - return selfParameterizedType.getActualTypeArguments() - .allTypesEqual(otherParameterizedType.getActualTypeArguments()) - } - - // If both are type variables, compare their bounds. - // FIXME: This is a hack. - if let selfTypeVariable = self.as(TypeVariable.self), - let otherTypeVariable = other.as(TypeVariable.self) { - return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) - } - - // If both are wildcards, compare their upper and lower bounds. - if let selfWildcard = self.as(WildcardType.self), - let otherWildcard = other.as(WildcardType.self) { - return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) - && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) - } - - return false - } - - /// Determine whether this type is equivalent to or a subtype of the - /// other type. - func isEqualToOrSubtypeOf(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) - } - - if isEqualTo(other) { - return true - } - - // If both are classes, check for subclassing. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - // If either is a Java array, then this cannot be a subtype relationship - // in Swift. - if selfClass.isArray() || otherClass.isArray() { - return false - } - - return selfClass.isSubclass(of: otherClass) - } - - // Anything object-like is a subclass of java.lang.Object - if let otherClass = other.as(JavaClass.self), - otherClass.getName() == "java.lang.Object" { - if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || - self.is(WildcardType.self) || self.is(TypeVariable.self) { - return true - } - } - return false - } -} diff --git a/Sources/SwiftJavaToolLib/JavaType+Equality.swift b/Sources/SwiftJavaToolLib/JavaType+Equality.swift index e69de29bb..b3d4a37a4 100644 --- a/Sources/SwiftJavaToolLib/JavaType+Equality.swift +++ b/Sources/SwiftJavaToolLib/JavaType+Equality.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava +import JavaLangReflect +import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging + +extension Type { + /// Adjust the given type to use its bounds, mirroring what we do in + /// mapping Java types into Swift. + func adjustToJavaBounds(adjusted: inout Bool) -> Type { + if let typeVariable = self.as(TypeVariable.self), + typeVariable.getBounds().count == 1, + let bound = typeVariable.getBounds()[0] { + adjusted = true + return bound + } + + if let wildcardType = self.as(WildcardType.self), + wildcardType.getUpperBounds().count == 1, + let bound = wildcardType.getUpperBounds()[0] { + adjusted = true + return bound + } + + return self + } + + /// Determine whether this type is equivalent to or a subtype of the other + /// type. + func isEqualTo(_ other: Type, file: String = #file, line: Int = #line, function: String = #function) -> Bool { + if self.javaHolder.object == other.javaHolder.object { + return true + } + + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualTo(adjustedOther) + } + + // If both are classes, check for equivalence. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + return selfClass.equals(otherClass.as(JavaObject.self)) + } + + // If both are arrays, check that their component types are equivalent. + if let selfArray = self.as(GenericArrayType.self), + let otherArray = other.as(GenericArrayType.self) { + return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) + } + + // If both are parameterized types, check their raw type and type + // arguments for equivalence. + if let selfParameterizedType = self.as(ParameterizedType.self), + let otherParameterizedType = other.as(ParameterizedType.self) { + if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { + return false + } + + return selfParameterizedType.getActualTypeArguments() + .allTypesEqual(otherParameterizedType.getActualTypeArguments()) + } + + // If both are type variables, compare their bounds. + // FIXME: This is a hack. + if let selfTypeVariable = self.as(TypeVariable.self), + let otherTypeVariable = other.as(TypeVariable.self) { + return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) + } + + // If both are wildcards, compare their upper and lower bounds. + if let selfWildcard = self.as(WildcardType.self), + let otherWildcard = other.as(WildcardType.self) { + return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) + && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) + } + + return false + } + + /// Determine whether this type is equivalent to or a subtype of the + /// other type. + func isEqualToOrSubtypeOf(_ other: Type) -> Bool { + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) + } + + if isEqualTo(other) { + return true + } + + // If both are classes, check for subclassing. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + // If either is a Java array, then this cannot be a subtype relationship + // in Swift. + if selfClass.isArray() || otherClass.isArray() { + return false + } + + return selfClass.isSubclass(of: otherClass) + } + + // Anything object-like is a subclass of java.lang.Object + if let otherClass = other.as(JavaClass.self), + otherClass.getName() == "java.lang.Object" { + if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || + self.is(WildcardType.self) || self.is(TypeVariable.self) { + return true + } + } + return false + } +} From b5de9d925cc279d7b1812d6c1f2235864747d927 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 23:23:03 +0900 Subject: [PATCH 23/28] rename to SWIFT_JAVA_JAVA_OPTS --- Package.swift | 4 ++-- Sources/SwiftJava/JVM/JavaVirtualMachine.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index 82a2b685b..7d872795a 100644 --- a/Package.swift +++ b/Package.swift @@ -14,8 +14,8 @@ func findJavaHome() -> String { print("JAVA_HOME = \(home)") return home } - if let opts = ProcessInfo.processInfo.environment["JAVA_OPTS"] { - print("JAVA_OPTS = \(opts)") + if let opts = ProcessInfo.processInfo.environment["SWIFT_JAVA_JAVA_OPTS"] { + print("SWIFT_JAVA_JAVA_OPTS = \(opts)") } // This is a workaround for envs (some IDEs) which have trouble with diff --git a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift index 205c32aa8..6fe95c934 100644 --- a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -430,10 +430,10 @@ extension JavaVirtualMachine { /// /// For example, to enable verbose JNI logging you can do: /// ``` - /// export JAVA_OPTS="-verbose:jni" + /// export SWIFT_JAVA_JAVA_OPTS="-verbose:jni" /// ``` public static func getSwiftJavaJVMEnvOptions() -> [String] { - guard let optionsString = ProcessInfo.processInfo.environment["JAVA_OPTS"], + guard let optionsString = ProcessInfo.processInfo.environment["SWIFT_JAVA_JAVA_OPTS"], !optionsString.isEmpty else { return [] } From 636387abcd70c6a64ec592e070f09716e944e997 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 23:55:23 +0900 Subject: [PATCH 24/28] remove verbose output options --- Samples/JavaDependencySampleApp/ci-validate.sh | 4 ++-- Samples/JavaKitSampleApp/ci-validate.sh | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index 49ddb34c3..feeb87675 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -4,7 +4,7 @@ set -e set -x # invoke resolve as part of a build run -swift build -v \ +swift build \ --disable-experimental-prebuilts \ --disable-sandbox @@ -12,7 +12,7 @@ swift build -v \ # the dependencies should be uses from the --swift-module # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 -swift run -v \ +swift run \ --disable-experimental-prebuilts \ swift-java resolve \ Sources/JavaCommonsCSV/swift-java.config \ diff --git a/Samples/JavaKitSampleApp/ci-validate.sh b/Samples/JavaKitSampleApp/ci-validate.sh index 369ad6962..f453a00be 100755 --- a/Samples/JavaKitSampleApp/ci-validate.sh +++ b/Samples/JavaKitSampleApp/ci-validate.sh @@ -5,7 +5,6 @@ set -x swift build "$JAVA_HOME/bin/java" \ - -verbose:jni \ -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \ -Djava.library.path=.build/debug \ "com.example.swift.JavaKitSampleMain" From 31e1d9b449ad38ca84af8de3aa4163b410f43d13 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 11 Nov 2025 23:55:27 +0900 Subject: [PATCH 25/28] shouldn't we use the deferEnv here? --- .../JNI/JNISwift2JavaGenerator+NativeTranslation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 13e8c676e..f76ea7bde 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -852,7 +852,7 @@ extension JNISwift2JavaGenerator { // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") for globalRef in globalRefs { - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") + printer.print("deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") } } if isThrowing { From c1e652ff45bca8ea02da86f19b3827d9f554a0c4 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 12 Nov 2025 11:54:39 +0900 Subject: [PATCH 26/28] fix deferEnv in async tests --- .../JNI/JNIAsyncTests.swift | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 975cccc6a..1930e601a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -66,7 +66,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() environment = try! JavaVirtualMachine.shared().environment() @@ -79,7 +79,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() environment = try! JavaVirtualMachine.shared().environment() @@ -140,7 +140,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } do { let swiftResult$ = await try SwiftModule.async() @@ -160,7 +160,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } do { let swiftResult$ = await try SwiftModule.async() @@ -228,7 +228,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try! JavaVirtualMachine.shared().environment() @@ -242,7 +242,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try! JavaVirtualMachine.shared().environment() @@ -319,7 +319,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) environment = try! JavaVirtualMachine.shared().environment() @@ -336,7 +336,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) environment = try! JavaVirtualMachine.shared().environment() @@ -397,8 +397,8 @@ struct JNIAsyncTests { ... defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) - environment.interface.DeleteGlobalRef(deferEnvironment, s) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, s) } ... environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))]) From ae855d0487da49a0065bbd1b807900a53fb5617e Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 12 Nov 2025 13:08:52 +0900 Subject: [PATCH 27/28] Undo all changes to JavaVirtualMachine --- .../com/example/swift/HelloJava2SwiftJNI.java | 2 - .../SwiftJava/JVM/JavaVirtualMachine.swift | 198 +++--------------- 2 files changed, 33 insertions(+), 167 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 3109f64e9..407ebe7ce 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -16,8 +16,6 @@ // Import swift-extract generated sources -// Import javakit/swiftkit support libraries - import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.SwiftLibraries; diff --git a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift index 6fe95c934..1c7936a34 100644 --- a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -39,22 +39,21 @@ public final class JavaVirtualMachine: @unchecked Sendable { /// Thread-local storage to detach from thread on exit private static let destroyTLS = ThreadLocalStorage { _ in - debug("Run destroyThreadLocalStorage; call JVM.shared() detach current thread") try? JavaVirtualMachine.shared().detachCurrentThread() } /// The Java virtual machine instance. private let jvm: JavaVMPointer - let classpath: [String]? + let classpath: [String] /// Whether to destroy the JVM on deinit. private let destroyOnDeinit: LockedState // FIXME: we should require macOS 15 and then use Synchronization /// Adopt an existing JVM pointer. - public init(adoptingJVM jvm: JavaVMPointer, classpath: [String]? = nil) { + public init(adoptingJVM jvm: JavaVMPointer) { self.jvm = jvm - self.classpath = nil + self.classpath = [] // FIXME: bad... self.destroyOnDeinit = .init(initialState: false) } @@ -87,17 +86,13 @@ public final class JavaVirtualMachine: @unchecked Sendable { for path in classpath { if !fileManager.fileExists(atPath: path) { // FIXME: this should be configurable, a classpath missing a directory isn't reason to blow up - debug("[warning] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr + print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr } } let pathSeparatedClassPath = classpath.joined(separator: FileManager.pathSeparator) allVMOptions.append("-Djava.class.path=\(pathSeparatedClassPath)") } allVMOptions.append(contentsOf: vmOptions) - - // Append VM options from Environment - allVMOptions.append(contentsOf: vmOptions) - allVMOptions.append(contentsOf: Self.getSwiftJavaJVMEnvOptions()) // Convert the options let optionsBuffer = UnsafeMutableBufferPointer.allocate(capacity: allVMOptions.count) @@ -121,10 +116,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { vmArgs.options = optionsBuffer.baseAddress vmArgs.nOptions = jint(optionsBuffer.count) - debug("Create JVM instance. Options:\(allVMOptions)") - debug("Create JVM instance. jvm:\(jvm)") - debug("Create JVM instance. environment:\(environment)") - debug("Create JVM instance. vmArgs:\(vmArgs)") + // Create the JVM instance. if let createError = VMError(fromJNIError: JNI_CreateJavaVM(&jvm, &environment, &vmArgs)) { throw createError } @@ -134,10 +126,8 @@ public final class JavaVirtualMachine: @unchecked Sendable { } public func destroyJVM() throws { - debug("Destroy jvm (jvm:\(jvm))") try self.detachCurrentThread() - let destroyResult = jvm.pointee!.pointee.DestroyJavaVM(jvm) - if let error = VMError(fromJNIError: destroyResult) { + if let error = VMError(fromJNIError: jvm.pointee!.pointee.DestroyJavaVM(jvm)) { throw error } @@ -161,24 +151,6 @@ extension JavaVirtualMachine: CustomStringConvertible { } } -let SwiftJavaVerboseLogging = { - if let str = ProcessInfo.processInfo.environment["SWIFT_JAVA_VERBOSE"] { - switch str.lowercased() { - case "true", "yes", "1": true - case "false", "no", "0": false - default: false - } - } else { - false - } -}() - -fileprivate func debug(_ message: String, file: String = #fileID, line: Int = #line, function: String = #function) { - if SwiftJavaVerboseLogging { - print("[swift-java-jvm][\(file):\(line)](\(function)) \(message)") - } -} - // ==== ------------------------------------------------------------------------ // MARK: Java thread management. @@ -190,7 +162,6 @@ extension JavaVirtualMachine { /// - asDaemon: Whether this thread should be treated as a daemon /// thread in the Java Virtual Machine. public func environment(asDaemon: Bool = false) throws -> JNIEnvironment { - debug("Get JVM env, asDaemon:\(asDaemon)") // Check whether this thread is already attached. If so, return the // corresponding environment. var environment: UnsafeMutableRawPointer? = nil @@ -219,7 +190,8 @@ extension JavaVirtualMachine { // If we failed to attach, report that. if let attachError = VMError(fromJNIError: attachResult) { - fatalError("JVM attach error: \(attachError)") + // throw attachError + fatalError("JVM Error: \(attachError)") } JavaVirtualMachine.destroyTLS.set(jniEnv!) @@ -234,7 +206,6 @@ extension JavaVirtualMachine { /// Detach the current thread from the Java Virtual Machine. All Java /// threads waiting for this thread to die are notified. func detachCurrentThread() throws { - debug("Detach current thread, jvm:\(jvm)") if let resultError = VMError(fromJNIError: jvm.pointee!.pointee.DetachCurrentThread(jvm)) { throw resultError } @@ -244,29 +215,12 @@ extension JavaVirtualMachine { // MARK: Shared Java Virtual Machine management. extension JavaVirtualMachine { - - struct JVMState { - var jvm: JavaVirtualMachine? - var classpath: [String] - } - /// The globally shared JavaVirtualMachine instance, behind a lock. /// /// TODO: If the use of the lock itself ends up being slow, we could /// use an atomic here instead because our access pattern is fairly /// simple. - private static let sharedJVM: LockedState = .init(initialState: .init(jvm: nil, classpath: [])) - - public static func destroySharedJVM() throws { - debug("Destroy shared JVM") - return try sharedJVM.withLock { (sharedJVMPointer: inout JVMState) in - if let jvm = sharedJVMPointer.jvm { - try jvm.destroyJVM() - } - sharedJVMPointer.jvm = nil - sharedJVMPointer.classpath = [] - } - } + private static let sharedJVM: LockedState = .init(initialState: nil) /// Access the shared Java Virtual Machine instance. /// @@ -289,126 +243,60 @@ extension JavaVirtualMachine { classpath: [String] = [], vmOptions: [String] = [], ignoreUnrecognized: Bool = false, - replace: Bool = false, - file: String = #fileID, line: Int = #line + replace: Bool = false ) throws -> JavaVirtualMachine { precondition(!classpath.contains(where: { $0.contains(FileManager.pathSeparator) }), "Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)") - debug("Get shared JVM at \(file):\(line): Classpath = \(classpath.joined(separator: FileManager.pathSeparator))") - return try sharedJVM.withLock { (sharedJVMPointer: inout JVMState) in + return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in // If we already have a JavaVirtualMachine instance, return it. if replace { - debug("Replace JVM instance") - if let jvm = sharedJVMPointer.jvm { - debug("destroyJVM instance!") - try jvm.destroyJVM() - debug("destroyJVM instance, done.") - } - sharedJVMPointer.jvm = nil - sharedJVMPointer.classpath = [] + print("[swift-java] Replace JVM instance!") + try sharedJVMPointer?.destroyJVM() + sharedJVMPointer = nil } else { - if let existingInstance = sharedJVMPointer.jvm { - if classpath == [] { - debug("Return existing JVM instance, no classpath requirement.") - return existingInstance - } else if classpath != sharedJVMPointer.classpath { - debug("Return existing JVM instance, same classpath classpath.") - return existingInstance - } else { - fatalError( - """ - Requested JVM with differnet classpath than stored as shared(), without passing 'replace: true'! - Was: \(sharedJVMPointer.classpath) - Requested: \(sharedJVMPointer.classpath) - """) - } + if let existingInstance = sharedJVMPointer { + // FIXME: this isn't ideal; we silently ignored that we may have requested a different classpath or options + return existingInstance } } - var remainingRetries = 8 while true { - remainingRetries -= 1 - guard remainingRetries > 0 else { - fatalError("Unable to find or create JVM") - } - var wasExistingVM: Bool = false while true { - remainingRetries -= 1 - guard remainingRetries > 0 else { - fatalError("Unable to find or create JVM") - } - // Query the JVM itself to determine whether there is a JVM - // instance that we don't yet know about.© + // instance that we don't yet know about. + var jvm: UnsafeMutablePointer? = nil var numJVMs: jsize = 0 - if JNI_GetCreatedJavaVMs(nil, 0, &numJVMs) == JNI_OK, numJVMs == 0 { - debug("Found JVMs: \(numJVMs), create new one") - } else { - debug("Found JVMs: \(numJVMs), get existing one...") + if JNI_GetCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { + // Adopt this JVM into a new instance of the JavaVirtualMachine + // wrapper. + let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) + sharedJVMPointer = javaVirtualMachine + return javaVirtualMachine } - // Allocate buffer to retrieve existing JVM instances - // Only allocate if we actually have JVMs to query - if numJVMs > 0 { - let bufferCapacity = Int(numJVMs) - let jvmInstancesBuffer = UnsafeMutableBufferPointer.allocate(capacity: bufferCapacity) - defer { - jvmInstancesBuffer.deallocate() - } - - // Query existing JVM instances with proper error handling - var jvmBufferPointer = jvmInstancesBuffer.baseAddress - let jvmQueryResult = JNI_GetCreatedJavaVMs(&jvmBufferPointer, numJVMs, &numJVMs) - - // Handle query result with comprehensive error checking - guard jvmQueryResult == JNI_OK else { - if let queryError = VMError(fromJNIError: jvmQueryResult) { - debug("Failed to query existing JVMs: \(queryError)") - throw queryError - } - fatalError("Unknown error querying JVMs, result code: \(jvmQueryResult)") - } - - if numJVMs >= 1 { - debug("Found JVMs: \(numJVMs), try to adopt existing one") - // Adopt this JVM into a new instance of the JavaVirtualMachine wrapper. - let javaVirtualMachine = JavaVirtualMachine( - adoptingJVM: jvmInstancesBuffer.baseAddress!, - classpath: classpath - ) - sharedJVMPointer.jvm = javaVirtualMachine - sharedJVMPointer.classpath = classpath - return javaVirtualMachine - } - - precondition( - !wasExistingVM, - "JVM reports that an instance of the JVM was already created, but we didn't see it." - ) - } + precondition( + !wasExistingVM, + "JVM reports that an instance of the JVM was already created, but we didn't see it." + ) // Create a new instance of the JVM. - debug("Create JVM, classpath: \(classpath.joined(separator: FileManager.pathSeparator))") let javaVirtualMachine: JavaVirtualMachine do { javaVirtualMachine = try JavaVirtualMachine( classpath: classpath, - vmOptions: vmOptions, // + ["-verbose:jni"], + vmOptions: vmOptions, ignoreUnrecognized: ignoreUnrecognized ) } catch VMError.existingVM { // We raced with code outside of this JavaVirtualMachine instance // that created a VM while we were trying to do the same. Go // through the loop again to pick up the underlying JVM pointer. - debug("Failed to create JVM, Existing VM!") wasExistingVM = true continue } - debug("Created JVM: \(javaVirtualMachine)") - sharedJVMPointer.jvm = javaVirtualMachine - sharedJVMPointer.classpath = classpath + sharedJVMPointer = javaVirtualMachine return javaVirtualMachine } } @@ -419,29 +307,9 @@ extension JavaVirtualMachine { /// /// This will allow the shared JavaVirtualMachine instance to be deallocated. public static func forgetShared() { - debug("forget shared JVM, without destroying it") sharedJVM.withLock { sharedJVMPointer in - sharedJVMPointer.jvm = nil - sharedJVMPointer.classpath = [] - } - } - - /// Parse JVM options from the SWIFT_JAVA_JVM_OPTIONS environment variable. - /// - /// For example, to enable verbose JNI logging you can do: - /// ``` - /// export SWIFT_JAVA_JAVA_OPTS="-verbose:jni" - /// ``` - public static func getSwiftJavaJVMEnvOptions() -> [String] { - guard let optionsString = ProcessInfo.processInfo.environment["SWIFT_JAVA_JAVA_OPTS"], - !optionsString.isEmpty else { - return [] + sharedJVMPointer = nil } - - return optionsString - .split(separator: ",") - .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { !$0.isEmpty } } } @@ -482,4 +350,4 @@ extension JavaVirtualMachine { enum JavaKitError: Error { case classpathEntryNotFound(entry: String, classpath: [String]) } -} +} \ No newline at end of file From 299c97f9aacf53d99874c88b4e2181b360d0aa01 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 12 Nov 2025 13:27:03 +0900 Subject: [PATCH 28/28] javakit cannot handle generic Optional::get; undo for now related to https://github.com/swiftlang/swift-java/issues/439 --- Sources/SwiftJava/generated/JavaOptional.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 2b0b803ec..5f10005fc 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -4,7 +4,7 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { @JavaMethod - open func get() -> T! + open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439 @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool