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.compatibility
20 import com.android.tools.metalava.model.AnnotationTarget
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.ConstructorItem
23 import com.android.tools.metalava.model.FieldItem
24 import com.android.tools.metalava.model.Item
25 import com.android.tools.metalava.model.MemberItem
26 import com.android.tools.metalava.model.MethodItem
27 import com.android.tools.metalava.model.ModifierList
28 import com.android.tools.metalava.model.PackageItem
29 import com.android.tools.metalava.model.TypeParameterList
30 import com.android.tools.metalava.model.psi.PsiClassItem
31 import com.android.tools.metalava.model.visitors.ItemVisitor
32 import com.android.tools.metalava.options
33 import java.io.PrintWriter
34 import java.util.function.Predicate
35 
36 class JavaStubWriter(
37     private val writer: PrintWriter,
38     private val filterEmit: Predicate<Item>,
39     private val filterReference: Predicate<Item>,
40     private val generateAnnotations: Boolean = false,
41     private val preFiltered: Boolean = true,
42     private val docStubs: Boolean
43 ) : ItemVisitor() {
44     private val annotationTarget = if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
45 
46     override fun visitClass(cls: ClassItem) {
47         if (cls.isTopLevelClass()) {
48             val qualifiedName = cls.containingPackage().qualifiedName()
49             if (qualifiedName.isNotBlank()) {
50                 writer.println("package $qualifiedName;")
51                 writer.println()
52             }
53             if (options.includeDocumentationInStubs) {
54                 // All the classes referenced in the stubs are fully qualified, so no imports are
55                 // needed. However, in some cases for javadoc, replacement with fully qualified name
56                 // fails and thus we need to include imports for the stubs to compile.
57                 val compilationUnit = cls.getCompilationUnit()
58                 compilationUnit?.getImportStatements(filterReference)?.let {
59                     for (item in it) {
60                         when (item) {
61                             is PackageItem ->
62                                 writer.println("import ${item.qualifiedName()}.*;")
63                             is ClassItem ->
64                                 writer.println("import ${item.qualifiedName()};")
65                             is MemberItem ->
66                                 writer.println(
67                                     "import static ${item.containingClass()
68                                         .qualifiedName()}.${item.name()};"
69                                 )
70                         }
71                     }
72                     writer.println()
73                 }
74             }
75         }
76 
77         appendDocumentation(cls, writer, docStubs)
78 
79         // "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked"
80         writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})")
81 
82         // Need to filter out abstract from the modifiers list and turn it
83         // into a concrete method to make the stub compile
84         val removeAbstract = cls.modifiers.isAbstract() && (cls.isEnum() || cls.isAnnotationType())
85 
86         appendModifiers(cls, removeAbstract)
87 
88         when {
89             cls.isAnnotationType() -> writer.print("@interface")
90             cls.isInterface() -> writer.print("interface")
91             cls.isEnum() -> writer.print("enum")
92             else -> writer.print("class")
93         }
94 
95         writer.print(" ")
96         writer.print(cls.simpleName())
97 
98         generateTypeParameterList(typeList = cls.typeParameterList(), addSpace = false)
99         generateSuperClassDeclaration(cls)
100         generateInterfaceList(cls)
101         writer.print(" {\n")
102 
103         if (cls.isEnum()) {
104             var first = true
105             // Enums should preserve the original source order, not alphabetical etc sort
106             for (field in cls.filteredFields(filterReference, true).sortedBy { it.sortingRank }) {
107                 if (field.isEnumConstant()) {
108                     if (first) {
109                         first = false
110                     } else {
111                         writer.write(",\n")
112                     }
113                     appendDocumentation(field, writer, docStubs)
114 
115                     // Can't just appendModifiers(field, true, true): enum constants
116                     // don't take modifier lists, only annotations
117                     ModifierList.writeAnnotations(
118                         item = field,
119                         target = annotationTarget,
120                         runtimeAnnotationsOnly = !generateAnnotations,
121                         includeDeprecated = true,
122                         writer = writer,
123                         separateLines = true,
124                         list = field.modifiers,
125                         skipNullnessAnnotations = false,
126                         omitCommonPackages = false
127                     )
128 
129                     writer.write(field.name())
130                 }
131             }
132             writer.println(";")
133         }
134 
135         generateMissingConstructors(cls)
136     }
137 
138     override fun afterVisitClass(cls: ClassItem) {
139         writer.print("}\n\n")
140     }
141 
142     private fun appendModifiers(
143         item: Item,
144         removeAbstract: Boolean = false,
145         removeFinal: Boolean = false,
146         addPublic: Boolean = false
147     ) {
148         appendModifiers(item, item.modifiers, removeAbstract, removeFinal, addPublic)
149     }
150 
151     private fun appendModifiers(
152         item: Item,
153         modifiers: ModifierList,
154         removeAbstract: Boolean,
155         removeFinal: Boolean = false,
156         addPublic: Boolean = false
157     ) {
158         val separateLines = item is ClassItem || item is MethodItem
159 
160         ModifierList.write(
161             writer, modifiers, item,
162             target = annotationTarget,
163             includeAnnotations = true,
164             includeDeprecated = true,
165             runtimeAnnotationsOnly = !generateAnnotations,
166             removeAbstract = removeAbstract,
167             removeFinal = removeFinal,
168             addPublic = addPublic,
169             separateLines = separateLines
170         )
171     }
172 
173     private fun generateSuperClassDeclaration(cls: ClassItem) {
174         if (cls.isEnum() || cls.isAnnotationType()) {
175             // No extends statement for enums and annotations; it's implied by the "enum" and "@interface" keywords
176             return
177         }
178 
179         val superClass = if (preFiltered)
180             cls.superClassType()
181         else cls.filteredSuperClassType(filterReference)
182 
183         if (superClass != null && !superClass.isJavaLangObject()) {
184             val qualifiedName = superClass.toTypeString()
185             writer.print(" extends ")
186 
187             if (qualifiedName.contains("<")) {
188                 // TODO: I need to push this into the model at filter-time such that clients don't need
189                 // to remember to do this!!
190                 val s = superClass.asClass()
191                 if (s != null) {
192                     val map = cls.mapTypeVariables(s)
193                     val replaced = superClass.convertTypeString(map)
194                     writer.print(replaced)
195                     return
196                 }
197             }
198             (cls as PsiClassItem).psiClass.superClassType
199             writer.print(qualifiedName)
200         }
201     }
202 
203     private fun generateInterfaceList(cls: ClassItem) {
204         if (cls.isAnnotationType()) {
205             // No extends statement for annotations; it's implied by the "@interface" keyword
206             return
207         }
208 
209         val interfaces = if (preFiltered)
210             cls.interfaceTypes().asSequence()
211         else cls.filteredInterfaceTypes(filterReference).asSequence()
212 
213         if (interfaces.any()) {
214             if (cls.isInterface() && cls.superClassType() != null)
215                 writer.print(", ")
216             else writer.print(" implements")
217             interfaces.forEachIndexed { index, type ->
218                 if (index > 0) {
219                     writer.print(",")
220                 }
221                 writer.print(" ")
222                 writer.print(type.toTypeString())
223             }
224         } else if (compatibility.classForAnnotations && cls.isAnnotationType()) {
225             writer.print(" implements java.lang.annotation.Annotation")
226         }
227     }
228 
229     private fun generateTypeParameterList(
230         typeList: TypeParameterList,
231         addSpace: Boolean
232     ) {
233         // TODO: Do I need to map type variables?
234 
235         val typeListString = typeList.toString()
236         if (typeListString.isNotEmpty()) {
237             writer.print(typeListString)
238 
239             if (addSpace) {
240                 writer.print(' ')
241             }
242         }
243     }
244 
245     override fun visitConstructor(constructor: ConstructorItem) {
246         writeConstructor(constructor, constructor.superConstructor)
247     }
248 
249     private fun writeConstructor(
250         constructor: MethodItem,
251         superConstructor: MethodItem?
252     ) {
253         writer.println()
254         appendDocumentation(constructor, writer, docStubs)
255         appendModifiers(constructor, false)
256         generateTypeParameterList(
257             typeList = constructor.typeParameterList(),
258             addSpace = true
259         )
260         writer.print(constructor.containingClass().simpleName())
261 
262         generateParameterList(constructor)
263         generateThrowsList(constructor)
264 
265         writer.print(" { ")
266 
267         writeConstructorBody(constructor, superConstructor)
268         writer.println(" }")
269     }
270 
271     private fun writeConstructorBody(constructor: MethodItem?, superConstructor: MethodItem?) {
272         // Find any constructor in parent that we can compile against
273         superConstructor?.let { it ->
274             val parameters = it.parameters()
275             val invokeOnThis = constructor != null && constructor.containingClass() == it.containingClass()
276             if (invokeOnThis || parameters.isNotEmpty()) {
277                 val includeCasts = parameters.isNotEmpty() &&
278                     it.containingClass().constructors().filter { filterReference.test(it) }.size > 1
279                 if (invokeOnThis) {
280                     writer.print("this(")
281                 } else {
282                     writer.print("super(")
283                 }
284                 parameters.forEachIndexed { index, parameter ->
285                     if (index > 0) {
286                         writer.write(", ")
287                     }
288                     val type = parameter.type()
289                     if (!type.primitive) {
290                         if (includeCasts) {
291                             // Types with varargs can't appear as varargs when used as an argument
292                             val typeString = type.toErasedTypeString(it).replace("...", "[]")
293                             writer.write("(")
294                             if (type.asTypeParameter(superConstructor) != null) {
295                                 // It's a type parameter: see if we should map the type back to the concrete
296                                 // type in this class
297                                 val map = constructor?.containingClass()?.mapTypeVariables(it.containingClass())
298                                 val cast = map?.get(type.toTypeString(context = it)) ?: typeString
299                                 writer.write(cast)
300                             } else {
301                                 writer.write(typeString)
302                             }
303                             writer.write(")")
304                         }
305                         writer.write("null")
306                     } else {
307                         // Add cast for things like shorts and bytes
308                         val typeString = type.toTypeString(context = it)
309                         if (typeString != "boolean" && typeString != "int" && typeString != "long") {
310                             writer.write("(")
311                             writer.write(typeString)
312                             writer.write(")")
313                         }
314                         writer.write(type.defaultValueString())
315                     }
316                 }
317                 writer.print("); ")
318             }
319         }
320 
321         writeThrowStub()
322     }
323 
324     private fun generateMissingConstructors(cls: ClassItem) {
325         val clsStubConstructor = cls.stubConstructor
326         val constructors = cls.filteredConstructors(filterEmit)
327         // If the default stub constructor is not publicly visible then it won't be output during the normal visiting
328         // so visit it specially to ensure that it is output.
329         if (clsStubConstructor != null && !constructors.contains(clsStubConstructor)) {
330             visitConstructor(clsStubConstructor)
331             return
332         }
333     }
334 
335     override fun visitMethod(method: MethodItem) {
336         writeMethod(method.containingClass(), method, false)
337     }
338 
339     private fun writeMethod(containingClass: ClassItem, method: MethodItem, movedFromInterface: Boolean) {
340         val modifiers = method.modifiers
341         val isEnum = containingClass.isEnum()
342         val isAnnotation = containingClass.isAnnotationType()
343 
344         if (method.isEnumSyntheticMethod()) {
345             // Skip the values() and valueOf(String) methods in enums: these are added by
346             // the compiler for enums anyway, but was part of the doclava1 signature files
347             // so inserted in compat mode.
348             return
349         }
350 
351         writer.println()
352         appendDocumentation(method, writer, docStubs)
353 
354         // Need to filter out abstract from the modifiers list and turn it
355         // into a concrete method to make the stub compile
356         val removeAbstract = modifiers.isAbstract() && (isEnum || isAnnotation) || movedFromInterface
357 
358         appendModifiers(method, modifiers, removeAbstract, movedFromInterface)
359         generateTypeParameterList(typeList = method.typeParameterList(), addSpace = true)
360 
361         val returnType = method.returnType()
362         writer.print(
363             returnType?.toTypeString(
364                 outerAnnotations = false,
365                 innerAnnotations = generateAnnotations,
366                 filter = filterReference
367             )
368         )
369 
370         writer.print(' ')
371         writer.print(method.name())
372         generateParameterList(method)
373         generateThrowsList(method)
374 
375         if (isAnnotation) {
376             val default = method.defaultValue()
377             if (default.isNotEmpty()) {
378                 writer.print(" default ")
379                 writer.print(default)
380             }
381         }
382 
383         if (modifiers.isAbstract() && !removeAbstract && !isEnum || isAnnotation || modifiers.isNative()) {
384             writer.println(";")
385         } else {
386             writer.print(" { ")
387             writeThrowStub()
388             writer.println(" }")
389         }
390     }
391 
392     override fun visitField(field: FieldItem) {
393         // Handled earlier in visitClass
394         if (field.isEnumConstant()) {
395             return
396         }
397 
398         writer.println()
399 
400         appendDocumentation(field, writer, docStubs)
401         appendModifiers(field, removeAbstract = false, removeFinal = false)
402         writer.print(
403             field.type().toTypeString(
404                 outerAnnotations = false,
405                 innerAnnotations = generateAnnotations,
406                 filter = filterReference
407             )
408         )
409         writer.print(' ')
410         writer.print(field.name())
411         val needsInitialization =
412             field.modifiers.isFinal() && field.initialValue(true) == null && field.containingClass().isClass()
413         field.writeValueWithSemicolon(
414             writer,
415             allowDefaultValue = !needsInitialization,
416             requireInitialValue = !needsInitialization
417         )
418         writer.print("\n")
419 
420         if (needsInitialization) {
421             if (field.modifiers.isStatic()) {
422                 writer.print("static ")
423             }
424             writer.print("{ ${field.name()} = ${field.type().defaultValueString()}; }\n")
425         }
426     }
427 
428     private fun writeThrowStub() {
429         writer.write("throw new RuntimeException(\"Stub!\");")
430     }
431 
432     private fun generateParameterList(method: MethodItem) {
433         writer.print("(")
434         method.parameters().asSequence().forEachIndexed { i, parameter ->
435             if (i > 0) {
436                 writer.print(", ")
437             }
438             appendModifiers(parameter, false)
439             writer.print(
440                 parameter.type().toTypeString(
441                     outerAnnotations = false,
442                     innerAnnotations = generateAnnotations,
443                     filter = filterReference
444                 )
445             )
446             writer.print(' ')
447             val name = parameter.publicName() ?: parameter.name()
448             writer.print(name)
449         }
450         writer.print(")")
451     }
452 
453     private fun generateThrowsList(method: MethodItem) {
454         // Note that throws types are already sorted internally to help comparison matching
455         val throws = if (preFiltered) {
456             method.throwsTypes().asSequence()
457         } else {
458             method.filteredThrowsTypes(filterReference).asSequence()
459         }
460         if (throws.any()) {
461             writer.print(" throws ")
462             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
463                 if (i > 0) {
464                     writer.print(", ")
465                 }
466                 // TODO: Shouldn't declare raw types here!
467                 writer.print(type.qualifiedName())
468             }
469         }
470     }
471 }
472