• 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.ApiPredicate
20 import com.android.tools.metalava.FilterPredicate
21 import com.android.tools.metalava.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.trimDocIndent
32 import com.android.tools.metalava.model.visitors.ApiVisitor
33 import com.android.tools.metalava.model.visitors.ItemVisitor
34 import com.android.tools.metalava.options
35 import com.android.tools.metalava.reporter
36 import java.io.BufferedWriter
37 import java.io.File
38 import java.io.FileWriter
39 import java.io.IOException
40 import java.io.PrintWriter
41 
42 class StubWriter(
43     private val codebase: Codebase,
44     private val stubsDir: File,
45     private val generateAnnotations: Boolean = false,
46     private val preFiltered: Boolean = true,
47     private val docStubs: Boolean
48 ) : ApiVisitor(
49     visitConstructorsAsMethods = false,
50     nestInnerClasses = true,
51     inlineInheritedFields = true,
52     fieldComparator = FieldItem.comparator,
53     // Methods are by default sorted in source order in stubs, to encourage methods
54     // that are near each other in the source to show up near each other in the documentation
55     methodComparator = MethodItem.sourceOrderComparator,
56     filterEmit = FilterPredicate(ApiPredicate(ignoreShown = true, includeDocOnly = docStubs)),
57     filterReference = ApiPredicate(ignoreShown = true, includeDocOnly = docStubs),
58     includeEmptyOuterClasses = true
59 ) {
60     private val annotationTarget =
61         if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
62 
63     private val sourceList = StringBuilder(20000)
64 
65     /** Writes a source file list of the generated stubs */
66     fun writeSourceList(target: File, root: File?) {
67         target.parentFile?.mkdirs()
68         val contents = if (root != null) {
69             val path = root.path.replace('\\', '/') + "/"
70             sourceList.toString().replace(path, "")
71         } else {
72             sourceList.toString()
73         }
74         target.writeText(contents)
75     }
76 
77     private fun startFile(sourceFile: File) {
78         if (sourceList.isNotEmpty()) {
79             sourceList.append(' ')
80         }
81         sourceList.append(sourceFile.path.replace('\\', '/'))
82     }
83 
84     override fun visitPackage(pkg: PackageItem) {
85         getPackageDir(pkg, create = true)
86 
87         writePackageInfo(pkg)
88 
89         if (docStubs) {
90             codebase.getPackageDocs()?.let { packageDocs ->
91                 packageDocs.getOverviewDocumentation(pkg)?.let { writeDocOverview(pkg, it) }
92             }
93         }
94     }
95 
96     fun writeDocOverview(pkg: PackageItem, content: String) {
97         if (content.isBlank()) {
98             return
99         }
100 
101         val sourceFile = File(getPackageDir(pkg), "overview.html")
102         val overviewWriter = try {
103             PrintWriter(BufferedWriter(FileWriter(sourceFile)))
104         } catch (e: IOException) {
105             reporter.report(Issues.IO_ERROR, sourceFile, "Cannot open file for write.")
106             return
107         }
108 
109         // Should we include this in our stub list?
110         //     startFile(sourceFile)
111 
112         overviewWriter.println(content)
113         overviewWriter.flush()
114         overviewWriter.close()
115     }
116 
117     private fun writePackageInfo(pkg: PackageItem) {
118         val annotations = pkg.modifiers.annotations()
119         if (annotations.isNotEmpty() && generateAnnotations || !pkg.documentation.isBlank()) {
120             val sourceFile = File(getPackageDir(pkg), "package-info.java")
121             val packageInfoWriter = try {
122                 PrintWriter(BufferedWriter(FileWriter(sourceFile)))
123             } catch (e: IOException) {
124                 reporter.report(Issues.IO_ERROR, sourceFile, "Cannot open file for write.")
125                 return
126             }
127             startFile(sourceFile)
128 
129             appendDocumentation(pkg, packageInfoWriter, docStubs)
130 
131             if (annotations.isNotEmpty()) {
132                 ModifierList.writeAnnotations(
133                     list = pkg.modifiers,
134                     separateLines = true,
135                     // Some bug in UAST triggers duplicate nullability annotations
136                     // here; make sure the are filtered out
137                     filterDuplicates = true,
138                     target = annotationTarget,
139                     writer = packageInfoWriter
140                 )
141             }
142             packageInfoWriter.println("package ${pkg.qualifiedName()};")
143 
144             packageInfoWriter.flush()
145             packageInfoWriter.close()
146         }
147     }
148 
149     private fun getPackageDir(packageItem: PackageItem, create: Boolean = true): File {
150         val relative = packageItem.qualifiedName().replace('.', File.separatorChar)
151         val dir = File(stubsDir, relative)
152         if (create && !dir.isDirectory) {
153             val ok = dir.mkdirs()
154             if (!ok) {
155                 throw IOException("Could not create $dir")
156             }
157         }
158 
159         return dir
160     }
161 
162     private fun getClassFile(classItem: ClassItem): File {
163         assert(classItem.containingClass() == null) { "Should only be called on top level classes" }
164         val packageDir = getPackageDir(classItem.containingPackage())
165 
166         return if (classItem.isKotlin() && options.kotlinStubs) {
167             File(packageDir, "${classItem.simpleName()}.kt")
168         } else {
169             File(packageDir, "${classItem.simpleName()}.java")
170         }
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 
185     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 = if (options.kotlinStubs && cls.isKotlin()) {
198                 KotlinStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
199             } else {
200                 JavaStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
201             }
202 
203             // Copyright statements from the original file?
204             val compilationUnit = cls.getCompilationUnit()
205             compilationUnit?.getHeaderComments()?.let { textWriter.println(it) }
206         }
207         stubWriter?.visitClass(cls)
208     }
209 
210     override fun afterVisitClass(cls: ClassItem) {
211         stubWriter?.afterVisitClass(cls)
212 
213         if (cls.isTopLevelClass()) {
214             textWriter.flush()
215             textWriter.close()
216             textWriter = errorTextWriter
217             stubWriter = null
218         }
219     }
220 
221     override fun visitConstructor(constructor: ConstructorItem) {
222         stubWriter?.visitConstructor(constructor)
223     }
224 
225     override fun afterVisitConstructor(constructor: ConstructorItem) {
226         stubWriter?.afterVisitConstructor(constructor)
227     }
228 
229     override fun visitMethod(method: MethodItem) {
230         stubWriter?.visitMethod(method)
231     }
232 
233     override fun afterVisitMethod(method: MethodItem) {
234         stubWriter?.afterVisitMethod(method)
235     }
236 
237     override fun visitField(field: FieldItem) {
238         stubWriter?.visitField(field)
239     }
240 
241     override fun afterVisitField(field: FieldItem) {
242         stubWriter?.afterVisitField(field)
243     }
244 }
245 
appendDocumentationnull246 internal fun appendDocumentation(
247     item: Item,
248     writer: PrintWriter,
249     docStubs: Boolean
250 ) {
251     if (options.includeDocumentationInStubs || docStubs) {
252         val documentation = item.fullyQualifiedDocumentation()
253         if (documentation.isNotBlank()) {
254             val trimmed = trimDocIndent(documentation)
255             writer.println(trimmed)
256             writer.println()
257         }
258     }
259 }