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