• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.model.CallableItem
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.ClassTypeItem
22 import com.android.tools.metalava.model.ConstructorItem
23 import com.android.tools.metalava.model.DelegatedVisitor
24 import com.android.tools.metalava.model.ExceptionTypeItem
25 import com.android.tools.metalava.model.FieldItem
26 import com.android.tools.metalava.model.Item
27 import com.android.tools.metalava.model.JAVA_LANG_STRING
28 import com.android.tools.metalava.model.MethodItem
29 import com.android.tools.metalava.model.ModifierListWriter
30 import com.android.tools.metalava.model.PrimitiveTypeItem
31 import com.android.tools.metalava.model.TypeItem
32 import com.android.tools.metalava.model.TypeParameterBindings
33 import com.android.tools.metalava.model.TypeParameterList
34 import com.android.tools.metalava.model.VariableTypeItem
35 import java.io.PrintWriter
36 
37 internal class JavaStubWriter(
38     private val writer: PrintWriter,
39     private val modifierListWriter: ModifierListWriter,
40     private val config: StubWriterConfig,
41     private val stubConstructorManager: StubConstructorManager,
42 ) : DelegatedVisitor {
43 
44     override fun visitClass(cls: ClassItem) {
45         if (cls.isTopLevelClass()) {
46             val qualifiedName = cls.containingPackage().qualifiedName()
47             if (qualifiedName.isNotBlank()) {
48                 writer.println("package $qualifiedName;")
49                 writer.println()
50             }
51             if (config.includeDocumentationInStubs) {
52                 // All the classes referenced in the stubs are fully qualified, so no imports are
53                 // needed. However, in some cases for javadoc, replacement with fully qualified name
54                 // fails, and thus we need to include imports for the stubs to compile.
55                 cls.sourceFile()?.getImports()?.let {
56                     for (item in it) {
57                         if (item.isMember) {
58                             writer.println("import static ${item.pattern};")
59                         } else {
60                             writer.println("import ${item.pattern};")
61                         }
62                     }
63                     writer.println()
64                 }
65             }
66         }
67 
68         appendDocumentation(cls, writer, config)
69 
70         // "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked"
71         writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})")
72 
73         appendModifiers(cls)
74 
75         when {
76             cls.isAnnotationType() -> writer.print("@interface")
77             cls.isInterface() -> writer.print("interface")
78             cls.isEnum() -> writer.print("enum")
79             else -> writer.print("class")
80         }
81 
82         writer.print(" ")
83         writer.print(cls.simpleName())
84 
85         generateTypeParameterList(typeList = cls.typeParameterList, addSpace = false)
86         generateSuperClassDeclaration(cls)
87         generateInterfaceList(cls)
88         writer.print(" {\n")
89 
90         // Enum constants must be written out first.
91         if (cls.isEnum()) {
92             var first = true
93             // While enum order is significant at runtime as it affects `Enum.ordinal` and its
94             // comparable order it is not significant in the stubs so sort alphabetically. That
95             // matches the order in the documentation and the signature files. It is theoretically
96             // possible for an annotation processor to care about the order but any that did would
97             // be poorly written and would break on stubs created from signature files.
98             val enumConstants =
99                 cls.fields().filter { it.isEnumConstant() }.sortedWith(FieldItem.comparator)
100             for (enumConstant in enumConstants) {
101                 if (first) {
102                     first = false
103                 } else {
104                     writer.write(",\n")
105                 }
106                 appendDocumentation(enumConstant, writer, config)
107 
108                 // Append the modifier list even though the enum constant does not actually have
109                 // modifiers as that will write the annotations which it does have and ignore
110                 // the modifiers.
111                 appendModifiers(enumConstant)
112 
113                 writer.write(enumConstant.name())
114             }
115             writer.println(";")
116         }
117     }
118 
119     override fun afterVisitClass(cls: ClassItem) {
120         writer.print("}\n\n")
121     }
122 
123     private fun appendModifiers(item: Item) {
124         modifierListWriter.write(item)
125     }
126 
127     private fun generateSuperClassDeclaration(cls: ClassItem) {
128         if (cls.isEnum() || cls.isAnnotationType() || cls.isInterface()) {
129             // No extends statement for enums and annotations; it's implied by the "enum" and
130             // "@interface" keywords. Normal interfaces do support an extends statement but it is
131             // generated in [generateInterfaceList].
132             return
133         }
134 
135         val superClass = cls.superClassType()
136         if (superClass != null && !superClass.isJavaLangObject()) {
137             writer.print(" extends ")
138             writer.print(superClass.toTypeString())
139         }
140     }
141 
142     private fun generateInterfaceList(cls: ClassItem) {
143         if (cls.isAnnotationType()) {
144             // No extends statement for annotations; it's implied by the "@interface" keyword
145             return
146         }
147 
148         val interfaces = cls.interfaceTypes()
149         if (interfaces.isNotEmpty()) {
150             val label = if (cls.isInterface()) " extends" else " implements"
151             writer.print(label)
152             interfaces.sortedWith(TypeItem.totalComparator).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         writer.println()
175         appendDocumentation(constructor, writer, config)
176         appendModifiers(constructor)
177         generateTypeParameterList(typeList = constructor.typeParameterList, addSpace = true)
178         writer.print(constructor.containingClass().simpleName())
179 
180         generateParameterList(constructor)
181         generateThrowsList(constructor)
182 
183         writer.print(" { ")
184 
185         writeConstructorBody(constructor)
186         writer.println(" }")
187     }
188 
189     private fun writeConstructorBody(constructor: ConstructorItem) {
190         val optionalSuperConstructor =
191             stubConstructorManager.optionalSuperConstructor(constructor.containingClass())
192         optionalSuperConstructor?.let { superConstructor ->
193             val parameters = superConstructor.parameters()
194             if (parameters.isNotEmpty()) {
195                 writer.print("super(")
196 
197                 // Get the types to which this class binds the super class's type parameters, if
198                 // any.
199                 val typeParameterBindings =
200                     constructor
201                         .containingClass()
202                         .mapTypeVariables(superConstructor.containingClass())
203 
204                 for ((index, parameter) in parameters.withIndex()) {
205                     if (index > 0) {
206                         writer.write(", ")
207                     }
208                     // Always make sure to add appropriate casts to the parameters in the super call
209                     // as without the casts the compiler will fail if there is more than one
210                     // constructor that could match.
211                     val defaultValueWithCast =
212                         defaultValueWithCastForType(parameter.type(), typeParameterBindings)
213                     writer.write(defaultValueWithCast)
214                 }
215                 writer.print("); ")
216             }
217         }
218 
219         writeThrowStub()
220     }
221 
222     /**
223      * Get the string representation of the default value for [type], it will include a cast if
224      * necessary.
225      *
226      * If [type] is a [VariableTypeItem] then it will map it to the appropriate type given the
227      * [typeParameterBindings]. See the comment in the body for more details.
228      */
229     private fun defaultValueWithCastForType(
230         type: TypeItem,
231         typeParameterBindings: TypeParameterBindings,
232     ): String {
233         // Handle special cases and non-reference types, drop through to handle the default
234         // reference type.
235         when (type) {
236             is PrimitiveTypeItem -> {
237                 val kind = type.kind
238                 return when (kind) {
239                     PrimitiveTypeItem.Primitive.BOOLEAN,
240                     PrimitiveTypeItem.Primitive.INT,
241                     PrimitiveTypeItem.Primitive.LONG -> kind.defaultValueString
242                     else -> "(${kind.primitiveName})${kind.defaultValueString}"
243                 }
244             }
245             is ClassTypeItem -> {
246                 val qualifiedName = type.qualifiedName
247                 when (qualifiedName) {
248                     JAVA_LANG_STRING -> return "\"\""
249                 }
250             }
251         }
252 
253         // Get the actual type that the super constructor expects, taking into account any type
254         // parameter mappings.
255         val mappedType =
256             if (type is VariableTypeItem) {
257                 // The super constructor's parameter is a type variable: so see if it should be
258                 // mapped back to a type specified by this class. e.g.
259                 //
260                 // Given:
261                 //   class Bar<T extends Number> {
262                 //       public Bar(int i) {}
263                 //       public Bar(T t) {}
264                 //   }
265                 //   class Foo extends Bar<Integer> {
266                 //       public Foo(Integer i) { super(i); }
267                 //   }
268                 //
269                 // The stub for Foo should use:
270                 //     super((Integer) i);
271                 // Not:
272                 //     super((Number) i);
273                 //
274                 // However, if the super class is referenced as a raw type then there will be no
275                 // mapping in which case fall back to the erased type which will use the type
276                 // variable's lower bound. e.g.
277                 //
278                 // Given:
279                 //   class Foo extends Bar {
280                 //       public Foo(Integer i) { super(i); }
281                 //   }
282                 //
283                 // The stub for Foo should use:
284                 //     super((Number) i);
285                 type.convertType(typeParameterBindings)
286             } else {
287                 type
288             }
289 
290         // Casting to the erased type could lead to unchecked warnings (which are suppressed) but
291         // avoids having to deal with parameterized types and ensures that casting to a vararg
292         // parameter uses an array type.
293         val erasedTypeString = mappedType.toErasedTypeString()
294         return "($erasedTypeString)null"
295     }
296 
297     override fun visitMethod(method: MethodItem) {
298         writeMethod(method.containingClass(), method)
299     }
300 
301     private fun writeMethod(containingClass: ClassItem, method: MethodItem) {
302         writer.println()
303         appendDocumentation(method, writer, config)
304 
305         appendModifiers(method)
306         generateTypeParameterList(typeList = method.typeParameterList, addSpace = true)
307 
308         val returnType = method.returnType()
309         writer.print(returnType.toTypeString())
310 
311         writer.print(' ')
312         writer.print(method.name())
313         generateParameterList(method)
314         generateThrowsList(method)
315 
316         if (containingClass.isAnnotationType()) {
317             val default = method.legacyDefaultValue()
318             if (default.isNotEmpty()) {
319                 writer.print(" default ")
320                 writer.print(default)
321             }
322         }
323 
324         if (ModifierListWriter.requiresMethodBodyInStubs(method)) {
325             writer.print(" { ")
326             writeThrowStub()
327             writer.println(" }")
328         } else {
329             writer.println(";")
330         }
331     }
332 
333     override fun visitField(field: FieldItem) {
334         // Handled earlier in visitClass
335         if (field.isEnumConstant()) {
336             return
337         }
338 
339         writer.println()
340 
341         appendDocumentation(field, writer, config)
342         appendModifiers(field)
343         writer.print(field.type().toTypeString())
344         writer.print(' ')
345         writer.print(field.name())
346         val needsInitialization =
347             field.modifiers.isFinal() &&
348                 field.legacyInitialValue(true) == null &&
349                 field.containingClass().isClass()
350         field.writeValueWithSemicolon(
351             writer,
352             allowDefaultValue = !needsInitialization,
353             requireInitialValue = !needsInitialization
354         )
355         writer.print("\n")
356 
357         if (needsInitialization) {
358             if (field.modifiers.isStatic()) {
359                 writer.print("static ")
360             }
361             writer.print("{ ${field.name()} = ${field.type().defaultValueString()}; }\n")
362         }
363     }
364 
365     private fun writeThrowStub() {
366         writer.write("throw new RuntimeException(\"Stub!\");")
367     }
368 
369     private fun generateParameterList(callable: CallableItem) {
370         writer.print("(")
371         callable.parameters().asSequence().forEachIndexed { i, parameter ->
372             if (i > 0) {
373                 writer.print(", ")
374             }
375             appendModifiers(parameter)
376             writer.print(parameter.type().toTypeString())
377             writer.print(' ')
378             val name = parameter.publicName() ?: parameter.name()
379             writer.print(name)
380         }
381         writer.print(")")
382     }
383 
384     private fun generateThrowsList(callable: CallableItem) {
385         val throws = callable.throwsTypes()
386         if (throws.isNotEmpty()) {
387             writer.print(" throws ")
388             throws.sortedWith(ExceptionTypeItem.fullNameComparator).forEachIndexed { i, type ->
389                 if (i > 0) {
390                     writer.print(", ")
391                 }
392                 writer.print(type.toTypeString())
393             }
394         }
395     }
396 }
397