• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2019 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.doclava1.ApiPredicate
20 import com.android.tools.metalava.doclava1.FilterPredicate
21 import com.android.tools.metalava.doclava1.Issues
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.MethodItem
29 import com.android.tools.metalava.model.ModifierList
30 import com.android.tools.metalava.model.PackageItem
31 import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
32 import com.android.tools.metalava.model.psi.trimDocIndent
33 import com.android.tools.metalava.model.visitors.ApiVisitor
34 import com.android.tools.metalava.model.visitors.ItemVisitor
35 import com.android.tools.metalava.options
36 import com.android.tools.metalava.reporter
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 =
66         if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
67 
68     private val sourceList = StringBuilder(20000)
69 
70     /** Writes a source file list of the generated stubs */
writeSourceListnull71     fun writeSourceList(target: File, root: File?) {
72         target.parentFile?.mkdirs()
73         val contents = if (root != null) {
74             val path = root.path.replace('\\', '/') + "/"
75             sourceList.toString().replace(path, "")
76         } else {
77             sourceList.toString()
78         }
79         target.writeText(contents)
80     }
81 
startFilenull82     private fun startFile(sourceFile: File) {
83         if (sourceList.isNotEmpty()) {
84             sourceList.append(' ')
85         }
86         sourceList.append(sourceFile.path.replace('\\', '/'))
87     }
88 
visitPackagenull89     override fun visitPackage(pkg: PackageItem) {
90         getPackageDir(pkg, create = true)
91 
92         writePackageInfo(pkg)
93 
94         if (docStubs) {
95             codebase.getPackageDocs()?.let { packageDocs ->
96                 packageDocs.getOverviewDocumentation(pkg)?.let { writeDocOverview(pkg, it) }
97             }
98         }
99     }
100 
writeDocOverviewnull101     fun writeDocOverview(pkg: PackageItem, content: String) {
102         if (content.isBlank()) {
103             return
104         }
105 
106         val sourceFile = File(getPackageDir(pkg), "overview.html")
107         val overviewWriter = try {
108             PrintWriter(BufferedWriter(FileWriter(sourceFile)))
109         } catch (e: IOException) {
110             reporter.report(Issues.IO_ERROR, sourceFile, "Cannot open file for write.")
111             return
112         }
113 
114         // Should we include this in our stub list?
115         //     startFile(sourceFile)
116 
117         overviewWriter.println(content)
118         overviewWriter.flush()
119         overviewWriter.close()
120     }
121 
writePackageInfonull122     private fun writePackageInfo(pkg: PackageItem) {
123         val annotations = pkg.modifiers.annotations()
124         if (annotations.isNotEmpty() && generateAnnotations || !pkg.documentation.isBlank()) {
125             val sourceFile = File(getPackageDir(pkg), "package-info.java")
126             val packageInfoWriter = try {
127                 PrintWriter(BufferedWriter(FileWriter(sourceFile)))
128             } catch (e: IOException) {
129                 reporter.report(Issues.IO_ERROR, sourceFile, "Cannot open file for write.")
130                 return
131             }
132             startFile(sourceFile)
133 
134             appendDocumentation(pkg, packageInfoWriter)
135 
136             if (annotations.isNotEmpty()) {
137                 ModifierList.writeAnnotations(
138                     list = pkg.modifiers,
139                     separateLines = true,
140                     // Some bug in UAST triggers duplicate nullability annotations
141                     // here; make sure the are filtered out
142                     filterDuplicates = true,
143                     target = annotationTarget,
144                     writer = packageInfoWriter
145                 )
146             }
147             packageInfoWriter.println("package ${pkg.qualifiedName()};")
148 
149             packageInfoWriter.flush()
150             packageInfoWriter.close()
151         }
152     }
153 
getPackageDirnull154     private fun getPackageDir(packageItem: PackageItem, create: Boolean = true): File {
155         val relative = packageItem.qualifiedName().replace('.', File.separatorChar)
156         val dir = File(stubsDir, relative)
157         if (create && !dir.isDirectory) {
158             val ok = dir.mkdirs()
159             if (!ok) {
160                 throw IOException("Could not create $dir")
161             }
162         }
163 
164         return dir
165     }
166 
getClassFilenull167     private fun getClassFile(classItem: ClassItem): File {
168         assert(classItem.containingClass() == null) { "Should only be called on top level classes" }
169         // TODO: Look up compilation unit language
170         return File(getPackageDir(classItem.containingPackage()), "${classItem.simpleName()}.java")
171     }
172 
173     /**
174      * Between top level class files the [textWriter] field doesn't point to a real file; it
175      * points to this writer, which redirects to the error output. Nothing should be written
176      * to the writer at that time.
177      */
178     private var errorTextWriter = PrintWriter(options.stderr)
179 
180     /** The writer to write the stubs file to */
181     private var textWriter: PrintWriter = errorTextWriter
182 
183     private var stubWriter: ItemVisitor? = null
184 
visitClassnull185     override fun visitClass(cls: ClassItem) {
186         if (cls.isTopLevelClass()) {
187             val sourceFile = getClassFile(cls)
188             textWriter = try {
189                 PrintWriter(BufferedWriter(FileWriter(sourceFile)))
190             } catch (e: IOException) {
191                 reporter.report(Issues.IO_ERROR, sourceFile, "Cannot open file for write.")
192                 errorTextWriter
193             }
194 
195             startFile(sourceFile)
196 
197             stubWriter = JavaStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
198 
199             // Copyright statements from the original file?
200             val compilationUnit = cls.getCompilationUnit()
201             compilationUnit?.getHeaderComments()?.let { textWriter.println(it) }
202         }
203         stubWriter?.visitClass(cls)
204     }
205 
appendDocumentationnull206     private fun appendDocumentation(item: Item, writer: PrintWriter) {
207         if (options.includeDocumentationInStubs || docStubs) {
208             val documentation = if (docStubs && EXPAND_DOCUMENTATION) {
209                 item.fullyQualifiedDocumentation()
210             } else {
211                 item.documentation
212             }
213             if (documentation.isNotBlank()) {
214                 val trimmed = trimDocIndent(documentation)
215                 writer.println(trimmed)
216                 writer.println()
217             }
218         }
219     }
220 
afterVisitClassnull221     override fun afterVisitClass(cls: ClassItem) {
222         stubWriter?.afterVisitClass(cls)
223 
224         if (cls.isTopLevelClass()) {
225             textWriter.flush()
226             textWriter.close()
227             textWriter = errorTextWriter
228             stubWriter = null
229         }
230     }
231 
visitConstructornull232     override fun visitConstructor(constructor: ConstructorItem) {
233         stubWriter?.visitConstructor(constructor)
234     }
235 
afterVisitConstructornull236     override fun afterVisitConstructor(constructor: ConstructorItem) {
237         stubWriter?.afterVisitConstructor(constructor)
238     }
239 
visitMethodnull240     override fun visitMethod(method: MethodItem) {
241         stubWriter?.visitMethod(method)
242     }
243 
afterVisitMethodnull244     override fun afterVisitMethod(method: MethodItem) {
245         stubWriter?.afterVisitMethod(method)
246     }
247 
visitFieldnull248     override fun visitField(field: FieldItem) {
249         stubWriter?.visitField(field)
250     }
251 
afterVisitFieldnull252     override fun afterVisitField(field: FieldItem) {
253         stubWriter?.afterVisitField(field)
254     }
255 }