forked from swiftlang/swift-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJavaTranslator.swift
More file actions
366 lines (313 loc) · 11.9 KB
/
JavaTranslator.swift
File metadata and controls
366 lines (313 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
//===----------------------------------------------------------------------===//
//
// 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
import JavaLangReflect
import Logging
import SwiftBasicFormat
import SwiftJava
import SwiftJavaConfigurationShared
import SwiftJavaJNICore
import SwiftSyntax
import SwiftSyntaxBuilder
/// 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
let environment: JNIEnvironment
/// Whether to translate Java classes into classes (rather than structs).
let translateAsClass: Bool
let format: BasicFormat
/// 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"),
"byte[]": SwiftTypeName(module: nil, name: "[UInt8]"),
]
/// A mapping from the name of each known Java class with the Swift value type
/// (and its module) to which it is mapped.
///
/// The Java classes here can also be part of `translatedClasses`. The entry in
/// `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: [JavaFullyQualifiedTypeName: SwiftTypeName] = [
"java.lang.String": SwiftTypeName(module: "SwiftJava", name: "String")
]
/// The set of Swift modules that need to be imported to make the generated
/// code compile. Use `getImportDecls()` to format this into a list of
/// import declarations.
package var importedSwiftModules: Set<String> = JavaTranslator.defaultImportedSwiftModules
/// The canonical names of Java classes whose declared 'native'
/// methods will be implemented in Swift.
package var swiftNativeImplementations: Set<String> = []
/// Parsed Android `api-versions.xml` data, if available.
/// When set, the translator will emit `@available(Android ...)` attributes
/// based on API-level introduction, deprecation, and removal data.
package var androidAPIVersions: AndroidAPIVersions?
/// The set of nested classes that we should traverse from the given class,
/// indexed by the name of the class.
///
/// TODO: Make JavaClass Hashable so we can index by the object?
package var nestedClasses: [String: [JavaClass<JavaObject>]] = [:]
package init(
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
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.
package func startNewFile() {
importedSwiftModules = Self.defaultImportedSwiftModules
}
/// Simplistic logging for all entities that couldn't be translated.
func logUntranslated(_ message: String) {
print("[warning] \(message)")
}
}
// MARK: Defaults
extension JavaTranslator {
/// Default formatting options.
private static let defaultFormat = BasicFormat(indentationWidth: .spaces(2))
/// Default set of modules that will always be imported.
private static let defaultImportedSwiftModules: Set<String> = [
"SwiftJava",
"SwiftJavaJNICore",
]
}
// MARK: Import translation
extension JavaTranslator {
/// Retrieve the import declarations.
package func getImportDecls() -> [DeclSyntax] {
importedSwiftModules.filter {
$0 != swiftModuleName
}.sorted().map {
"import \(raw: $0)\n"
}
}
}
// MARK: Type translation
extension JavaTranslator {
func getSwiftReturnTypeNameAsString(
method: JavaLangReflect.Method,
substitution: SubstitutionMap?,
preferValueTypes: Bool,
outerOptional: OptionalKind
) throws -> String {
let genericReturnType = method.getGenericReturnType()
// Special handle the case when the return type is the generic type of the method: `<T> T foo()`
return try getSwiftTypeNameAsString(
method: method,
genericReturnType!,
substitution: substitution,
preferValueTypes: preferValueTypes,
outerOptional: outerOptional
)
}
/// Turn a Java type into a string.
func getSwiftTypeNameAsString(
method: JavaLangReflect.Method? = nil,
_ javaType: Type,
substitution: SubstitutionMap?,
preferValueTypes: Bool,
outerOptional: OptionalKind,
eraseTypeArguments: Bool = false
) throws -> String {
// Replace if it is a type variable and we have a substitution for it.
let javaType = substitution?.resolve(javaType) ?? javaType
// Replace type variables with their bounds.
if let typeVariable = javaType.as(TypeVariable<GenericDeclaration>.self),
typeVariable.getBounds().count == 1,
typeVariable.getBounds()[0] != nil
{
return outerOptional.adjustTypeName(typeVariable.getName())
}
// Replace wildcards with their upper bound.
if let wildcardType = javaType.as(WildcardType.self),
wildcardType.getUpperBounds().count == 1,
let bound = wildcardType.getUpperBounds()[0]
{
// Replace a wildcard type with its first bound.
return try getSwiftTypeNameAsString(
bound,
substitution: substitution,
preferValueTypes: preferValueTypes,
outerOptional: outerOptional
)
}
// Handle array types by recursing into the component type.
if let arrayType = javaType.as(GenericArrayType.self) {
if preferValueTypes {
let elementType = try getSwiftTypeNameAsString(
arrayType.getGenericComponentType()!,
substitution: substitution,
preferValueTypes: preferValueTypes,
outerOptional: .optional
)
return "[\(elementType)]"
}
let (swiftName, _) = try getSwiftTypeName(
JavaClass<JavaArray>().as(JavaClass<JavaObject>.self)!,
preferValueTypes: false
)
return outerOptional.adjustTypeName(swiftName)
}
// Handle parameterized types by recursing on the raw type and the type
// arguments.
if let parameterizedType = javaType.as(ParameterizedType.self) {
if let rawJavaType = parameterizedType.getRawType() {
var rawSwiftType = try getSwiftTypeNameAsString(
rawJavaType,
substitution: substitution,
preferValueTypes: false,
outerOptional: outerOptional
)
let optionalSuffix: String
if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" {
optionalSuffix = "\(lastChar)"
rawSwiftType.removeLast()
} else {
optionalSuffix = ""
}
let typeArguments: [String] = try parameterizedType.getActualTypeArguments().compactMap { typeArg in
guard let typeArg else { return nil }
if eraseTypeArguments {
return "JavaObject"
}
let mappedSwiftName = try getSwiftTypeNameAsString(
method: method,
typeArg,
substitution: substitution,
preferValueTypes: false,
outerOptional: .nonoptional
)
// FIXME: improve the get instead...
if mappedSwiftName == "JavaObject" {
// Try to salvage it, is it perhaps a type parameter?
if let method {
let typeParameters = method.getTypeParameters() as [TypeVariable<JavaLangReflect.Method>?]
if typeParameters.contains(where: { $0?.getTypeName() == typeArg.getTypeName() }) {
return typeArg.getTypeName()
}
}
}
return mappedSwiftName
}
return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)"
}
}
// Handle direct references to Java classes.
guard let javaClass = javaType.as(JavaClass<JavaObject>.self) else {
throw TranslationError.unhandledJavaType(javaType)
}
let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes)
let resultString =
if isOptional {
outerOptional.adjustTypeName(swiftName)
} else {
swiftName
}
return resultString
}
/// Translate a Java class into its corresponding Swift type name.
package func getSwiftTypeName(
_ javaClass: JavaClass<JavaObject>,
preferValueTypes: Bool
) throws -> (swiftName: String, isOptional: Bool) {
let javaType = try JavaType(javaTypeName: javaClass.getName())
let isSwiftOptional = javaType.isSwiftOptional(stringIsValueType: preferValueTypes)
let swiftTypeName: String
if !preferValueTypes, case .array(_) = javaType {
swiftTypeName = try self.getSwiftTypeNameFromJavaClassName("java.lang.reflect.Array", preferValueTypes: false)
} else {
swiftTypeName = try javaType.swiftTypeName { javaClassName in
try self.getSwiftTypeNameFromJavaClassName(javaClassName, preferValueTypes: preferValueTypes)
}
}
return (swiftTypeName, isSwiftOptional)
}
/// Map a Java class name to its corresponding Swift type.
func getSwiftTypeNameFromJavaClassName(
_ name: String,
preferValueTypes: Bool,
escapeMemberNames: Bool = true
) throws -> String {
// If we want a value type, look for one.
if preferValueTypes, let translatedValueType = translatedToValueTypes[name] {
// Note that we need to import this Swift module.
if translatedValueType.swiftModule != swiftModuleName {
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
}
if let translated = translatedClasses[name] {
// Note that we need to import this Swift module.
if let swiftModule = translated.swiftModule, swiftModule != swiftModuleName {
importedSwiftModules.insert(swiftModule)
}
if escapeMemberNames {
return translated.swiftType.escapingSwiftMemberNames
}
return translated.swiftType
}
throw TranslationError.untranslatedJavaClass(name)
}
}
// MARK: Class translation
extension JavaTranslator {
/// Translates the given Java class into the corresponding Swift type. This
/// can produce multiple declarations, such as a separate extension of
/// JavaClass to house static methods.
package func translateClass(_ javaClass: JavaClass<JavaObject>) throws -> [DeclSyntax] {
try JavaClassTranslator(javaClass: javaClass, translator: self).render()
}
}
extension String {
/// Escape Swift types that involve member name references like '.Type'
fileprivate var escapingSwiftMemberNames: String {
var count = 0
return split(separator: ".").map { component in
defer {
count += 1
}
if count > 0 && component.memberRequiresBackticks {
return "`\(component)`"
}
return String(component)
}.joined(separator: ".")
}
}
extension Substring {
fileprivate var memberRequiresBackticks: Bool {
switch self {
case "Type": return true
default: return false
}
}
}