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