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