diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift new file mode 100644 index 00000000..906f17a4 --- /dev/null +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -0,0 +1,555 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaKit +import JavaKitReflection +import SwiftSyntax + +/// Utility type that translates a single Java class into its corresponding +/// Swift type and any additional helper types or functions. +struct JavaClassTranslator { + /// The translator we are working with, which provides global knowledge + /// needed for translation. + let translator: JavaTranslator + + /// The Java class (or interface) being translated. + let javaClass: JavaClass + + /// The type parameters to the Java class or interface. + let javaTypeParameters: [TypeVariable>] + + /// The set of nested classes of this class that will be rendered along + /// with it. + let nestedClasses: [JavaClass] + + /// The full name of the Swift type that will be generated for this Java + /// class. + let swiftTypeName: String + + /// The Swift name of the superclass. + let swiftSuperclass: String? + + /// The Swift names of the interfaces that this class implements. + let swiftInterfaces: [String] + + /// The (instance) fields of the Java class. + var fields: [Field] = [] + + /// The static fields of the Java class. + var staticFields: [Field] = [] + + /// Enum constants of the Java class, which are also static fields and are + /// reflected additionally as enum cases. + var enumConstants: [Field] = [] + + /// Constructors of the Java class. + var constructors: [Constructor] = [] + + /// The (instance) methods of the Java class. + var methods: [Method] = [] + + /// The static methods of the Java class. + var staticMethods: [Method] = [] + + /// The native instance methods of the Java class, which are also reflected + /// in a `*NativeMethods` protocol so they can be implemented in Swift. + var nativeMethods: [Method] = [] + + /// The native static methods of the Java class. + /// TODO: These are currently unimplemented. + var nativeStaticMethods: [Method] = [] + + /// Whether the Java class we're translating is actually an interface. + var isInterface: Bool { + return javaClass.isInterface() + } + + /// The name of the enclosing Swift type, if there is one. + var swiftParentType: String? { + swiftTypeName.splitSwiftTypeName().parentType + } + + /// The name of the innermost Swift type, without the enclosing type. + var swiftInnermostTypeName: String { + swiftTypeName.splitSwiftTypeName().name + } + + /// The generic parameter clause for the Swift version of the Java class. + var genericParameterClause: String { + if javaTypeParameters.isEmpty { + return "" + } + + let genericParameters = javaTypeParameters.map { param in + "\(param.getName()): AnyJavaObject" + } + + return "<\(genericParameters.joined(separator: ", "))>" + } + + /// Prepare translation for the given Java class (or interface). + init(javaClass: JavaClass, translator: JavaTranslator) throws { + let fullName = javaClass.getName() + self.javaClass = javaClass + self.translator = translator + self.swiftTypeName = try translator.getSwiftTypeNameFromJavaClassName(fullName, escapeMemberNames: false) + + // Type parameters. + self.javaTypeParameters = javaClass.getTypeParameters().compactMap { $0 } + self.nestedClasses = translator.nestedClasses[fullName] ?? [] + + // Superclass. + if !javaClass.isInterface(), + let javaSuperclass = javaClass.getSuperclass(), + javaSuperclass.getName() != "java.lang.Object" + { + do { + self.swiftSuperclass = try translator.getSwiftTypeName(javaSuperclass).swiftName + } catch { + translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") + self.swiftSuperclass = nil + } + } else { + self.swiftSuperclass = nil + } + + // Interfaces. + self.swiftInterfaces = javaClass.getGenericInterfaces().compactMap { (javaType) -> String? in + guard let javaType else { + return nil + } + + do { + let typeName = try translator.getSwiftTypeNameAsString(javaType, outerOptional: false) + return "\(typeName)" + } catch { + translator.logUntranslated("Unable to translate '\(fullName)' interface '\(javaType.getTypeName())': \(error)") + return nil + } + } + + // Collect all of the class members that we will need to translate. + // TODO: Switch over to "declared" versions of these whenever we don't need + // to see inherited members. + + // Gather fields. + for field in javaClass.getFields() { + guard let field else { continue } + addField(field) + } + + // Gather constructors. + for constructor in javaClass.getConstructors() { + guard let constructor else { continue } + addConstructor(constructor) + } + + // Gather methods. + for method in javaClass.getMethods() { + guard let 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 + // we want to see non-public methods as well. + let implementedInSwift = method.isNative && + method.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) && + translator.swiftNativeImplementations.contains(javaClass.getName()) + if implementedInSwift { + continue + } + + addMethod(method, isNative: false) + } + + if translator.swiftNativeImplementations.contains(javaClass.getName()) { + for method in javaClass.getDeclaredMethods() { + guard let method else { continue } + + // Only visit native methods in this second pass. + if !method.isNative { + continue + } + + addMethod(method, isNative: true) + } + } + } +} + +/// MARK: Collection of Java class members. +extension JavaClassTranslator { + /// Add a field to the appropriate lists(s) for later translation. + private mutating func addField(_ field: Field) { + // Static fields go into a separate list. + if field.isStatic { + staticFields.append(field) + + // Enum constants will be used to produce a Swift enum projecting the + // Java enum. + if field.isEnumConstant() { + enumConstants.append(field) + } + + return + } + + fields.append(field) + } + + /// Add a constructor to the list of constructors for later translation. + private mutating func addConstructor(_ constructor: Constructor) { + constructors.append(constructor) + } + + /// Add a method to the appropriate list for later translation. + private mutating func addMethod(_ method: Method, isNative: Bool) { + switch (method.isStatic, isNative) { + case (false, false): methods.append(method) + case (true, false): staticMethods.append(method) + case (false, true): nativeMethods.append(method) + case (true, true): nativeStaticMethods.append(method) + } + } +} + +/// MARK: Rendering of Java class members as Swift declarations. +extension JavaClassTranslator { + /// Render the Swift declarations that will express this Java class in Swift. + package func render() -> [DeclSyntax] { + var allDecls: [DeclSyntax] = [] + allDecls.append(renderPrimaryType()) + allDecls.append(contentsOf: renderNestedClasses()) + if let staticMemberExtension = renderStaticMemberExtension() { + allDecls.append(staticMemberExtension) + } + if let nativeMethodsProtocol = renderNativeMethodsProtocol() { + allDecls.append(nativeMethodsProtocol) + } + return allDecls + } + + /// Render the declaration for the main part of the Java class, which + /// includes the constructors, non-static fields, and non-static methods. + private func renderPrimaryType() -> DeclSyntax { + // Render all of the instance fields as Swift properties. + let properties = fields.compactMap { field in + do { + return try renderField(field) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' static field '\(field.getName())': \(error)") + return nil + } + } + + // Declarations used to capture Java enums. + let enumDecls: [DeclSyntax] = renderEnum(name: "\(swiftInnermostTypeName)Cases") + + // Render all of the constructors as Swift initializers. + let initializers = constructors.compactMap { constructor in + do { + return try renderConstructor(constructor) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' constructor: \(error)") + return nil + } + } + + // Render all of the instance methods in Swift. + let instanceMethods = methods.compactMap { method in + do { + return try renderMethod(method, implementedInSwift: false) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' method '\(method.getName())': \(error)") + return nil + } + } + + // Collect all of the members of this type. + let members = properties + enumDecls + initializers + instanceMethods + + // Compute the "extends" clause for the superclass. + let extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + + // Compute the string to capture all of the interfaces. + let interfacesStr: String + if swiftInterfaces.isEmpty { + interfacesStr = "" + } else { + let prefix = javaClass.isInterface() ? "extends" : "implements" + interfacesStr = ", \(prefix): \(swiftInterfaces.map { "\($0).self" }.joined(separator: ", "))" + } + + // Emit the struct declaration describing the java class. + let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; + var classDecl: DeclSyntax = + """ + @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) + public struct \(raw: swiftInnermostTypeName)\(raw: genericParameterClause) { + \(raw: members.map { $0.description }.joined(separator: "\n\n")) + } + """ + + // If there is a parent type, wrap this type up in an extension of that + // parent type. + if let swiftParentType { + classDecl = + """ + extension \(raw: swiftParentType) { + \(classDecl) + } + """ + } + + // Format the class declaration. + return classDecl.formatted(using: translator.format).cast(DeclSyntax.self) + } + + /// Render any nested classes that will not be rendered separately. + func renderNestedClasses() -> [DeclSyntax] { + return nestedClasses.compactMap { clazz in + do { + return try translator.translateClass(clazz) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' nested class '\(clazz.getName())': \(error)") + return nil + } + }.flatMap(\.self) + } + + /// Render the extension of JavaClass that collects all of the static + /// fields and methods. + package func renderStaticMemberExtension() -> DeclSyntax? { + // Determine the where clause we need for static methods. + let staticMemberWhereClause: String + if !javaTypeParameters.isEmpty { + let genericParameterNames = javaTypeParameters.compactMap { typeVar in + typeVar.getName() + } + + let genericArgumentClause = "<\(genericParameterNames.joined(separator: ", "))>" + staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" + } else { + staticMemberWhereClause = "" + } + + // Render static fields. + let properties = staticFields.compactMap { field in + // Translate each static field. + do { + return try renderField(field) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' field '\(field.getName())': \(error)") + return nil + } + } + + // Render static methods. + let methods = staticMethods.compactMap { method in + // Translate each static method. + do { + return try renderMethod( + method, implementedInSwift: /*FIXME:*/false, + genericParameterClause: genericParameterClause, + whereClause: staticMemberWhereClause + ) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' static method '\(method.getName())': \(error)") + return nil + } + } + + // Gather all of the members. + let members = properties + methods + if members.isEmpty { + return nil + } + + // Specify the specialization arguments when needed. + let extSpecialization: String + if genericParameterClause.isEmpty { + extSpecialization = "<\(swiftTypeName)>" + } else { + extSpecialization = "" + } + + let extDecl: DeclSyntax = + """ + extension JavaClass\(raw: extSpecialization) { + \(raw: members.map { $0.description }.joined(separator: "\n\n")) + } + """ + + return extDecl.formatted(using: translator.format).cast(DeclSyntax.self) + } + + /// Render the protocol used for native methods. + func renderNativeMethodsProtocol() -> DeclSyntax? { + guard translator.swiftNativeImplementations.contains(javaClass.getName()) else { + return nil + } + + let nativeMembers = nativeMethods.compactMap { method in + do { + return try renderMethod( + method, + implementedInSwift: true + ) + } catch { + translator.logUntranslated("Unable to translate '\(javaClass.getName())' method '\(method.getName())': \(error)") + return nil + } + } + + if nativeMembers.isEmpty { + return nil + } + + let protocolDecl: DeclSyntax = + """ + /// Describes the Java `native` methods for ``\(raw: swiftTypeName)``. + /// + /// To implement all of the `native` methods for \(raw: swiftTypeName) in Swift, + /// extend \(raw: swiftTypeName) to conform to this protocol and mark + /// each implementation of the protocol requirement with + /// `@JavaMethod`. + protocol \(raw: swiftTypeName)NativeMethods { + \(raw: nativeMembers.map { $0.description }.joined(separator: "\n\n")) + } + """ + + return protocolDecl.formatted(using: translator.format).cast(DeclSyntax.self) + } + + /// Render the given Java constructor as a Swift initializer. + package func renderConstructor( + _ javaConstructor: Constructor + ) throws -> DeclSyntax { + let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] + let parametersStr = parameters.map { $0.description }.joined(separator: ", ") + let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" + let accessModifier = javaConstructor.isPublic ? "public " : "" + return """ + @JavaMethod + \(raw: accessModifier)init(\(raw: parametersStr))\(raw: throwsStr) + """ + } + + /// Translates the given Java method into a Swift declaration. + package func renderMethod( + _ javaMethod: Method, + implementedInSwift: Bool, + genericParameterClause: String = "", + whereClause: String = "" + ) throws -> DeclSyntax { + // Map the parameters. + let parameters = try translateParameters(javaMethod.getParameters()) + + let parametersStr = parameters.map { $0.description }.joined(separator: ", ") + + // Map the result type. + let resultTypeStr: String + let resultType = try translator.getSwiftTypeNameAsString(javaMethod.getGenericReturnType()!, outerOptional: true) + if resultType != "Void" { + resultTypeStr = " -> \(resultType)" + } else { + resultTypeStr = "" + } + + let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" + let swiftMethodName = javaMethod.getName().escapedSwiftName + let methodAttribute: AttributeSyntax = implementedInSwift + ? "" + : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; + let accessModifier = implementedInSwift ? "" : "public " + return """ + \(methodAttribute)\(raw: accessModifier)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + """ + } + + /// Render a single Java field into the corresponding Swift property, or + /// throw an error if that is not possible for any reason. + package func renderField(_ javaField: Field) throws -> DeclSyntax { + let typeName = try translator.getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: true) + let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField"; + let swiftFieldName = javaField.getName().escapedSwiftName + return """ + \(fieldAttribute)(isFinal: \(raw: javaField.isFinal)) + public var \(raw: swiftFieldName): \(raw: typeName) + """ + } + + package func renderEnum(name: String) -> [DeclSyntax] { + if enumConstants.isEmpty { + return [] + } + + let extensionSyntax: DeclSyntax = """ + public enum \(raw: name): Equatable { + \(raw: enumConstants.map { "case \($0.getName())" }.joined(separator: "\n")) + } + """ + + let mappingSyntax: DeclSyntax = """ + public var enumValue: \(raw: name)? { + let classObj = self.javaClass + \(raw: enumConstants.map { + // The equals method takes a java object, so we need to cast it here + """ + if self.equals(classObj.\($0.getName())?.as(JavaObject.self)) { + return \(name).\($0.getName()) + } + """ + }.joined(separator: " else ")) else { + return nil + } + } + """ + + let initSyntax: DeclSyntax = """ + public init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) { + let _environment = if let environment { + environment + } else { + try! JavaVirtualMachine.shared().environment() + } + let classObj = try! JavaClass(environment: _environment) + switch enumValue { + \(raw: enumConstants.map { + return """ + case .\($0.getName()): + if let \($0.getName()) = classObj.\($0.getName()) { + self = \($0.getName()) + } else { + fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + } + """ + }.joined(separator: "\n")) + } + } + """ + + return [extensionSyntax, mappingSyntax, initSyntax] + } + + // Translate a Java parameter list into Swift parameters. + private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + return try parameters.compactMap { javaParameter in + guard let javaParameter else { return nil } + + let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: true) + let paramName = javaParameter.getName() + return "_ \(raw: paramName): \(raw: typeName)" + } + } +} diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index 9a9a5b4d..89e417e6 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -178,7 +178,7 @@ extension JavaTranslator { } /// Map a Java class name to its corresponding Swift type. - private func getSwiftTypeNameFromJavaClassName( + func getSwiftTypeNameFromJavaClassName( _ name: String, escapeMemberNames: Bool = true ) throws -> String { @@ -205,444 +205,7 @@ extension JavaTranslator { /// can produce multiple declarations, such as a separate extension of /// JavaClass to house static methods. package func translateClass(_ javaClass: JavaClass) throws -> [DeclSyntax] { - let fullName = javaClass.getName() - let swiftTypeName = try getSwiftTypeNameFromJavaClassName(fullName, escapeMemberNames: false) - let (swiftParentType, swiftInnermostTypeName) = swiftTypeName.splitSwiftTypeName() - - // If the swift parent type has not been translated, don't try to translate this one - if let swiftParentType, - !translatedClasses.contains(where: { _, value in value.swiftType == swiftParentType }) - { - logUntranslated("Unable to translate '\(fullName)' parent class: \(swiftParentType) not found") - return [] - } - - // Superclass. - let extends: String - if !javaClass.isInterface(), - let superclass = javaClass.getSuperclass(), - superclass.getName() != "java.lang.Object" - { - do { - extends = ", extends: \(try getSwiftTypeName(superclass).swiftName).self" - } catch { - logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") - extends = "" - } - } else { - extends = "" - } - - // The set of generic interfaces implemented by a class or - // extended by an interface. - let interfaces: [String] = javaClass.getGenericInterfaces().compactMap { javaType in - guard let javaType else { - return nil - } - - do { - let typeName = try getSwiftTypeNameAsString(javaType, outerOptional: false) - return "\(typeName).self" - } catch { - logUntranslated("Unable to translate '\(fullName)' interface '\(javaType.getTypeName())': \(error)") - return nil - } - } - let interfacesStr: String - if interfaces.isEmpty { - interfacesStr = "" - } else { - let prefix = javaClass.isInterface() ? "extends" : "implements" - interfacesStr = ", \(prefix): \(interfaces.joined(separator: ", "))" - } - - // The top-level declarations we will be returning. - var topLevelDecls: [DeclSyntax] = [] - - // Members - var members: [DeclSyntax] = [] - - // Fields - var staticFields: [Field] = [] - var enumConstants: [Field] = [] - members.append( - contentsOf: javaClass.getFields().compactMap { - $0.flatMap { field in - if field.isStatic { - staticFields.append(field) - - if field.isEnumConstant() { - enumConstants.append(field) - } - return nil - } - - do { - return try translateField(field) - } catch { - logUntranslated("Unable to translate '\(fullName)' static field '\(field.getName())': \(error)") - return nil - } - } - } - ) - - if !enumConstants.isEmpty { - let enumName = "\(swiftInnermostTypeName)Cases" - members.append( - contentsOf: translateToEnumValue(name: enumName, enumFields: enumConstants) - ) - } - - // Constructors - members.append( - contentsOf: javaClass.getConstructors().compactMap { - $0.flatMap { constructor in - do { - let implementedInSwift = constructor.isNative && - constructor.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) && - swiftNativeImplementations.contains(javaClass.getName()) - - let translated = try translateConstructor( - constructor, - implementedInSwift: implementedInSwift - ) - - if implementedInSwift { - return nil - } - - return translated - } catch { - logUntranslated("Unable to translate '\(fullName)' constructor: \(error)") - return nil - } - } - } - ) - - // Methods - var staticMethods: [Method] = [] - members.append( - contentsOf: javaClass.getMethods().compactMap { - $0.flatMap { (method) -> DeclSyntax? in - // Save the static methods; they need to go on an extension of - // JavaClass. - if method.isStatic { - staticMethods.append(method) - return nil - } - - let implementedInSwift = method.isNative && - method.getDeclaringClass()!.equals(javaClass.as(JavaObject.self)!) && - swiftNativeImplementations.contains(javaClass.getName()) - - // Translate the method if we can. - do { - let translated = try translateMethod( - method, - implementedInSwift: implementedInSwift - ) - - if implementedInSwift { - return nil - } - - return translated - } catch { - logUntranslated("Unable to translate '\(fullName)' method '\(method.getName())': \(error)") - return nil - } - } - } - ) - - // Map the generic parameters. - let genericParameterClause: String - let staticMemberWhereClause: String - let javaTypeParameters = javaClass.getTypeParameters() - if !javaTypeParameters.isEmpty { - let genericParameterNames = javaTypeParameters.compactMap { typeVar in - typeVar?.getName() - } - - let genericParameters = genericParameterNames.map { name in - "\(name): AnyJavaObject" - } - - genericParameterClause = "<\(genericParameters.joined(separator: ", "))>" - let genericArgumentClause = "<\(genericParameterNames.joined(separator: ", "))>" - - staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" - } else { - genericParameterClause = "" - staticMemberWhereClause = "" - } - - // Emit the struct declaration describing the java class. - let classOrInterface: String = javaClass.isInterface() ? "JavaInterface" : "JavaClass"; - var classDecl = - """ - @\(raw:classOrInterface)(\(literal: fullName)\(raw: extends)\(raw: interfacesStr)) - public struct \(raw: swiftInnermostTypeName)\(raw: genericParameterClause) { - \(raw: members.map { $0.description }.joined(separator: "\n\n")) - } - """ as DeclSyntax - - // If there is a parent type, wrap this type up in an extension of that - // parent type. - if let swiftParentType { - classDecl = - """ - extension \(raw: swiftParentType) { - \(classDecl) - } - """ - } - - // Format the class declaration. - classDecl = classDecl.formatted(using: format).cast(DeclSyntax.self) - - topLevelDecls.append(classDecl) - - let subClassDecls = (nestedClasses[fullName] ?? []).compactMap { clazz in - do { - return try translateClass(clazz) - } catch { - logUntranslated("Unable to translate '\(fullName)' subclass '\(clazz.getName())': \(error)") - return nil - } - }.flatMap(\.self) - - topLevelDecls.append( - contentsOf: subClassDecls - ) - // Translate static members. - var staticMembers: [DeclSyntax] = [] - - staticMembers.append( - contentsOf: staticFields.compactMap { field in - // Translate each static field. - do { - return try translateField(field) - } catch { - logUntranslated("Unable to translate '\(fullName)' field '\(field.getName())': \(error)") - return nil - } - } - ) - - staticMembers.append( - contentsOf: staticMethods.compactMap { method in - // Translate each static method. - do { - return try translateMethod( - method, implementedInSwift: /*FIXME:*/false, - genericParameterClause: genericParameterClause, - whereClause: staticMemberWhereClause - ) - } catch { - logUntranslated("Unable to translate '\(fullName)' static method '\(method.getName())': \(error)") - return nil - } - } - ) - - if !staticMembers.isEmpty { - // Specify the specialization arguments when needed. - let extSpecialization: String - if genericParameterClause.isEmpty { - extSpecialization = "<\(swiftTypeName)>" - } else { - extSpecialization = "" - } - - let extDecl: DeclSyntax = - """ - extension JavaClass\(raw: extSpecialization) { - \(raw: staticMembers.map { $0.description }.joined(separator: "\n\n")) - } - """ - - topLevelDecls.append( - extDecl.formatted(using: format).cast(DeclSyntax.self) - ) - } - - // Members that are native and will instead go into a NativeMethods - // protocol. - var nativeMembers: [DeclSyntax] = [] - if swiftNativeImplementations.contains(javaClass.getName()) { - nativeMembers.append( - contentsOf: javaClass.getDeclaredMethods().compactMap { - $0.flatMap { method in - // FIXME: For now, ignore static methods - if method.isStatic { - return nil - } - - if !method.isNative { - return nil - } - - // Translate the method if we can. - do { - return try translateMethod( - method, - implementedInSwift: true - ) - } catch { - logUntranslated("Unable to translate '\(fullName)' method '\(method.getName())': \(error)") - return nil - } - } - } - ) - } - - if !nativeMembers.isEmpty { - let protocolDecl: DeclSyntax = - """ - /// Describes the Java `native` methods for ``\(raw: swiftTypeName)``. - /// - /// To implement all of the `native` methods for \(raw: swiftTypeName) in Swift, - /// extend \(raw: swiftTypeName) to conform to this protocol and mark - /// each implementation of the protocol requirement with - /// `@JavaMethod`. - protocol \(raw: swiftTypeName)NativeMethods { - \(raw: nativeMembers.map { $0.description }.joined(separator: "\n\n")) - } - """ - - topLevelDecls.append( - protocolDecl.formatted(using: format).cast(DeclSyntax.self) - ) - } - - return topLevelDecls - } -} - -// MARK: Method and constructor translation -extension JavaTranslator { - /// Translates the given Java constructor into a Swift declaration. - package func translateConstructor( - _ javaConstructor: Constructor, - implementedInSwift: Bool - ) throws -> DeclSyntax { - let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] - let parametersStr = parameters.map { $0.description }.joined(separator: ", ") - let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" - - let javaMethodAttribute = implementedInSwift - ? "" - : "@JavaMethod\n" - let accessModifier = implementedInSwift ? "" : "public " - return """ - \(raw: javaMethodAttribute)\(raw: accessModifier)init(\(raw: parametersStr))\(raw: throwsStr) - """ - } - - /// Translates the given Java method into a Swift declaration. - package func translateMethod( - _ javaMethod: Method, - implementedInSwift: Bool, - genericParameterClause: String = "", - whereClause: String = "" - ) throws -> DeclSyntax { - // Map the parameters. - let parameters = try translateParameters(javaMethod.getParameters()) - - let parametersStr = parameters.map { $0.description }.joined(separator: ", ") - - // Map the result type. - let resultTypeStr: String - let resultType = try getSwiftTypeNameAsString(javaMethod.getGenericReturnType()!, outerOptional: true) - if resultType != "Void" { - resultTypeStr = " -> \(resultType)" - } else { - resultTypeStr = "" - } - - let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" - let swiftMethodName = javaMethod.getName().escapedSwiftName - let methodAttribute: AttributeSyntax = implementedInSwift - ? "" - : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; - let accessModifier = implementedInSwift ? "" : "public " - return """ - \(methodAttribute)\(raw: accessModifier)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) - """ - } - - package func translateField(_ javaField: Field) throws -> DeclSyntax { - let typeName = try getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: true) - let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField"; - let swiftFieldName = javaField.getName().escapedSwiftName - return """ - \(fieldAttribute)(isFinal: \(raw: javaField.isFinal)) - public var \(raw: swiftFieldName): \(raw: typeName) - """ - } - - package func translateToEnumValue(name: String, enumFields: [Field]) -> [DeclSyntax] { - let extensionSyntax: DeclSyntax = """ - public enum \(raw: name): Equatable { - \(raw: enumFields.map { "case \($0.getName())" }.joined(separator: "\n")) - } - """ - - let mappingSyntax: DeclSyntax = """ - public var enumValue: \(raw: name)? { - let classObj = self.javaClass - \(raw: enumFields.map { - // The equals method takes a java object, so we need to cast it here - """ - if self.equals(classObj.\($0.getName())?.as(JavaObject.self)) { - return \(name).\($0.getName()) - } - """ - }.joined(separator: " else ")) else { - return nil - } - } - """ - - let initSyntax: DeclSyntax = """ - public init(_ enumValue: \(raw: name), environment: JNIEnvironment? = nil) { - let _environment = if let environment { - environment - } else { - try! JavaVirtualMachine.shared().environment() - } - let classObj = try! JavaClass(environment: _environment) - switch enumValue { - \(raw: enumFields.map { - return """ - case .\($0.getName()): - if let \($0.getName()) = classObj.\($0.getName()) { - self = \($0.getName()) - } else { - fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") - } - """ - }.joined(separator: "\n")) - } - } - """ - - return [extensionSyntax, mappingSyntax, initSyntax] - } - - // Translate a Java parameter list into Swift parameters. - private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { - return try parameters.compactMap { javaParameter in - guard let javaParameter else { return nil } - - let typeName = try getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: true) - let paramName = javaParameter.getName() - return "_ \(raw: paramName): \(raw: typeName)" - } + return try JavaClassTranslator(javaClass: javaClass, translator: self).render() } } diff --git a/Sources/JavaKitReflection/Constructor+Utilities.swift b/Sources/JavaKitReflection/Constructor+Utilities.swift index 0eed3edd..676a6283 100644 --- a/Sources/JavaKitReflection/Constructor+Utilities.swift +++ b/Sources/JavaKitReflection/Constructor+Utilities.swift @@ -13,7 +13,12 @@ //===----------------------------------------------------------------------===// extension Constructor { - /// Whether this is a 'native' method. + /// Whether this is a 'public' constructor. + public var isPublic: Bool { + return (getModifiers() & 1) != 0 + } + + /// Whether this is a 'native' constructor. public var isNative: Bool { return (getModifiers() & 256) != 0 } diff --git a/Sources/JavaKitReflection/Method+Utilities.swift b/Sources/JavaKitReflection/Method+Utilities.swift index 28556c0f..1b632337 100644 --- a/Sources/JavaKitReflection/Method+Utilities.swift +++ b/Sources/JavaKitReflection/Method+Utilities.swift @@ -13,6 +13,11 @@ //===----------------------------------------------------------------------===// extension Method { + /// Whether this is a 'public' method. + public var isPublic: Bool { + return (getModifiers() & 1) != 0 + } + /// Whether this is a 'static' method. public var isStatic: Bool { return (getModifiers() & 0x08) != 0