1 /* 2 * 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.checks.AnnotationDetector 47 import com.android.tools.lint.detector.api.getChildren 48 import com.android.tools.metalava.doclava1.ApiFile 49 import com.android.tools.metalava.doclava1.ApiParseException 50 import com.android.tools.metalava.doclava1.ApiPredicate 51 import com.android.tools.metalava.model.AnnotationAttribute 52 import com.android.tools.metalava.model.AnnotationAttributeValue 53 import com.android.tools.metalava.model.AnnotationItem 54 import com.android.tools.metalava.model.AnnotationTarget 55 import com.android.tools.metalava.model.ClassItem 56 import com.android.tools.metalava.model.Codebase 57 import com.android.tools.metalava.model.DefaultAnnotationItem 58 import com.android.tools.metalava.model.DefaultAnnotationValue 59 import com.android.tools.metalava.model.Item 60 import com.android.tools.metalava.model.MethodItem 61 import com.android.tools.metalava.model.ModifierList 62 import com.android.tools.metalava.model.TypeItem 63 import com.android.tools.metalava.model.parseDocument 64 import com.android.tools.metalava.model.psi.PsiAnnotationItem 65 import com.android.tools.metalava.model.psi.PsiBasedCodebase 66 import com.android.tools.metalava.model.psi.PsiTypeItem 67 import com.android.tools.metalava.model.visitors.ApiVisitor 68 import com.google.common.io.ByteStreams 69 import com.google.common.io.Closeables 70 import com.google.common.io.Files 71 import org.w3c.dom.Document 72 import org.w3c.dom.Element 73 import org.xml.sax.SAXParseException 74 import java.io.File 75 import java.io.FileInputStream 76 import java.io.IOException 77 import java.lang.reflect.Field 78 import java.util.jar.JarInputStream 79 import java.util.regex.Pattern 80 import java.util.zip.ZipEntry 81 import kotlin.text.Charsets.UTF_8 82 83 /** Merges annotations into classes already registered in the given [Codebase] */ 84 class AnnotationsMerger( 85 private val codebase: Codebase 86 ) { 87 88 /** Merge annotations which will appear in the output API. */ mergeQualifierAnnotationsnull89 fun mergeQualifierAnnotations(files: List<File>) { 90 mergeAll( 91 files, 92 ::mergeQualifierAnnotationsFromFile, 93 ::mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase 94 ) 95 } 96 97 /** Merge annotations which control what is included in the output API. */ mergeInclusionAnnotationsnull98 fun mergeInclusionAnnotations(files: List<File>) { 99 mergeAll( 100 files, 101 { 102 throw DriverException( 103 "External inclusion annotations files must be .java, found ${it.path}" 104 ) 105 }, 106 ::mergeInclusionAnnotationsFromCodebase 107 ) 108 } 109 mergeAllnull110 private fun mergeAll( 111 mergeAnnotations: List<File>, 112 mergeFile: (File) -> Unit, 113 mergeJavaStubsCodebase: (PsiBasedCodebase) -> Unit 114 ) { 115 val javaStubFiles = mutableListOf<File>() 116 mergeAnnotations.forEach { 117 mergeFileOrDir(it, mergeFile, javaStubFiles) 118 } 119 if (javaStubFiles.isNotEmpty()) { 120 // Set up class path to contain our main sources such that we can 121 // resolve types in the stubs 122 val roots = mutableListOf<File>() 123 extractRoots(options.sources, roots) 124 roots.addAll(options.classpath) 125 roots.addAll(options.sourcePath) 126 val classpath = roots.distinct().toList() 127 val javaStubsCodebase = parseSources(javaStubFiles, "Codebase loaded from stubs", 128 classpath = classpath) 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 */ mergeFileOrDirnull139 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 mergeQualifierAnnotationsFromFilenull160 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("Aborting: 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("Aborting: I/O problem during transform: $e") 180 } 181 } 182 } 183 mergeFromJarnull184 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("Aborting: 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 mergeAnnotationsXmlnull211 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 mergeAnnotationsSignatureFilenull227 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 mergeAndValidateQualifierAnnotationsFromJavaStubsCodebasenull238 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 mergeQualifierAnnotationsFromCodebasenull248 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 private fun mergeAnnotation( 261 annotation: AnnotationItem, 262 newModifiers: ModifierList, 263 new: Item 264 ) { 265 var addAnnotation = false 266 if (annotation.isNullnessAnnotation()) { 267 if (!newModifiers.hasNullnessInfo()) { 268 addAnnotation = true 269 } 270 } else { 271 // TODO: Check for other incompatibilities than nullness? 272 val qualifiedName = annotation.qualifiedName() ?: return 273 if (newModifiers.findAnnotation(qualifiedName) == null) { 274 addAnnotation = true 275 } 276 } 277 278 if (addAnnotation) { 279 // Don't map annotation names - this would turn newly non null back into non null 280 new.mutableModifiers().addAnnotation( 281 new.codebase.createAnnotation( 282 annotation.toSource(), 283 new, 284 mapName = false 285 ) 286 ) 287 } 288 } 289 290 private fun mergeTypeAnnotations( 291 typeItem: TypeItem, 292 new: Item 293 ) { 294 val type = (typeItem as? PsiTypeItem)?.psiType ?: return 295 val typeAnnotations = type.annotations 296 if (typeAnnotations.isNotEmpty()) { 297 for (annotation in typeAnnotations) { 298 val codebase = new.codebase as PsiBasedCodebase 299 val annotationItem = PsiAnnotationItem.create(codebase, annotation) 300 mergeAnnotation(annotationItem, new.modifiers, new) 301 } 302 } 303 } 304 } 305 306 CodebaseComparator().compare( 307 visitor, externalCodebase, codebase, ApiPredicate() 308 ) 309 } 310 mergeInclusionAnnotationsFromCodebasenull311 private fun mergeInclusionAnnotationsFromCodebase(externalCodebase: Codebase) { 312 val inclusionAnnotations = options.showAnnotations union options.hideAnnotations 313 if (inclusionAnnotations.isNotEmpty()) { 314 val visitor = object : ComparisonVisitor() { 315 override fun compare(old: Item, new: Item) { 316 // Transfer any show/hide annotations from the external to the main codebase. 317 for (annotation in old.modifiers.annotations()) { 318 val qualifiedName = annotation.qualifiedName() ?: continue 319 if (inclusionAnnotations.contains(qualifiedName) && 320 new.modifiers.findAnnotation(qualifiedName) == null 321 ) { 322 new.mutableModifiers().addAnnotation(annotation) 323 } 324 } 325 // The hidden field in the main codebase is already initialized. So if the 326 // element is hidden in the external codebase, hide it in the main codebase too. 327 if (old.hidden) { 328 new.hidden = true 329 } 330 if (old.originallyHidden) { 331 new.originallyHidden = true 332 } 333 } 334 } 335 CodebaseComparator().compare(visitor, externalCodebase, codebase) 336 } 337 } 338 errornull339 internal fun error(message: String) { 340 // TODO: Integrate with metalava error facility 341 options.stderr.println("Error: $message") 342 } 343 warningnull344 internal fun warning(message: String) { 345 if (options.verbose) { 346 options.stdout.println("Warning: $message") 347 } 348 } 349 350 @Suppress("PrivatePropertyName") 351 private val XML_SIGNATURE: Pattern = Pattern.compile( 352 // Class (FieldName | Type? Name(ArgList) Argnum?) 353 // "(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)"); 354 "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)" 355 ) 356 mergeDocumentnull357 private fun mergeDocument(document: Document) { 358 359 val root = document.documentElement 360 val rootTag = root.tagName 361 assert(rootTag == "root") { rootTag } 362 363 for (item in getChildren(root)) { 364 var signature: String? = item.getAttribute(ATTR_NAME) 365 if (signature == null || signature == "null") { 366 continue // malformed item 367 } 368 369 signature = unescapeXml(signature) 370 if (signature == "java.util.Calendar int get(int)") { 371 // https://youtrack.jetbrains.com/issue/IDEA-137385 372 continue 373 } else if (signature == "java.util.Calendar void set(int, int, int) 1" || 374 signature == "java.util.Calendar void set(int, int, int, int, int) 1" || 375 signature == "java.util.Calendar void set(int, int, int, int, int, int) 1" 376 ) { 377 // http://b.android.com/76090 378 continue 379 } 380 381 val matcher = XML_SIGNATURE.matcher(signature) 382 if (matcher.matches()) { 383 val containingClass = matcher.group(1) 384 if (containingClass == null) { 385 warning("Could not find class for $signature") 386 continue 387 } 388 389 val classItem = codebase.findClass(containingClass) 390 if (classItem == null) { 391 // Well known exceptions from IntelliJ's external annotations 392 // we won't complain loudly about 393 if (wellKnownIgnoredImport(containingClass)) { 394 continue 395 } 396 397 warning("Could not find class $containingClass; omitting annotation from merge") 398 continue 399 } 400 401 val methodName = matcher.group(5) 402 if (methodName != null) { 403 val parameters = matcher.group(6) 404 val parameterIndex = 405 if (matcher.group(7) != null) { 406 Integer.parseInt(matcher.group(7).trim()) 407 } else { 408 -1 409 } 410 mergeMethodOrParameter(item, containingClass, classItem, methodName, parameterIndex, parameters) 411 } else { 412 val fieldName = matcher.group(2) 413 mergeField(item, containingClass, classItem, fieldName) 414 } 415 } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) { 416 // Must be just a class 417 val containingClass = signature 418 val classItem = codebase.findClass(containingClass) 419 if (classItem == null) { 420 if (wellKnownIgnoredImport(containingClass)) { 421 continue 422 } 423 424 warning("Could not find class $containingClass; omitting annotation from merge") 425 continue 426 } 427 428 mergeAnnotations(item, classItem) 429 } else { 430 warning("No merge match for signature $signature") 431 } 432 } 433 } 434 wellKnownIgnoredImportnull435 private fun wellKnownIgnoredImport(containingClass: String): Boolean { 436 if (containingClass.startsWith("javax.swing.") || 437 containingClass.startsWith("javax.naming.") || 438 containingClass.startsWith("java.awt.") || 439 containingClass.startsWith("org.jdom.") 440 ) { 441 return true 442 } 443 return false 444 } 445 446 // The parameter declaration used in XML files should not have duplicated spaces, 447 // and there should be no space after commas (we can't however strip out all spaces, 448 // since for example the spaces around the "extends" keyword needs to be there in 449 // types like Map<String,? extends Number> fixParameterStringnull450 private fun fixParameterString(parameters: String): String { 451 return parameters.replace(" ", " ").replace(", ", ",").replace("?super", "? super ") 452 .replace("?extends", "? extends ") 453 } 454 mergeMethodOrParameternull455 private fun mergeMethodOrParameter( 456 item: Element, 457 containingClass: String, 458 classItem: ClassItem, 459 methodName: String, 460 parameterIndex: Int, 461 parameters: String 462 ) { 463 @Suppress("NAME_SHADOWING") 464 val parameters = fixParameterString(parameters) 465 466 val methodItem: MethodItem? = classItem.findMethod(methodName, parameters) 467 if (methodItem == null) { 468 if (wellKnownIgnoredImport(containingClass)) { 469 return 470 } 471 472 warning("Could not find method $methodName($parameters) in $containingClass; omitting annotation from merge") 473 return 474 } 475 476 if (parameterIndex != -1) { 477 val parameterItem = methodItem.parameters()[parameterIndex] 478 479 if ("java.util.Calendar" == containingClass && "set" == methodName && 480 parameterIndex > 0 481 ) { 482 // Skip the metadata for Calendar.set(int, int, int+); see 483 // https://code.google.com/p/android/issues/detail?id=73982 484 return 485 } 486 487 mergeAnnotations(item, parameterItem) 488 } else { 489 // Annotation on the method itself 490 mergeAnnotations(item, methodItem) 491 } 492 } 493 mergeFieldnull494 private fun mergeField(item: Element, containingClass: String, classItem: ClassItem, fieldName: String) { 495 496 val fieldItem = classItem.findField(fieldName) 497 if (fieldItem == null) { 498 if (wellKnownIgnoredImport(containingClass)) { 499 return 500 } 501 502 warning("Could not find field $fieldName in $containingClass; omitting annotation from merge") 503 return 504 } 505 506 mergeAnnotations(item, fieldItem) 507 } 508 getAnnotationNamenull509 private fun getAnnotationName(element: Element): String { 510 val tagName = element.tagName 511 assert(tagName == "annotation") { tagName } 512 513 val qualifiedName = element.getAttribute(ATTR_NAME) 514 assert(qualifiedName != null && !qualifiedName.isEmpty()) 515 return qualifiedName 516 } 517 mergeAnnotationsnull518 private fun mergeAnnotations(xmlElement: Element, item: Item) { 519 loop@ for (annotationElement in getChildren(xmlElement)) { 520 val originalName = getAnnotationName(annotationElement) 521 val qualifiedName = AnnotationItem.mapName(codebase, originalName) ?: originalName 522 if (hasNullnessConflicts(item, qualifiedName)) { 523 continue@loop 524 } 525 526 val annotationItem = createAnnotation(annotationElement) ?: continue 527 item.mutableModifiers().addAnnotation(annotationItem) 528 } 529 } 530 hasNullnessConflictsnull531 private fun hasNullnessConflicts( 532 item: Item, 533 qualifiedName: String 534 ): Boolean { 535 var haveNullable = false 536 var haveNotNull = false 537 for (existing in item.modifiers.annotations()) { 538 val name = existing.qualifiedName() ?: continue 539 if (isNonNull(name)) { 540 haveNotNull = true 541 } 542 if (isNullable(name)) { 543 haveNullable = true 544 } 545 if (name == qualifiedName) { 546 return true 547 } 548 } 549 550 // Make sure we don't have a conflict between nullable and not nullable 551 if (isNonNull(qualifiedName) && haveNullable || isNullable(qualifiedName) && haveNotNull) { 552 warning("Found both @Nullable and @NonNull after import for $item") 553 return true 554 } 555 return false 556 } 557 558 /** Reads in annotation data from an XML item (using IntelliJ IDE's external annotations XML format) and 559 * creates a corresponding [AnnotationItem], performing some "translations" in the process (e.g. mapping 560 * from IntelliJ annotations like `org.jetbrains.annotations.Nullable` to `android.support.annotation.Nullable`. */ createAnnotationnull561 private fun createAnnotation(annotationElement: Element): AnnotationItem? { 562 val tagName = annotationElement.tagName 563 assert(tagName == "annotation") { tagName } 564 val name = annotationElement.getAttribute(ATTR_NAME) 565 assert(name != null && !name.isEmpty()) 566 when { 567 name == "org.jetbrains.annotations.Range" -> { 568 val children = getChildren(annotationElement) 569 assert(children.size == 2) { children.size } 570 val valueElement1 = children[0] 571 val valueElement2 = children[1] 572 val valName1 = valueElement1.getAttribute(ATTR_NAME) 573 val value1 = valueElement1.getAttribute(ATTR_VAL) 574 val valName2 = valueElement2.getAttribute(ATTR_NAME) 575 val value2 = valueElement2.getAttribute(ATTR_VAL) 576 return PsiAnnotationItem.create( 577 codebase, XmlBackedAnnotationItem( 578 codebase, AnnotationDetector.INT_RANGE_ANNOTATION.newName(), 579 listOf( 580 // Add "L" suffix to ensure that we don't for example interpret "-1" as 581 // an integer -1 and then end up recording it as "ffffffff" instead of -1L 582 XmlBackedAnnotationAttribute( 583 valName1, 584 value1 + (if (value1.last().isDigit()) "L" else "") 585 ), 586 XmlBackedAnnotationAttribute( 587 valName2, 588 value2 + (if (value2.last().isDigit()) "L" else "") 589 ) 590 ) 591 ) 592 ) 593 } 594 name == IDEA_MAGIC -> { 595 val children = getChildren(annotationElement) 596 assert(children.size == 1) { children.size } 597 val valueElement = children[0] 598 val valName = valueElement.getAttribute(ATTR_NAME) 599 var value = valueElement.getAttribute(ATTR_VAL) 600 val flagsFromClass = valName == "flagsFromClass" 601 val flag = valName == "flags" || flagsFromClass 602 if (valName == "valuesFromClass" || flagsFromClass) { 603 // Not supported 604 var found = false 605 if (value.endsWith(DOT_CLASS)) { 606 val clsName = value.substring(0, value.length - DOT_CLASS.length) 607 val sb = StringBuilder() 608 sb.append('{') 609 610 var reflectionFields: Array<Field>? = null 611 try { 612 val cls = Class.forName(clsName) 613 reflectionFields = cls.declaredFields 614 } catch (ignore: Exception) { 615 // Class not available: not a problem. We'll rely on API filter. 616 // It's mainly used for sorting anyway. 617 } 618 619 // Attempt to sort in reflection order 620 if (!found && reflectionFields != null) { 621 val filterEmit = ApiVisitor().filterEmit 622 623 // Attempt with reflection 624 var first = true 625 for (field in reflectionFields) { 626 if (field.type == Integer.TYPE || field.type == Int::class.javaPrimitiveType) { 627 // Make sure this field is included in our API too 628 val fieldItem = codebase.findClass(clsName)?.findField(field.name) 629 if (fieldItem == null || !filterEmit.test(fieldItem)) { 630 continue 631 } 632 633 if (first) { 634 first = false 635 } else { 636 sb.append(',').append(' ') 637 } 638 sb.append(clsName).append('.').append(field.name) 639 } 640 } 641 } 642 sb.append('}') 643 value = sb.toString() 644 if (sb.length > 2) { // 2: { } 645 found = true 646 } 647 } 648 649 if (!found) { 650 return null 651 } 652 } 653 654 val attributes = mutableListOf<XmlBackedAnnotationAttribute>() 655 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) 656 if (flag) { 657 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE)) 658 } 659 return PsiAnnotationItem.create( 660 codebase, XmlBackedAnnotationItem( 661 codebase, 662 if (valName == "stringValues") STRING_DEF_ANNOTATION.newName() else INT_DEF_ANNOTATION.newName(), 663 attributes 664 ) 665 ) 666 } 667 668 name == STRING_DEF_ANNOTATION.oldName() || 669 name == STRING_DEF_ANNOTATION.newName() || 670 name == ANDROID_STRING_DEF || 671 name == INT_DEF_ANNOTATION.oldName() || 672 name == INT_DEF_ANNOTATION.newName() || 673 name == ANDROID_INT_DEF -> { 674 val children = getChildren(annotationElement) 675 var valueElement = children[0] 676 val valName = valueElement.getAttribute(ATTR_NAME) 677 assert(TYPE_DEF_VALUE_ATTRIBUTE == valName) 678 val value = valueElement.getAttribute(ATTR_VAL) 679 var flag = false 680 if (children.size == 2) { 681 valueElement = children[1] 682 assert(TYPE_DEF_FLAG_ATTRIBUTE == valueElement.getAttribute(ATTR_NAME)) 683 flag = VALUE_TRUE == valueElement.getAttribute(ATTR_VAL) 684 } 685 val intDef = INT_DEF_ANNOTATION.oldName() == name || 686 INT_DEF_ANNOTATION.newName() == name || 687 ANDROID_INT_DEF == name 688 689 val attributes = mutableListOf<XmlBackedAnnotationAttribute>() 690 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) 691 if (flag) { 692 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE)) 693 } 694 return PsiAnnotationItem.create( 695 codebase, XmlBackedAnnotationItem( 696 codebase, 697 if (intDef) INT_DEF_ANNOTATION.newName() else STRING_DEF_ANNOTATION.newName(), attributes 698 ) 699 ) 700 } 701 702 name == IDEA_CONTRACT -> { 703 val children = getChildren(annotationElement) 704 val valueElement = children[0] 705 val value = valueElement.getAttribute(ATTR_VAL) 706 val pure = valueElement.getAttribute(ATTR_PURE) 707 return if (pure != null && !pure.isEmpty()) { 708 PsiAnnotationItem.create( 709 codebase, XmlBackedAnnotationItem( 710 codebase, name, 711 listOf( 712 XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value), 713 XmlBackedAnnotationAttribute(ATTR_PURE, pure) 714 ) 715 ) 716 ) 717 } else { 718 PsiAnnotationItem.create( 719 codebase, XmlBackedAnnotationItem( 720 codebase, name, 721 listOf(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) 722 ) 723 ) 724 } 725 } 726 727 isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NONNULL") 728 729 isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE") 730 731 else -> { 732 val children = getChildren(annotationElement) 733 if (children.isEmpty()) { 734 return codebase.createAnnotation("@$name") 735 } 736 val attributes = mutableListOf<XmlBackedAnnotationAttribute>() 737 for (valueElement in children) { 738 attributes.add( 739 XmlBackedAnnotationAttribute( 740 valueElement.getAttribute(ATTR_NAME) ?: continue, 741 valueElement.getAttribute(ATTR_VAL) ?: continue 742 ) 743 ) 744 } 745 return PsiAnnotationItem.create(codebase, XmlBackedAnnotationItem(codebase, name, attributes)) 746 } 747 } 748 } 749 isNonNullnull750 private fun isNonNull(name: String): Boolean { 751 return name == IDEA_NOTNULL || 752 name == ANDROID_NOTNULL || 753 name == ANDROIDX_NONNULL || 754 name == SUPPORT_NOTNULL 755 } 756 isNullablenull757 private fun isNullable(name: String): Boolean { 758 return name == IDEA_NULLABLE || 759 name == ANDROID_NULLABLE || 760 name == ANDROIDX_NULLABLE || 761 name == SUPPORT_NULLABLE 762 } 763 unescapeXmlnull764 private fun unescapeXml(escaped: String): String { 765 var workingString = escaped.replace(QUOT_ENTITY, "\"") 766 workingString = workingString.replace(LT_ENTITY, "<") 767 workingString = workingString.replace(GT_ENTITY, ">") 768 workingString = workingString.replace(APOS_ENTITY, "'") 769 workingString = workingString.replace(AMP_ENTITY, "&") 770 771 return workingString 772 } 773 } 774 775 // TODO: Replace with usage of DefaultAnnotationValue? 776 data class XmlBackedAnnotationAttribute( 777 override val name: String, 778 private val valueLiteral: String 779 ) : AnnotationAttribute { 780 override val value: AnnotationAttributeValue = DefaultAnnotationValue.create(valueLiteral) 781 toStringnull782 override fun toString(): String { 783 return "$name=$valueLiteral" 784 } 785 } 786 787 // TODO: Replace with usage of DefaultAnnotationAttribute? 788 class XmlBackedAnnotationItem( 789 codebase: Codebase, 790 private val qualifiedName: String, 791 private val attributes: List<XmlBackedAnnotationAttribute> = emptyList() 792 ) : DefaultAnnotationItem(codebase) { 793 originalNamenull794 override fun originalName(): String? = qualifiedName 795 override fun qualifiedName(): String? = AnnotationItem.mapName(codebase, qualifiedName) 796 797 override fun attributes() = attributes 798 799 override fun toSource(target: AnnotationTarget): String { 800 val qualifiedName = AnnotationItem.mapName(codebase, qualifiedName, null, target) ?: return "" 801 802 if (attributes.isEmpty()) { 803 return "@$qualifiedName" 804 } 805 806 val sb = StringBuilder(30) 807 sb.append("@") 808 sb.append(qualifiedName) 809 sb.append("(") 810 attributes.joinTo(sb) 811 sb.append(")") 812 813 return sb.toString() 814 } 815 } 816