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 }