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.SdkConstants.AMP_ENTITY 20 import com.android.SdkConstants.APOS_ENTITY 21 import com.android.SdkConstants.ATTR_NAME 22 import com.android.SdkConstants.DOT_CLASS 23 import com.android.SdkConstants.DOT_JAR 24 import com.android.SdkConstants.DOT_XML 25 import com.android.SdkConstants.DOT_ZIP 26 import com.android.SdkConstants.GT_ENTITY 27 import com.android.SdkConstants.LT_ENTITY 28 import com.android.SdkConstants.QUOT_ENTITY 29 import com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE 30 import com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE 31 import com.android.tools.lint.annotations.Extractor.ANDROID_INT_DEF 32 import com.android.tools.lint.annotations.Extractor.ANDROID_NOTNULL 33 import com.android.tools.lint.annotations.Extractor.ANDROID_NULLABLE 34 import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF 35 import com.android.tools.lint.annotations.Extractor.ATTR_PURE 36 import com.android.tools.lint.annotations.Extractor.ATTR_VAL 37 import com.android.tools.lint.annotations.Extractor.IDEA_CONTRACT 38 import com.android.tools.lint.annotations.Extractor.IDEA_MAGIC 39 import com.android.tools.lint.annotations.Extractor.IDEA_NOTNULL 40 import com.android.tools.lint.annotations.Extractor.IDEA_NULLABLE 41 import com.android.tools.lint.annotations.Extractor.SUPPORT_NOTNULL 42 import com.android.tools.lint.annotations.Extractor.SUPPORT_NULLABLE 43 import com.android.tools.lint.detector.api.getChildren 44 import com.android.tools.metalava.cli.common.cliError 45 import com.android.tools.metalava.model.ANDROIDX_INT_DEF 46 import com.android.tools.metalava.model.ANDROIDX_NONNULL 47 import com.android.tools.metalava.model.ANDROIDX_NULLABLE 48 import com.android.tools.metalava.model.ANDROIDX_STRING_DEF 49 import com.android.tools.metalava.model.ANDROID_FLAGGED_API 50 import com.android.tools.metalava.model.ANNOTATION_VALUE_TRUE 51 import com.android.tools.metalava.model.AnnotationAttribute 52 import com.android.tools.metalava.model.AnnotationItem 53 import com.android.tools.metalava.model.ClassItem 54 import com.android.tools.metalava.model.Codebase 55 import com.android.tools.metalava.model.DefaultAnnotationAttribute 56 import com.android.tools.metalava.model.DefaultAnnotationItem 57 import com.android.tools.metalava.model.Item 58 import com.android.tools.metalava.model.PackageItem 59 import com.android.tools.metalava.model.SelectableItem 60 import com.android.tools.metalava.model.TraversingVisitor 61 import com.android.tools.metalava.model.TypeNullability 62 import com.android.tools.metalava.model.source.SourceParser 63 import com.android.tools.metalava.model.source.SourceSet 64 import com.android.tools.metalava.model.text.ApiFile 65 import com.android.tools.metalava.model.text.ApiParseException 66 import com.android.tools.metalava.model.text.SignatureFile 67 import com.android.tools.metalava.model.typeNullability 68 import com.android.tools.metalava.model.visitors.ApiVisitor 69 import com.android.tools.metalava.reporter.Issues 70 import com.android.tools.metalava.reporter.Reporter 71 import com.android.tools.metalava.xml.parseDocument 72 import com.google.common.io.Closeables 73 import java.io.File 74 import java.io.FileInputStream 75 import java.io.IOException 76 import java.lang.reflect.Field 77 import java.util.jar.JarInputStream 78 import java.util.regex.Pattern 79 import java.util.zip.ZipEntry 80 import kotlin.text.Charsets.UTF_8 81 import org.w3c.dom.Document 82 import org.w3c.dom.Element 83 import org.xml.sax.SAXParseException 84 85 /** Merges annotations into classes already registered in the given [Codebase] */ 86 @Suppress("DEPRECATION") 87 class AnnotationsMerger( 88 private val sourceParser: SourceParser, 89 private val codebase: Codebase, 90 private val reporter: Reporter, 91 ) { 92 93 /** Merge annotations which will appear in the output API. */ 94 fun mergeQualifierAnnotationsFromFiles(files: List<File>) { 95 mergeAll( 96 files, 97 ::mergeQualifierAnnotationsFromFile, 98 ::mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase 99 ) 100 } 101 102 /** Merge annotations which control what is included in the output API. */ 103 fun mergeInclusionAnnotationsFromFiles(files: List<File>) { 104 mergeAll( 105 files, 106 { cliError("External inclusion annotations files must be .java, found ${it.path}") }, 107 ::mergeInclusionAnnotationsFromCodebase 108 ) 109 } 110 111 /** 112 * Given a list of directories containing various files, scan those files merging them into the 113 * [codebase]. 114 * 115 * All `.java` files are collated and 116 */ 117 private fun mergeAll( 118 mergeAnnotations: List<File>, 119 mergeFile: (File) -> Unit, 120 mergeJavaStubsCodebase: (Codebase) -> Unit 121 ) { 122 // Process each file (which are almost certainly directories) separately. That allows for a 123 // single Java class to merge in annotations from multiple separate files. 124 mergeAnnotations.forEach { 125 val javaStubFiles = mutableListOf<File>() 126 mergeFileOrDir(it, mergeFile, javaStubFiles) 127 if (javaStubFiles.isNotEmpty()) { 128 // Set up class path to contain our main sources such that we can 129 // resolve types in the stubs 130 val roots = 131 SourceSet(options.sources, options.sourcePath).extractRoots(reporter).sourcePath 132 val javaStubsCodebase = 133 sourceParser.parseSources( 134 SourceSet(javaStubFiles, roots), 135 "Codebase loaded from stubs", 136 classPath = options.classpath, 137 apiPackages = options.apiPackages, 138 projectDescription = null, 139 ) 140 mergeJavaStubsCodebase(javaStubsCodebase) 141 } 142 } 143 } 144 145 /** 146 * Merges annotations from `file`, or from all the files under it if `file` is a directory. All 147 * files apart from Java stub files are merged using [mergeFile]. Java stub files are not merged 148 * by this method, instead they are added to [javaStubFiles] and should be merged later (so that 149 * all the Java stubs can be loaded as a single codebase). 150 */ 151 private fun mergeFileOrDir( 152 file: File, 153 mergeFile: (File) -> Unit, 154 javaStubFiles: MutableList<File> 155 ) { 156 if (file.isDirectory) { 157 val files = file.listFiles() 158 if (files != null) { 159 for (child in files) { 160 mergeFileOrDir(child, mergeFile, javaStubFiles) 161 } 162 } 163 } else if (file.isFile) { 164 if (file.path.endsWith(".java")) { 165 javaStubFiles.add(file) 166 } else { 167 mergeFile(file) 168 } 169 } 170 } 171 172 private fun mergeQualifierAnnotationsFromFile(file: File) { 173 if (file.path.endsWith(DOT_JAR) || file.path.endsWith(DOT_ZIP)) { 174 mergeFromJar(file) 175 } else if (file.path.endsWith(DOT_XML)) { 176 try { 177 val xml = file.readText() 178 mergeQualifierAnnotationsFromXml(file.path, xml) 179 } catch (e: IOException) { 180 error("I/O problem during transform: $e") 181 } 182 } else if ( 183 file.path.endsWith(".txt") || 184 file.path.endsWith(".signatures") || 185 file.path.endsWith(".api") 186 ) { 187 try { 188 // .txt: Old style signature files 189 // Others: new signature files (e.g. kotlin-style nullness info) 190 mergeQualifierAnnotationsFromSignatureFile(file) 191 } catch (e: IOException) { 192 error("I/O problem during transform: $e") 193 } 194 } 195 } 196 197 private fun mergeFromJar(jar: File) { 198 // Reads in an existing annotations jar and merges in entries found there 199 // with the annotations analyzed from source. 200 var zis: JarInputStream? = null 201 try { 202 val fis = FileInputStream(jar) 203 zis = JarInputStream(fis) 204 var entry: ZipEntry? = zis.nextEntry 205 while (entry != null) { 206 if (entry.name.endsWith(".xml")) { 207 val bytes = zis.readBytes() 208 val xml = String(bytes, UTF_8) 209 mergeQualifierAnnotationsFromXml(jar.path + ": " + entry, xml) 210 } 211 entry = zis.nextEntry 212 } 213 } catch (e: IOException) { 214 error("I/O problem during transform: $e") 215 } finally { 216 try { 217 Closeables.close(zis, true /* swallowIOException */) 218 } catch (e: IOException) { 219 // cannot happen 220 } 221 } 222 } 223 224 private fun mergeQualifierAnnotationsFromXml(path: String, xml: String) { 225 try { 226 val document = parseDocument(xml, false) 227 mergeDocument(document) 228 } catch (e: Exception) { 229 var message = "Failed to merge $path: $e" 230 if (e is SAXParseException) { 231 message = "Line ${e.lineNumber}:${e.columnNumber}: $message" 232 } 233 if (e !is IOException) { 234 message += "\n" + e.stackTraceToString().prependIndent(" ") 235 } 236 error(message) 237 } 238 } 239 240 private fun mergeQualifierAnnotationsFromSignatureFile(file: File) { 241 try { 242 val signatureCodebase = 243 ApiFile.parseApi( 244 SignatureFile.fromFiles(file), 245 codebase.config, 246 "Signature files for annotation merger: loaded from $file" 247 ) 248 mergeQualifierAnnotationsFromCodebase(signatureCodebase) 249 } catch (ex: ApiParseException) { 250 val message = "Unable to parse signature file $file: ${ex.message}" 251 cliError(message) 252 } 253 } 254 255 private fun mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase( 256 javaStubsCodebase: Codebase 257 ) { 258 mergeQualifierAnnotationsFromCodebase(javaStubsCodebase) 259 if (options.validateNullabilityFromMergedStubs) { 260 options.nullabilityAnnotationsValidator?.validateAll( 261 codebase, 262 javaStubsCodebase.getTopLevelClassesFromSource().map(ClassItem::qualifiedName) 263 ) 264 } 265 } 266 267 private fun mergeQualifierAnnotationsFromCodebase(externalCodebase: Codebase) { 268 val visitor = 269 object : ComparisonVisitor() { 270 override fun compareItems(old: Item, new: Item) { 271 val itemAnnotations = old.modifiers.annotations() 272 mergeQualifierAnnotations(itemAnnotations, new) 273 old.type()?.let { 274 // Ignore type annotations that are duplicates of the item's annotations. 275 val typeAnnotations = 276 it.modifiers.annotations.filter { it !in itemAnnotations } 277 mergeQualifierAnnotations(typeAnnotations, new) 278 } 279 } 280 281 override fun removedItem(old: SelectableItem, from: SelectableItem?) { 282 // Do not report missing items if there are no annotations to copy. 283 if (old.modifiers.annotations().isEmpty()) { 284 old.type()?.let { typeItem -> 285 if (typeItem.modifiers.annotations.isEmpty()) return 286 } 287 ?: return 288 } 289 290 reporter.report( 291 Issues.UNMATCHED_MERGE_ANNOTATION, 292 old, 293 "qualifier annotations were given for $old but no matching item was found" 294 ) 295 } 296 } 297 298 CodebaseComparator().compare(visitor, externalCodebase, codebase) 299 } 300 301 private fun mergeInclusionAnnotationsFromCodebase(externalCodebase: Codebase) { 302 val visitor = 303 object : TraversingVisitor() { 304 override fun visitItem(item: Item): TraversalAction { 305 // Find any show/hide annotations or FlaggedApi annotations to merge from the 306 // external to the main codebase. If there are none to copy then return. 307 val annotationsToMerge = 308 item.modifiers.annotations().filter { annotation -> 309 val qualifiedName = annotation.qualifiedName 310 annotation.isShowabilityAnnotation() || 311 qualifiedName == ANDROID_FLAGGED_API 312 } 313 if (annotationsToMerge.isEmpty()) { 314 // Just because there are no annotations on an [Item] does not mean that 315 // there will not be on the children so make sure to visit them as normal. 316 return TraversalAction.CONTINUE 317 } 318 319 // Find the item to which the annotations should be copied, reporting an issue 320 // if it could not be found. 321 val mainItem = 322 item.findCorrespondingItemIn(codebase) 323 ?: run { 324 reporter.report( 325 Issues.UNMATCHED_MERGE_ANNOTATION, 326 item, 327 "inclusion annotations were given for $item but no matching item was found" 328 ) 329 330 // If an [Item] cannot be found then there is no point in visiting 331 // its children as they will not be found either. 332 return TraversalAction.SKIP_CHILDREN 333 } 334 335 // Merge the annotations to the main item, ignoring any that match, i.e. are of 336 // the same type as, an existing annotation. 337 mainItem.mutateModifiers { 338 mutateAnnotations { 339 for (annotation in annotationsToMerge) { 340 val qualifiedName = annotation.qualifiedName 341 if (none { it.qualifiedName == qualifiedName }) { 342 // TODO: This simply uses the AnnotationItem from the Codebase 343 // being merged from in the Codebase being merged into. That is 344 // not safe as the Codebases may be from different models. 345 add(annotation) 346 } 347 } 348 } 349 } 350 351 return TraversalAction.CONTINUE 352 } 353 } 354 externalCodebase.accept(visitor) 355 } 356 357 internal fun error(message: String) { 358 reporter.report(Issues.INTERNAL_ERROR, reportable = null, message) 359 } 360 361 internal fun warning(message: String) { 362 if (options.verbose) { 363 options.stdout.println("Warning: $message") 364 } 365 } 366 367 @Suppress("PrivatePropertyName") 368 private val XML_SIGNATURE: Pattern = 369 Pattern.compile( 370 // Class (FieldName | Type? Name(ArgList) Argnum?) 371 // "(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)"); 372 "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)" 373 ) 374 375 private fun mergeDocument(document: Document) { 376 val root = document.documentElement 377 val rootTag = root.tagName 378 assert(rootTag == "root") { rootTag } 379 380 for (item in getChildren(root)) { 381 var signature: String? = item.getAttribute(ATTR_NAME) 382 if (signature == null || signature == "null") { 383 continue // malformed item 384 } 385 386 signature = unescapeXml(signature) 387 388 val matcher = XML_SIGNATURE.matcher(signature) 389 if (matcher.matches()) { 390 val containingClass = matcher.group(1) 391 if (containingClass == null) { 392 warning("Could not find class for $signature") 393 continue 394 } 395 396 val classItem = codebase.findClass(containingClass) 397 if (classItem == null) { 398 // Well known exceptions from IntelliJ's external annotations 399 // we won't complain loudly about 400 if (wellKnownIgnoredImport(containingClass)) { 401 continue 402 } 403 404 warning("Could not find class $containingClass; omitting annotation from merge") 405 continue 406 } 407 408 val methodName = matcher.group(5) 409 if (methodName != null) { 410 val parameters = matcher.group(6) 411 val parameterIndex = 412 if (matcher.group(7) != null) { 413 Integer.parseInt(matcher.group(7).trim()) 414 } else { 415 -1 416 } 417 mergeMethodOrParameter( 418 item, 419 containingClass, 420 classItem, 421 methodName, 422 parameterIndex, 423 parameters 424 ) 425 } else { 426 val fieldName = matcher.group(2) 427 mergeField(item, containingClass, classItem, fieldName) 428 } 429 } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) { 430 // Must be just a class 431 val containingClass = signature 432 val classItem = codebase.findClass(containingClass) 433 if (classItem == null) { 434 if (wellKnownIgnoredImport(containingClass)) { 435 continue 436 } 437 438 warning("Could not find class $containingClass; omitting annotation from merge") 439 continue 440 } 441 442 mergeQualifierAnnotationsFromXmlElement(item, classItem) 443 } else { 444 warning("No merge match for signature $signature") 445 } 446 } 447 } 448 449 private fun wellKnownIgnoredImport(containingClass: String): Boolean { 450 if ( 451 containingClass.startsWith("javax.swing.") || 452 containingClass.startsWith("javax.naming.") || 453 containingClass.startsWith("java.awt.") || 454 containingClass.startsWith("org.jdom.") 455 ) { 456 return true 457 } 458 return false 459 } 460 461 // The parameter declaration used in XML files should not have duplicated spaces, 462 // and there should be no space after commas (we can't however strip out all spaces, 463 // since for example the spaces around the "extends" keyword needs to be there in 464 // types like Map<String,? extends Number> 465 private fun fixParameterString(parameters: String): String { 466 return parameters 467 .replace(" ", " ") 468 .replace(", ", ",") 469 .replace("?super", "? super ") 470 .replace("?extends", "? extends ") 471 } 472 473 private fun mergeMethodOrParameter( 474 item: Element, 475 containingClass: String, 476 classItem: ClassItem, 477 methodName: String, 478 parameterIndex: Int, 479 parameters: String 480 ) { 481 @Suppress("NAME_SHADOWING") val parameters = fixParameterString(parameters) 482 483 val callableItem = classItem.findCallable(methodName, parameters) 484 if (callableItem == null) { 485 if (wellKnownIgnoredImport(containingClass)) { 486 return 487 } 488 489 warning( 490 "Could not find method $methodName($parameters) in $containingClass; omitting annotation from merge" 491 ) 492 return 493 } 494 495 if (parameterIndex != -1) { 496 val parameterItem = callableItem.parameters()[parameterIndex] 497 mergeQualifierAnnotationsFromXmlElement(item, parameterItem) 498 } else { 499 // Annotation on the method itself 500 mergeQualifierAnnotationsFromXmlElement(item, callableItem) 501 } 502 } 503 504 private fun mergeField( 505 item: Element, 506 containingClass: String, 507 classItem: ClassItem, 508 fieldName: String 509 ) { 510 val fieldItem = classItem.findField(fieldName) 511 if (fieldItem == null) { 512 if (wellKnownIgnoredImport(containingClass)) { 513 return 514 } 515 516 warning( 517 "Could not find field $fieldName in $containingClass; omitting annotation from merge" 518 ) 519 return 520 } 521 522 mergeQualifierAnnotationsFromXmlElement(item, fieldItem) 523 } 524 525 private fun getAnnotationName(element: Element): String { 526 val tagName = element.tagName 527 assert(tagName == "annotation") { tagName } 528 529 val qualifiedName = element.getAttribute(ATTR_NAME) 530 assert(qualifiedName.isNotEmpty()) 531 return qualifiedName 532 } 533 534 private fun mergeQualifierAnnotationsFromXmlElement(xmlElement: Element, item: Item) { 535 val annotationsToMerge = 536 getChildren(xmlElement).mapNotNull { annotationElement -> 537 createAnnotation(annotationElement) 538 } 539 540 mergeQualifierAnnotations(annotationsToMerge, item) 541 } 542 543 /** 544 * Reads in annotation data from an XML item (using IntelliJ IDE's external annotations XML 545 * format) and creates a corresponding [AnnotationItem], performing some "translations" in the 546 * process (e.g. mapping from IntelliJ annotations like `org.jetbrains.annotations.Nullable` to 547 * `androidx.annotation.Nullable`. 548 */ 549 private fun createAnnotation(annotationElement: Element): AnnotationItem? { 550 val tagName = annotationElement.tagName 551 assert(tagName == "annotation") { tagName } 552 val name = annotationElement.getAttribute(ATTR_NAME) 553 assert(name.isNotEmpty()) 554 when { 555 name == "org.jetbrains.annotations.Range" -> { 556 val children = getChildren(annotationElement) 557 assert(children.size == 2) { children.size } 558 val valueElement1 = children[0] 559 val valueElement2 = children[1] 560 val valName1 = valueElement1.getAttribute(ATTR_NAME) 561 val value1 = valueElement1.getAttribute(ATTR_VAL) 562 val valName2 = valueElement2.getAttribute(ATTR_NAME) 563 val value2 = valueElement2.getAttribute(ATTR_VAL) 564 return DefaultAnnotationItem.create( 565 codebase, 566 "androidx.annotation.IntRange", 567 listOf( 568 // Add "L" suffix to ensure that we don't for example interpret "-1" as 569 // an integer -1 and then end up recording it as "ffffffff" instead of 570 // -1L 571 DefaultAnnotationAttribute.create( 572 valName1, 573 value1 + (if (value1.last().isDigit()) "L" else "") 574 ), 575 DefaultAnnotationAttribute.create( 576 valName2, 577 value2 + (if (value2.last().isDigit()) "L" else "") 578 ) 579 ), 580 ) 581 } 582 name == IDEA_MAGIC -> { 583 val children = getChildren(annotationElement) 584 assert(children.size == 1) { children.size } 585 val valueElement = children[0] 586 val valName = valueElement.getAttribute(ATTR_NAME) 587 var value = valueElement.getAttribute(ATTR_VAL) 588 val flagsFromClass = valName == "flagsFromClass" 589 val flag = valName == "flags" || flagsFromClass 590 if (valName == "valuesFromClass" || flagsFromClass) { 591 // Not supported 592 var found = false 593 if (value.endsWith(DOT_CLASS)) { 594 val clsName = value.substring(0, value.length - DOT_CLASS.length) 595 val sb = StringBuilder() 596 sb.append('{') 597 598 var reflectionFields: Array<Field>? = null 599 try { 600 val cls = Class.forName(clsName) 601 reflectionFields = cls.declaredFields 602 } catch (ignore: Exception) { 603 // Class not available: not a problem. We'll rely on API filter. 604 // It's mainly used for sorting anyway. 605 } 606 607 // Attempt to sort in reflection order 608 if (!found && reflectionFields != null) { 609 val filterEmit = 610 ApiVisitor.defaultEmitFilter( 611 @Suppress("DEPRECATION") options.apiPredicateConfig, 612 ) 613 614 // Attempt with reflection 615 var first = true 616 for (field in reflectionFields) { 617 if ( 618 field.type == Integer.TYPE || 619 field.type == Int::class.javaPrimitiveType 620 ) { 621 // Make sure this field is included in our API too 622 val fieldItem = 623 codebase.findClass(clsName)?.findField(field.name) 624 if (fieldItem == null || !filterEmit.test(fieldItem)) { 625 continue 626 } 627 628 if (first) { 629 first = false 630 } else { 631 sb.append(',').append(' ') 632 } 633 sb.append(clsName).append('.').append(field.name) 634 } 635 } 636 } 637 sb.append('}') 638 value = sb.toString() 639 if (sb.length > 2) { // 2: { } 640 found = true 641 } 642 } 643 644 if (!found) { 645 return null 646 } 647 } 648 649 val attributes = mutableListOf<AnnotationAttribute>() 650 attributes.add(DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value)) 651 if (flag) { 652 attributes.add( 653 DefaultAnnotationAttribute.create( 654 TYPE_DEF_FLAG_ATTRIBUTE, 655 ANNOTATION_VALUE_TRUE 656 ) 657 ) 658 } 659 return DefaultAnnotationItem.create( 660 codebase, 661 if (valName == "stringValues") ANDROIDX_STRING_DEF else ANDROIDX_INT_DEF, 662 attributes, 663 ) 664 } 665 name == ANDROIDX_STRING_DEF || 666 name == ANDROID_STRING_DEF || 667 name == ANDROIDX_INT_DEF || 668 name == ANDROID_INT_DEF -> { 669 val attributes = mutableListOf<AnnotationAttribute>() 670 val parseChild: (Element) -> Unit = { child: Element -> 671 val elementName = child.getAttribute(ATTR_NAME) 672 val value = child.getAttribute(ATTR_VAL) 673 when (elementName) { 674 TYPE_DEF_VALUE_ATTRIBUTE -> { 675 attributes.add( 676 DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value) 677 ) 678 } 679 TYPE_DEF_FLAG_ATTRIBUTE -> { 680 if (ANNOTATION_VALUE_TRUE == value) { 681 attributes.add( 682 DefaultAnnotationAttribute.create( 683 TYPE_DEF_FLAG_ATTRIBUTE, 684 ANNOTATION_VALUE_TRUE 685 ) 686 ) 687 } 688 } 689 else -> { 690 error("Unrecognized element: " + elementName) 691 } 692 } 693 } 694 val children = getChildren(annotationElement) 695 parseChild(children[0]) 696 if (children.size == 2) { 697 parseChild(children[1]) 698 } 699 val intDef = ANDROIDX_INT_DEF == name || ANDROID_INT_DEF == name 700 return DefaultAnnotationItem.create( 701 codebase, 702 if (intDef) ANDROIDX_INT_DEF else ANDROIDX_STRING_DEF, 703 attributes, 704 ) 705 } 706 name == IDEA_CONTRACT -> { 707 val children = getChildren(annotationElement) 708 val valueElement = children[0] 709 val value = valueElement.getAttribute(ATTR_VAL) 710 val pure = valueElement.getAttribute(ATTR_PURE) 711 return if (pure != null && pure.isNotEmpty()) { 712 DefaultAnnotationItem.create( 713 codebase, 714 name, 715 listOf( 716 DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value), 717 DefaultAnnotationAttribute.create(ATTR_PURE, pure) 718 ), 719 ) 720 } else { 721 DefaultAnnotationItem.create( 722 codebase, 723 name, 724 listOf(DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value)), 725 ) 726 } 727 } 728 isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NONNULL") 729 isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE") 730 else -> { 731 val children = getChildren(annotationElement) 732 if (children.isEmpty()) { 733 return codebase.createAnnotation("@$name") 734 } 735 val attributes = mutableListOf<AnnotationAttribute>() 736 for (valueElement in children) { 737 attributes.add( 738 DefaultAnnotationAttribute.create( 739 valueElement.getAttribute(ATTR_NAME) ?: continue, 740 valueElement.getAttribute(ATTR_VAL) ?: continue 741 ) 742 ) 743 } 744 return DefaultAnnotationItem.create(codebase, name, attributes) 745 } 746 } 747 } 748 749 private fun isNonNull(name: String): Boolean { 750 return name == IDEA_NOTNULL || 751 name == ANDROID_NOTNULL || 752 name == ANDROIDX_NONNULL || 753 name == SUPPORT_NOTNULL 754 } 755 756 private fun isNullable(name: String): Boolean { 757 return name == IDEA_NULLABLE || 758 name == ANDROID_NULLABLE || 759 name == ANDROIDX_NULLABLE || 760 name == SUPPORT_NULLABLE 761 } 762 763 /** Merge qualifier annotations in [annotations] into the [Item.modifiers] of [item]. */ 764 private fun mergeQualifierAnnotations(annotations: List<AnnotationItem>, item: Item) { 765 if (annotations.isEmpty()) return 766 767 // Check to make sure that the annotations are not adding a conflicting type nullability. 768 val nullabilityBefore = item.modifiers.annotations().typeNullability 769 if (nullabilityBefore != null) { 770 val mergeNullability = annotations.typeNullability 771 if (mergeNullability != null && mergeNullability != nullabilityBefore) { 772 when (nullabilityBefore) { 773 TypeNullability.NULLABLE -> 774 reporter.report( 775 Issues.INCONSISTENT_MERGE_ANNOTATION, 776 item, 777 "Merge conflict, has @Nullable (or equivalent) attempting to merge" + 778 " @NonNull (or equivalent)" 779 ) 780 TypeNullability.NONNULL -> 781 reporter.report( 782 Issues.INCONSISTENT_MERGE_ANNOTATION, 783 item, 784 "Merge conflict, has @NonNull (or equivalent) attempting to merge" + 785 " @Nullable (or equivalent)" 786 ) 787 else -> {} 788 } 789 } 790 } 791 792 item.mutateModifiers { 793 mutateAnnotations { 794 for (annotation in annotations) { 795 // If the item already has nullness annotations then ignore any others. 796 if (nullabilityBefore != null && annotation.isNullnessAnnotation()) continue 797 798 // Ignore annotation that has the same type as an existing annotation. 799 val qualifiedName = annotation.qualifiedName 800 if (any { it.qualifiedName == qualifiedName }) continue 801 802 val annotationToMerge = 803 item.codebase.createAnnotation( 804 annotation.toSource(showDefaultAttrs = false), 805 item, 806 ) 807 ?: continue 808 809 add(annotationToMerge) 810 } 811 } 812 } 813 814 // Update the type nullability from the annotations, if necessary. 815 // 816 // Nullability annotations do not make sense on class definitions or in package-info.java 817 // files and in fact many nullability annotations do not support targeting them at all. Some 818 // nullability checkers do support annotating packages and classes with annotations to set 819 // the default nullability for unannotated types but Metalava does not currently support 820 // them. If it did then they would need special treatment here anyway so, for now we just 821 // ignore them. 822 if (item is ClassItem || item is PackageItem) return 823 824 // Check to make sure that the item has a type. 825 val typeItem = item.type() ?: return 826 827 // If the nullability after is null then nullability annotations cannot have been added so 828 // there is nothing to check. 829 val nullabilityAfter = item.modifiers.annotations().typeNullability ?: return 830 831 // If the type nullability has changed then update the type nullability to match. 832 if (nullabilityAfter != nullabilityBefore) { 833 // Finally, duplicate the type with the new nullability. 834 item.setType(typeItem.substitute(nullabilityAfter)) 835 } 836 } 837 838 private fun unescapeXml(escaped: String): String { 839 var workingString = escaped.replace(QUOT_ENTITY, "\"") 840 workingString = workingString.replace(LT_ENTITY, "<") 841 workingString = workingString.replace(GT_ENTITY, ">") 842 workingString = workingString.replace(APOS_ENTITY, "'") 843 workingString = workingString.replace(AMP_ENTITY, "&") 844 845 return workingString 846 } 847 } 848