1 /*
<lambda>null2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.stub
18 
19 import com.android.tools.metalava.actualItem
20 import com.android.tools.metalava.model.BaseItemVisitor
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.ConstructorItem
23 import com.android.tools.metalava.model.ExceptionTypeItem
24 import com.android.tools.metalava.model.FieldItem
25 import com.android.tools.metalava.model.Item
26 import com.android.tools.metalava.model.MethodItem
27 import com.android.tools.metalava.model.ModifierListWriter
28 import com.android.tools.metalava.model.PrimitiveTypeItem
29 import com.android.tools.metalava.model.TypeParameterList
30 import com.android.tools.metalava.model.VariableTypeItem
31 import java.io.PrintWriter
32 import java.util.function.Predicate
33 
34 internal class JavaStubWriter(
35     private val writer: PrintWriter,
36     private val modifierListWriter: ModifierListWriter,
37     private val filterEmit: Predicate<Item>,
38     private val filterReference: Predicate<Item>,
39     private val preFiltered: Boolean = true,
40     private val config: StubWriterConfig,
41 ) : BaseItemVisitor() {
42 
43     override fun visitClass(cls: ClassItem) {
44         if (cls.isTopLevelClass()) {
45             val qualifiedName = cls.containingPackage().qualifiedName()
46             if (qualifiedName.isNotBlank()) {
47                 writer.println("package $qualifiedName;")
48                 writer.println()
49             }
50             if (config.includeDocumentationInStubs) {
51                 // All the classes referenced in the stubs are fully qualified, so no imports are
52                 // needed. However, in some cases for javadoc, replacement with fully qualified name
53                 // fails, and thus we need to include imports for the stubs to compile.
54                 cls.getSourceFile()?.getImports(filterReference)?.let {
55                     for (item in it) {
56                         if (item.isMember) {
57                             writer.println("import static ${item.pattern};")
58                         } else {
59                             writer.println("import ${item.pattern};")
60                         }
61                     }
62                     writer.println()
63                 }
64             }
65         }
66 
67         appendDocumentation(cls, writer, config)
68 
69         // "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked"
70         writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})")
71 
72         appendModifiers(cls)
73 
74         when {
75             cls.isAnnotationType() -> writer.print("@interface")
76             cls.isInterface() -> writer.print("interface")
77             cls.isEnum() -> writer.print("enum")
78             else -> writer.print("class")
79         }
80 
81         writer.print(" ")
82         writer.print(cls.simpleName())
83 
84         generateTypeParameterList(typeList = cls.typeParameterList, addSpace = false)
85         generateSuperClassDeclaration(cls)
86         generateInterfaceList(cls)
87         writer.print(" {\n")
88 
89         if (cls.isEnum()) {
90             var first = true
91             // Enums should preserve the original source order, not alphabetical etc. sort
92             for (field in cls.filteredFields(filterReference, true).sortedBy { it.sortingRank }) {
93                 if (field.isEnumConstant()) {
94                     if (first) {
95                         first = false
96                     } else {
97                         writer.write(",\n")
98                     }
99                     appendDocumentation(field, writer, config)
100 
101                     // Append the modifier list even though the enum constant does not actually have
102                     // modifiers as that will write the annotations which it does have and ignore
103                     // the modifiers.
104                     appendModifiers(field)
105 
106                     writer.write(field.name())
107                 }
108             }
109             writer.println(";")
110         }
111 
112         generateMissingConstructors(cls)
113     }
114 
115     override fun afterVisitClass(cls: ClassItem) {
116         writer.print("}\n\n")
117     }
118 
119     private fun appendModifiers(item: Item) {
120         modifierListWriter.write(item.actualItem)
121     }
122 
123     private fun generateSuperClassDeclaration(cls: ClassItem) {
124         if (cls.isEnum() || cls.isAnnotationType() || cls.isInterface()) {
125             // No extends statement for enums and annotations; it's implied by the "enum" and
126             // "@interface" keywords. Normal interfaces do support an extends statement but it is
127             // generated in [generateInterfaceList].
128             return
129         }
130 
131         val superClass =
132             if (preFiltered) cls.superClassType() else cls.filteredSuperClassType(filterReference)
133 
134         if (superClass != null && !superClass.isJavaLangObject()) {
135             writer.print(" extends ")
136             writer.print(superClass.toTypeString())
137         }
138     }
139 
140     private fun generateInterfaceList(cls: ClassItem) {
141         if (cls.isAnnotationType()) {
142             // No extends statement for annotations; it's implied by the "@interface" keyword
143             return
144         }
145 
146         val interfaces =
147             if (preFiltered) cls.interfaceTypes() else cls.filteredInterfaceTypes(filterReference)
148 
149         if (interfaces.any()) {
150             val label = if (cls.isInterface()) " extends" else " implements"
151             writer.print(label)
152             interfaces.forEachIndexed { index, type ->
153                 if (index > 0) {
154                     writer.print(",")
155                 }
156                 writer.print(" ")
157                 writer.print(type.toTypeString())
158             }
159         }
160     }
161 
162     private fun generateTypeParameterList(typeList: TypeParameterList, addSpace: Boolean) {
163         val typeListString = typeList.toString()
164         if (typeListString.isNotEmpty()) {
165             writer.print(typeListString)
166 
167             if (addSpace) {
168                 writer.print(' ')
169             }
170         }
171     }
172 
173     override fun visitConstructor(constructor: ConstructorItem) {
174         writeConstructor(constructor, constructor.superConstructor)
175     }
176 
177     private fun writeConstructor(constructor: MethodItem, superConstructor: MethodItem?) {
178         writer.println()
179         appendDocumentation(constructor, writer, config)
180         appendModifiers(constructor)
181         generateTypeParameterList(typeList = constructor.typeParameterList, addSpace = true)
182         writer.print(constructor.containingClass().simpleName())
183 
184         generateParameterList(constructor)
185         generateThrowsList(constructor)
186 
187         writer.print(" { ")
188 
189         writeConstructorBody(constructor, superConstructor)
190         writer.println(" }")
191     }
192 
193     private fun writeConstructorBody(constructor: MethodItem, superConstructor: MethodItem?) {
194         // Find any constructor in parent that we can compile against
195         superConstructor?.let { it ->
196             val parameters = it.parameters()
197             if (parameters.isNotEmpty()) {
198                 val includeCasts =
199                     it.containingClass().constructors().filter { filterReference.test(it) }.size > 1
200                 writer.print("super(")
201                 parameters.forEachIndexed { index, parameter ->
202                     if (index > 0) {
203                         writer.write(", ")
204                     }
205                     val type = parameter.type()
206                     if (type !is PrimitiveTypeItem) {
207                         if (includeCasts) {
208                             // Casting to the erased type could lead to unchecked warnings (which
209                             // are suppressed) but avoids having to deal with parameterized types
210                             // and ensures that casting to a vararg parameter uses an array type.
211                             val typeString = type.toErasedTypeString()
212                             writer.write("(")
213                             if (type is VariableTypeItem) {
214                                 // The super constructor's parameter is a type variable: so see if
215                                 // it should be mapped back to a type specified by this class. e.g.
216                                 // Given:
217                                 //   class Bar<T extends Number> {
218                                 //       public Bar(int i) {}
219                                 //       public Bar(T t) {}
220                                 //   }
221                                 //   class Foo extends Bar<Integer> {
222                                 //       public Foo(Integer i) { super(i); }
223                                 //   }
224                                 //
225                                 // The stub for Foo should use:
226                                 //     super((Integer) i);
227                                 // Not:
228                                 //     super((Number) i);
229                                 //
230                                 // However, if the super class is referenced as a raw type then
231                                 // there will be no mapping in which case fall back to the erased
232                                 // type which will use the type variable's lower bound. e.g.
233                                 // Given:
234                                 //   class Foo extends Bar {
235                                 //       public Foo(Integer i) { super(i); }
236                                 //   }
237                                 //
238                                 // The stub for Foo should use:
239                                 //     super((Number) i);
240                                 val map =
241                                     constructor
242                                         .containingClass()
243                                         .mapTypeVariables(it.containingClass())
244                                 val cast = map[type.asTypeParameter]?.toTypeString() ?: typeString
245                                 writer.write(cast)
246                             } else {
247                                 writer.write(typeString)
248                             }
249                             writer.write(")")
250                         }
251                         writer.write("null")
252                     } else {
253                         // Add cast for things like shorts and bytes
254                         val typeString = type.toTypeString()
255                         if (
256                             typeString != "boolean" && typeString != "int" && typeString != "long"
257                         ) {
258                             writer.write("(")
259                             writer.write(typeString)
260                             writer.write(")")
261                         }
262                         writer.write(type.defaultValueString())
263                     }
264                 }
265                 writer.print("); ")
266             }
267         }
268 
269         writeThrowStub()
270     }
271 
272     private fun generateMissingConstructors(cls: ClassItem) {
273         val clsStubConstructor = cls.stubConstructor
274         val constructors = cls.filteredConstructors(filterEmit)
275         // If the default stub constructor is not publicly visible then it won't be output during
276         // the normal visiting
277         // so visit it specially to ensure that it is output.
278         if (clsStubConstructor != null && !constructors.contains(clsStubConstructor)) {
279             visitConstructor(clsStubConstructor)
280             return
281         }
282     }
283 
284     override fun visitMethod(method: MethodItem) {
285         writeMethod(method.containingClass(), method)
286     }
287 
288     private fun writeMethod(containingClass: ClassItem, method: MethodItem) {
289         writer.println()
290         appendDocumentation(method, writer, config)
291 
292         appendModifiers(method)
293         generateTypeParameterList(typeList = method.typeParameterList, addSpace = true)
294 
295         val returnType = method.returnType()
296         writer.print(returnType.toTypeString(annotations = false, filter = filterReference))
297 
298         writer.print(' ')
299         writer.print(method.name())
300         generateParameterList(method)
301         generateThrowsList(method)
302 
303         if (containingClass.isAnnotationType()) {
304             val default = method.defaultValue()
305             if (default.isNotEmpty()) {
306                 writer.print(" default ")
307                 writer.print(default)
308             }
309         }
310 
311         if (ModifierListWriter.requiresMethodBodyInStubs(method.actualItem)) {
312             writer.print(" { ")
313             writeThrowStub()
314             writer.println(" }")
315         } else {
316             writer.println(";")
317         }
318     }
319 
320     override fun visitField(field: FieldItem) {
321         // Handled earlier in visitClass
322         if (field.isEnumConstant()) {
323             return
324         }
325 
326         writer.println()
327 
328         appendDocumentation(field, writer, config)
329         appendModifiers(field)
330         writer.print(field.type().toTypeString(annotations = false, filter = filterReference))
331         writer.print(' ')
332         writer.print(field.name())
333         val needsInitialization =
334             field.modifiers.isFinal() &&
335                 field.initialValue(true) == null &&
336                 field.containingClass().isClass()
337         field.writeValueWithSemicolon(
338             writer,
339             allowDefaultValue = !needsInitialization,
340             requireInitialValue = !needsInitialization
341         )
342         writer.print("\n")
343 
344         if (needsInitialization) {
345             if (field.modifiers.isStatic()) {
346                 writer.print("static ")
347             }
348             writer.print("{ ${field.name()} = ${field.type().defaultValueString()}; }\n")
349         }
350     }
351 
352     private fun writeThrowStub() {
353         writer.write("throw new RuntimeException(\"Stub!\");")
354     }
355 
356     private fun generateParameterList(method: MethodItem) {
357         writer.print("(")
358         method.parameters().asSequence().forEachIndexed { i, parameter ->
359             if (i > 0) {
360                 writer.print(", ")
361             }
362             appendModifiers(parameter)
363             writer.print(
364                 parameter.type().toTypeString(annotations = false, filter = filterReference)
365             )
366             writer.print(' ')
367             val name = parameter.publicName() ?: parameter.name()
368             writer.print(name)
369         }
370         writer.print(")")
371     }
372 
373     private fun generateThrowsList(method: MethodItem) {
374         // Note that throws types are already sorted internally to help comparison matching
375         val throws =
376             if (preFiltered) {
377                 method.throwsTypes().asSequence()
378             } else {
379                 method.filteredThrowsTypes(filterReference).asSequence()
380             }
381         if (throws.any()) {
382             writer.print(" throws ")
383             throws.sortedWith(ExceptionTypeItem.fullNameComparator).forEachIndexed { i, type ->
384                 if (i > 0) {
385                     writer.print(", ")
386                 }
387                 writer.print(type.toTypeString())
388             }
389         }
390     }
391 }
392