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