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