• 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
18 
19 import com.android.tools.metalava.doclava1.ApiPredicate
20 import com.android.tools.metalava.doclava1.Errors
21 import com.android.tools.metalava.doclava1.FilterPredicate
22 import com.android.tools.metalava.model.AnnotationTarget
23 import com.android.tools.metalava.model.ClassItem
24 import com.android.tools.metalava.model.Codebase
25 import com.android.tools.metalava.model.ConstructorItem
26 import com.android.tools.metalava.model.FieldItem
27 import com.android.tools.metalava.model.Item
28 import com.android.tools.metalava.model.MemberItem
29 import com.android.tools.metalava.model.MethodItem
30 import com.android.tools.metalava.model.ModifierList
31 import com.android.tools.metalava.model.PackageItem
32 import com.android.tools.metalava.model.TypeParameterList
33 import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
34 import com.android.tools.metalava.model.psi.PsiClassItem
35 import com.android.tools.metalava.model.psi.trimDocIndent
36 import com.android.tools.metalava.model.visitors.ApiVisitor
37 import java.io.BufferedWriter
38 import java.io.File
39 import java.io.FileWriter
40 import java.io.IOException
41 import java.io.PrintWriter
42 
43 class StubWriter(
44     private val codebase: Codebase,
45     private val stubsDir: File,
46     private val generateAnnotations: Boolean = false,
47     private val preFiltered: Boolean = true,
48     private val docStubs: Boolean
49 ) : ApiVisitor(
50     visitConstructorsAsMethods = false,
51     nestInnerClasses = true,
52     inlineInheritedFields = true,
53     fieldComparator = FieldItem.comparator,
54     // Methods are by default sorted in source order in stubs, to encourage methods
55     // that are near each other in the source to show up near each other in the documentation
56     methodComparator = MethodItem.sourceOrderComparator,
57     filterEmit = FilterPredicate(ApiPredicate(ignoreShown = true, includeDocOnly = docStubs))
58         // In stubs we have to include non-strippable things too. This is an error in the API,
59         // and we've removed all of it from the framework, but there are libraries which still
60         // have reference errors.
61         .or { it is ClassItem && it.notStrippable },
62     filterReference = ApiPredicate(ignoreShown = true, includeDocOnly = docStubs),
63     includeEmptyOuterClasses = true
64 ) {
65     private val annotationTarget = if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
66 
67     private val sourceList = StringBuilder(20000)
68 
69     /** Writes a source file list of the generated stubs */
writeSourceListnull70     fun writeSourceList(target: File, root: File?) {
71         target.parentFile?.mkdirs()
72         val contents = if (root != null) {
73             val path = root.path.replace('\\', '/') + "/"
74             sourceList.toString().replace(path, "")
75         } else {
76             sourceList.toString()
77         }
78         target.writeText(contents)
79     }
80 
startFilenull81     private fun startFile(sourceFile: File) {
82         if (sourceList.isNotEmpty()) {
83             sourceList.append(' ')
84         }
85         sourceList.append(sourceFile.path.replace('\\', '/'))
86     }
87 
visitPackagenull88     override fun visitPackage(pkg: PackageItem) {
89         getPackageDir(pkg, create = true)
90 
91         writePackageInfo(pkg)
92 
93         if (docStubs) {
94             codebase.getPackageDocs()?.let { packageDocs ->
95                 packageDocs.getOverviewDocumentation(pkg)?.let { writeDocOverview(pkg, it) }
96             }
97         }
98     }
99 
writeDocOverviewnull100     fun writeDocOverview(pkg: PackageItem, content: String) {
101         if (content.isBlank()) {
102             return
103         }
104 
105         val sourceFile = File(getPackageDir(pkg), "overview.html")
106         val writer = try {
107             PrintWriter(BufferedWriter(FileWriter(sourceFile)))
108         } catch (e: IOException) {
109             reporter.report(Errors.IO_ERROR, sourceFile, "Cannot open file for write.")
110             return
111         }
112 
113         // Should we include this in our stub list?
114         //     startFile(sourceFile)
115 
116         writer.println(content)
117         writer.flush()
118         writer.close()
119     }
120 
writePackageInfonull121     private fun writePackageInfo(pkg: PackageItem) {
122         val annotations = pkg.modifiers.annotations()
123         if (annotations.isNotEmpty() && generateAnnotations || !pkg.documentation.isBlank()) {
124             val sourceFile = File(getPackageDir(pkg), "package-info.java")
125             val writer = try {
126                 PrintWriter(BufferedWriter(FileWriter(sourceFile)))
127             } catch (e: IOException) {
128                 reporter.report(Errors.IO_ERROR, sourceFile, "Cannot open file for write.")
129                 return
130             }
131             startFile(sourceFile)
132 
133             appendDocumentation(pkg, writer)
134 
135             if (annotations.isNotEmpty()) {
136                 ModifierList.writeAnnotations(
137                     list = pkg.modifiers,
138                     separateLines = true,
139                     // Some bug in UAST triggers duplicate nullability annotations
140                     // here; make sure the are filtered out
141                     filterDuplicates = true,
142                     target = annotationTarget,
143                     writer = writer
144                 )
145             }
146             writer.println("package ${pkg.qualifiedName()};")
147 
148             writer.flush()
149             writer.close()
150         }
151     }
152 
getPackageDirnull153     private fun getPackageDir(packageItem: PackageItem, create: Boolean = true): File {
154         val relative = packageItem.qualifiedName().replace('.', File.separatorChar)
155         val dir = File(stubsDir, relative)
156         if (create && !dir.isDirectory) {
157             val ok = dir.mkdirs()
158             if (!ok) {
159                 throw IOException("Could not create $dir")
160             }
161         }
162 
163         return dir
164     }
165 
getClassFilenull166     private fun getClassFile(classItem: ClassItem): File {
167         assert(classItem.containingClass() == null) { "Should only be called on top level classes" }
168         // TODO: Look up compilation unit language
169         return File(getPackageDir(classItem.containingPackage()), "${classItem.simpleName()}.java")
170     }
171 
172     /**
173      * Between top level class files the [writer] field doesn't point to a real file; it
174      * points to this writer, which redirects to the error output. Nothing should be written
175      * to the writer at that time.
176      */
177     private var errorWriter = PrintWriter(options.stderr)
178 
179     /** The writer to write the stubs file to */
180     private var writer: PrintWriter = errorWriter
181 
visitClassnull182     override fun visitClass(cls: ClassItem) {
183         if (cls.isTopLevelClass()) {
184             val sourceFile = getClassFile(cls)
185             writer = try {
186                 PrintWriter(BufferedWriter(FileWriter(sourceFile)))
187             } catch (e: IOException) {
188                 reporter.report(Errors.IO_ERROR, sourceFile, "Cannot open file for write.")
189                 errorWriter
190             }
191 
192             startFile(sourceFile)
193 
194             // Copyright statements from the original file?
195             val compilationUnit = cls.getCompilationUnit()
196             compilationUnit?.getHeaderComments()?.let { writer.println(it) }
197 
198             val qualifiedName = cls.containingPackage().qualifiedName()
199             if (qualifiedName.isNotBlank()) {
200                 writer.println("package $qualifiedName;")
201                 writer.println()
202             }
203 
204             @Suppress("ConstantConditionIf")
205             if (EXPAND_DOCUMENTATION) {
206                 compilationUnit?.getImportStatements(filterReference)?.let {
207                     for (item in it) {
208                         when (item) {
209                             is PackageItem ->
210                                 writer.println("import ${item.qualifiedName()}.*;")
211                             is ClassItem ->
212                                 writer.println("import ${item.qualifiedName()};")
213                             is MemberItem ->
214                                 writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()};")
215                         }
216                     }
217                     writer.println()
218                 }
219             }
220         }
221 
222         appendDocumentation(cls, writer)
223 
224         // "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked"
225         writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})")
226 
227         // Need to filter out abstract from the modifiers list and turn it
228         // into a concrete method to make the stub compile
229         val removeAbstract = cls.modifiers.isAbstract() && (cls.isEnum() || cls.isAnnotationType())
230 
231         appendModifiers(cls, removeAbstract)
232 
233         when {
234             cls.isAnnotationType() -> writer.print("@interface")
235             cls.isInterface() -> writer.print("interface")
236             cls.isEnum() -> writer.print("enum")
237             else -> writer.print("class")
238         }
239 
240         writer.print(" ")
241         writer.print(cls.simpleName())
242 
243         generateTypeParameterList(typeList = cls.typeParameterList(), addSpace = false)
244         generateSuperClassStatement(cls)
245         if (!cls.notStrippable) {
246             generateInterfaceList(cls)
247         }
248         writer.print(" {\n")
249 
250         if (cls.isEnum()) {
251             var first = true
252             // Enums should preserve the original source order, not alphabetical etc sort
253             for (field in cls.filteredFields(filterReference, true).sortedBy { it.sortingRank }) {
254                 if (field.isEnumConstant()) {
255                     if (first) {
256                         first = false
257                     } else {
258                         writer.write(",\n")
259                     }
260                     appendDocumentation(field, writer)
261 
262                     // Can't just appendModifiers(field, true, true): enum constants
263                     // don't take modifier lists, only annotations
264                     ModifierList.writeAnnotations(
265                         item = field,
266                         target = annotationTarget,
267                         runtimeAnnotationsOnly = !generateAnnotations,
268                         includeDeprecated = true,
269                         writer = writer,
270                         separateLines = true,
271                         list = field.modifiers,
272                         skipNullnessAnnotations = false,
273                         omitCommonPackages = false
274                     )
275 
276                     writer.write(field.name())
277                 }
278             }
279             writer.println(";")
280         }
281 
282         generateMissingConstructors(cls)
283     }
284 
appendDocumentationnull285     private fun appendDocumentation(item: Item, writer: PrintWriter) {
286         if (options.includeDocumentationInStubs || docStubs) {
287             val documentation = if (docStubs && EXPAND_DOCUMENTATION) {
288                 item.fullyQualifiedDocumentation()
289             } else {
290                 item.documentation
291             }
292             if (documentation.isNotBlank()) {
293                 val trimmed = trimDocIndent(documentation)
294                 writer.println(trimmed)
295                 writer.println()
296             }
297         }
298     }
299 
afterVisitClassnull300     override fun afterVisitClass(cls: ClassItem) {
301         writer.print("}\n\n")
302 
303         if (cls.isTopLevelClass()) {
304             writer.flush()
305             writer.close()
306             writer = errorWriter
307         }
308     }
309 
appendModifiersnull310     private fun appendModifiers(
311         item: Item,
312         removeAbstract: Boolean = false,
313         removeFinal: Boolean = false,
314         addPublic: Boolean = false
315     ) {
316         appendModifiers(item, item.modifiers, removeAbstract, removeFinal, addPublic)
317     }
318 
appendModifiersnull319     private fun appendModifiers(
320         item: Item,
321         modifiers: ModifierList,
322         removeAbstract: Boolean,
323         removeFinal: Boolean = false,
324         addPublic: Boolean = false
325     ) {
326         val separateLines = item is ClassItem || item is MethodItem
327 
328         ModifierList.write(
329             writer, modifiers, item,
330             target = annotationTarget,
331             includeAnnotations = true,
332             includeDeprecated = true,
333             runtimeAnnotationsOnly = !generateAnnotations,
334             removeAbstract = removeAbstract,
335             removeFinal = removeFinal,
336             addPublic = addPublic,
337             separateLines = separateLines
338         )
339     }
340 
generateSuperClassStatementnull341     private fun generateSuperClassStatement(cls: ClassItem) {
342         if (cls.isEnum() || cls.isAnnotationType()) {
343             // No extends statement for enums and annotations; it's implied by the "enum" and "@interface" keywords
344             return
345         }
346 
347         val superClass = if (preFiltered)
348             cls.superClassType()
349         else cls.filteredSuperClassType(filterReference)
350 
351         if (superClass != null && !superClass.isJavaLangObject()) {
352             val qualifiedName = superClass.toTypeString()
353             writer.print(" extends ")
354 
355             if (qualifiedName.contains("<")) {
356                 // TODO: I need to push this into the model at filter-time such that clients don't need
357                 // to remember to do this!!
358                 val s = superClass.asClass()
359                 if (s != null) {
360                     val map = cls.mapTypeVariables(s)
361                     val replaced = superClass.convertTypeString(map)
362                     writer.print(replaced)
363                     return
364                 }
365             }
366             (cls as PsiClassItem).psiClass.superClassType
367             writer.print(qualifiedName)
368         }
369     }
370 
generateInterfaceListnull371     private fun generateInterfaceList(cls: ClassItem) {
372         if (cls.isAnnotationType()) {
373             // No extends statement for annotations; it's implied by the "@interface" keyword
374             return
375         }
376 
377         val interfaces = if (preFiltered)
378             cls.interfaceTypes().asSequence()
379         else cls.filteredInterfaceTypes(filterReference).asSequence()
380 
381         if (interfaces.any()) {
382             if (cls.isInterface() && cls.superClassType() != null)
383                 writer.print(", ")
384             else writer.print(" implements")
385             interfaces.forEachIndexed { index, type ->
386                 if (index > 0) {
387                     writer.print(",")
388                 }
389                 writer.print(" ")
390                 writer.print(type.toTypeString())
391             }
392         } else if (compatibility.classForAnnotations && cls.isAnnotationType()) {
393             writer.print(" implements java.lang.annotation.Annotation")
394         }
395     }
396 
generateTypeParameterListnull397     private fun generateTypeParameterList(
398         typeList: TypeParameterList,
399         addSpace: Boolean
400     ) {
401         // TODO: Do I need to map type variables?
402 
403         val typeListString = typeList.toString()
404         if (typeListString.isNotEmpty()) {
405             writer.print(typeListString)
406 
407             if (addSpace) {
408                 writer.print(' ')
409             }
410         }
411     }
412 
visitConstructornull413     override fun visitConstructor(constructor: ConstructorItem) {
414         if (constructor.containingClass().notStrippable) {
415             return
416         }
417         writeConstructor(constructor, constructor.superConstructor)
418     }
419 
writeConstructornull420     private fun writeConstructor(
421         constructor: MethodItem,
422         superConstructor: MethodItem?
423     ) {
424         writer.println()
425         appendDocumentation(constructor, writer)
426         appendModifiers(constructor, false)
427         generateTypeParameterList(
428             typeList = constructor.typeParameterList(),
429             addSpace = true
430         )
431         writer.print(constructor.containingClass().simpleName())
432 
433         generateParameterList(constructor)
434         generateThrowsList(constructor)
435 
436         writer.print(" { ")
437 
438         writeConstructorBody(constructor, superConstructor)
439         writer.println(" }")
440     }
441 
writeConstructorBodynull442     private fun writeConstructorBody(constructor: MethodItem?, superConstructor: MethodItem?) {
443         // Find any constructor in parent that we can compile against
444         superConstructor?.let { it ->
445             val parameters = it.parameters()
446             val invokeOnThis = constructor != null && constructor.containingClass() == it.containingClass()
447             if (invokeOnThis || parameters.isNotEmpty()) {
448                 val includeCasts = parameters.isNotEmpty() &&
449                     it.containingClass().constructors().filter { filterReference.test(it) }.size > 1
450                 if (invokeOnThis) {
451                     writer.print("this(")
452                 } else {
453                     writer.print("super(")
454                 }
455                 parameters.forEachIndexed { index, parameter ->
456                     if (index > 0) {
457                         writer.write(", ")
458                     }
459                     val type = parameter.type()
460                     if (!type.primitive) {
461                         if (includeCasts) {
462                             // Types with varargs can't appear as varargs when used as an argument
463                             val typeString = type.toErasedTypeString(it).replace("...", "[]")
464                             writer.write("(")
465                             if (type.asTypeParameter(superConstructor) != null) {
466                                 // It's a type parameter: see if we should map the type back to the concrete
467                                 // type in this class
468                                 val map = constructor?.containingClass()?.mapTypeVariables(it.containingClass())
469                                 val cast = map?.get(type.toTypeString(context = it)) ?: typeString
470                                 writer.write(cast)
471                             } else {
472                                 writer.write(typeString)
473                             }
474                             writer.write(")")
475                         }
476                         writer.write("null")
477                     } else {
478                         // Add cast for things like shorts and bytes
479                         val typeString = type.toTypeString(context = it)
480                         if (typeString != "boolean" && typeString != "int" && typeString != "long") {
481                             writer.write("(")
482                             writer.write(typeString)
483                             writer.write(")")
484                         }
485                         writer.write(type.defaultValueString())
486                     }
487                 }
488                 writer.print("); ")
489             }
490         }
491 
492         writeThrowStub()
493     }
494 
generateMissingConstructorsnull495     private fun generateMissingConstructors(cls: ClassItem) {
496         val clsDefaultConstructor = cls.defaultConstructor
497         val constructors = cls.filteredConstructors(filterEmit)
498         if (clsDefaultConstructor != null && !constructors.contains(clsDefaultConstructor)) {
499             clsDefaultConstructor.mutableModifiers().setPackagePrivate(true)
500             visitConstructor(clsDefaultConstructor)
501             return
502         }
503     }
504 
visitMethodnull505     override fun visitMethod(method: MethodItem) {
506         if (method.containingClass().notStrippable) {
507             return
508         }
509         writeMethod(method.containingClass(), method, false)
510     }
511 
writeMethodnull512     private fun writeMethod(containingClass: ClassItem, method: MethodItem, movedFromInterface: Boolean) {
513         val modifiers = method.modifiers
514         val isEnum = containingClass.isEnum()
515         val isAnnotation = containingClass.isAnnotationType()
516 
517         if (isEnum && (method.name() == "values" ||
518                 method.name() == "valueOf" && method.parameters().size == 1 &&
519                 method.parameters()[0].type().toTypeString() == JAVA_LANG_STRING)
520         ) {
521             // Skip the values() and valueOf(String) methods in enums: these are added by
522             // the compiler for enums anyway, but was part of the doclava1 signature files
523             // so inserted in compat mode.
524             return
525         }
526 
527         writer.println()
528         appendDocumentation(method, writer)
529 
530         // Need to filter out abstract from the modifiers list and turn it
531         // into a concrete method to make the stub compile
532         val removeAbstract = modifiers.isAbstract() && (isEnum || isAnnotation) || movedFromInterface
533 
534         appendModifiers(method, modifiers, removeAbstract, movedFromInterface)
535         generateTypeParameterList(typeList = method.typeParameterList(), addSpace = true)
536 
537         val returnType = method.returnType()
538         writer.print(
539             returnType?.toTypeString(
540                 outerAnnotations = false,
541                 innerAnnotations = generateAnnotations,
542                 filter = filterReference
543             )
544         )
545 
546         writer.print(' ')
547         writer.print(method.name())
548         generateParameterList(method)
549         generateThrowsList(method)
550 
551         if (isAnnotation) {
552             val default = method.defaultValue()
553             if (default.isNotEmpty()) {
554                 writer.print(" default ")
555                 writer.print(default)
556             }
557         }
558 
559         if (modifiers.isAbstract() && !removeAbstract && !isEnum || isAnnotation || modifiers.isNative()) {
560             writer.println(";")
561         } else {
562             writer.print(" { ")
563             writeThrowStub()
564             writer.println(" }")
565         }
566     }
567 
visitFieldnull568     override fun visitField(field: FieldItem) {
569         // Handled earlier in visitClass
570         if (field.isEnumConstant()) {
571             return
572         }
573 
574         if (field.containingClass().notStrippable) {
575             return
576         }
577 
578         writer.println()
579 
580         appendDocumentation(field, writer)
581         appendModifiers(field, false, false)
582         writer.print(
583             field.type().toTypeString(
584                 outerAnnotations = false,
585                 innerAnnotations = generateAnnotations,
586                 filter = filterReference
587             )
588         )
589         writer.print(' ')
590         writer.print(field.name())
591         val needsInitialization =
592             field.modifiers.isFinal() && field.initialValue(true) == null && field.containingClass().isClass()
593         field.writeValueWithSemicolon(
594             writer,
595             allowDefaultValue = !needsInitialization,
596             requireInitialValue = !needsInitialization
597         )
598         writer.print("\n")
599 
600         if (needsInitialization) {
601             if (field.modifiers.isStatic()) {
602                 writer.print("static ")
603             }
604             writer.print("{ ${field.name()} = ${field.type().defaultValueString()}; }\n")
605         }
606     }
607 
writeThrowStubnull608     private fun writeThrowStub() {
609         writer.write("throw new RuntimeException(\"Stub!\");")
610     }
611 
generateParameterListnull612     private fun generateParameterList(method: MethodItem) {
613         writer.print("(")
614         method.parameters().asSequence().forEachIndexed { i, parameter ->
615             if (i > 0) {
616                 writer.print(", ")
617             }
618             appendModifiers(parameter, false)
619             writer.print(
620                 parameter.type().toTypeString(
621                     outerAnnotations = false,
622                     innerAnnotations = generateAnnotations,
623                     filter = filterReference
624                 )
625             )
626             writer.print(' ')
627             val name = parameter.publicName() ?: parameter.name()
628             writer.print(name)
629         }
630         writer.print(")")
631     }
632 
generateThrowsListnull633     private fun generateThrowsList(method: MethodItem) {
634         // Note that throws types are already sorted internally to help comparison matching
635         val throws = if (preFiltered) {
636             method.throwsTypes().asSequence()
637         } else {
638             method.filteredThrowsTypes(filterReference).asSequence()
639         }
640         if (throws.any()) {
641             writer.print(" throws ")
642             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
643                 if (i > 0) {
644                     writer.print(", ")
645                 }
646                 // TODO: Shouldn't declare raw types here!
647                 writer.print(type.qualifiedName())
648             }
649         }
650     }
651 }
652