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.sourcePath) 124 val javaStubsCodebase = parseSources( 125 javaStubFiles, 126 "Codebase loaded from stubs", 127 sourcePath = roots, 128 classpath = options.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("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 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("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 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 mergeInclusionAnnotationsFromCodebasenull315 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 errornull345 internal fun error(message: String) { 346 // TODO: Integrate with metalava error facility 347 options.stderr.println("Error: $message") 348 } 349 warningnull350 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 mergeDocumentnull363 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 wellKnownIgnoredImportnull441 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> fixParameterStringnull456 private fun fixParameterString(parameters: String): String { 457 return parameters.replace(" ", " ").replace(", ", ",").replace("?super", "? super ") 458 .replace("?extends", "? extends ") 459 } 460 mergeMethodOrParameternull461 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 mergeFieldnull500 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 getAnnotationNamenull515 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 mergeAnnotationsnull524 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 hasNullnessConflictsnull537 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 `android.support.annotation.Nullable`. */ createAnnotationnull567 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, XmlBackedAnnotationItem( 584 codebase, AnnotationDetector.INT_RANGE_ANNOTATION.newName(), 585 listOf( 586 // Add "L" suffix to ensure that we don't for example interpret "-1" as 587 // an integer -1 and then end up recording it as "ffffffff" instead of -1L 588 XmlBackedAnnotationAttribute( 589 valName1, 590 value1 + (if (value1.last().isDigit()) "L" else "") 591 ), 592 XmlBackedAnnotationAttribute( 593 valName2, 594 value2 + (if (value2.last().isDigit()) "L" else "") 595 ) 596 ) 597 ) 598 ) 599 } 600 name == IDEA_MAGIC -> { 601 val children = getChildren(annotationElement) 602 assert(children.size == 1) { children.size } 603 val valueElement = children[0] 604 val valName = valueElement.getAttribute(ATTR_NAME) 605 var value = valueElement.getAttribute(ATTR_VAL) 606 val flagsFromClass = valName == "flagsFromClass" 607 val flag = valName == "flags" || flagsFromClass 608 if (valName == "valuesFromClass" || flagsFromClass) { 609 // Not supported 610 var found = false 611 if (value.endsWith(DOT_CLASS)) { 612 val clsName = value.substring(0, value.length - DOT_CLASS.length) 613 val sb = StringBuilder() 614 sb.append('{') 615 616 var reflectionFields: Array<Field>? = null 617 try { 618 val cls = Class.forName(clsName) 619 reflectionFields = cls.declaredFields 620 } catch (ignore: Exception) { 621 // Class not available: not a problem. We'll rely on API filter. 622 // It's mainly used for sorting anyway. 623 } 624 625 // Attempt to sort in reflection order 626 if (!found && reflectionFields != null) { 627 val filterEmit = ApiVisitor().filterEmit 628 629 // Attempt with reflection 630 var first = true 631 for (field in reflectionFields) { 632 if (field.type == Integer.TYPE || field.type == Int::class.javaPrimitiveType) { 633 // Make sure this field is included in our API too 634 val fieldItem = codebase.findClass(clsName)?.findField(field.name) 635 if (fieldItem == null || !filterEmit.test(fieldItem)) { 636 continue 637 } 638 639 if (first) { 640 first = false 641 } else { 642 sb.append(',').append(' ') 643 } 644 sb.append(clsName).append('.').append(field.name) 645 } 646 } 647 } 648 sb.append('}') 649 value = sb.toString() 650 if (sb.length > 2) { // 2: { } 651 found = true 652 } 653 } 654 655 if (!found) { 656 return null 657 } 658 } 659 660 val attributes = mutableListOf<XmlBackedAnnotationAttribute>() 661 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) 662 if (flag) { 663 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE)) 664 } 665 return PsiAnnotationItem.create( 666 codebase, XmlBackedAnnotationItem( 667 codebase, 668 if (valName == "stringValues") STRING_DEF_ANNOTATION.newName() else INT_DEF_ANNOTATION.newName(), 669 attributes 670 ) 671 ) 672 } 673 674 name == STRING_DEF_ANNOTATION.oldName() || 675 name == STRING_DEF_ANNOTATION.newName() || 676 name == ANDROID_STRING_DEF || 677 name == INT_DEF_ANNOTATION.oldName() || 678 name == INT_DEF_ANNOTATION.newName() || 679 name == ANDROID_INT_DEF -> { 680 val children = getChildren(annotationElement) 681 var valueElement = children[0] 682 val valName = valueElement.getAttribute(ATTR_NAME) 683 assert(TYPE_DEF_VALUE_ATTRIBUTE == valName) 684 val value = valueElement.getAttribute(ATTR_VAL) 685 var flag = false 686 if (children.size == 2) { 687 valueElement = children[1] 688 assert(TYPE_DEF_FLAG_ATTRIBUTE == valueElement.getAttribute(ATTR_NAME)) 689 flag = VALUE_TRUE == valueElement.getAttribute(ATTR_VAL) 690 } 691 val intDef = INT_DEF_ANNOTATION.oldName() == name || 692 INT_DEF_ANNOTATION.newName() == name || 693 ANDROID_INT_DEF == name 694 695 val attributes = mutableListOf<XmlBackedAnnotationAttribute>() 696 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) 697 if (flag) { 698 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE)) 699 } 700 return PsiAnnotationItem.create( 701 codebase, XmlBackedAnnotationItem( 702 codebase, 703 if (intDef) INT_DEF_ANNOTATION.newName() else STRING_DEF_ANNOTATION.newName(), attributes 704 ) 705 ) 706 } 707 708 name == IDEA_CONTRACT -> { 709 val children = getChildren(annotationElement) 710 val valueElement = children[0] 711 val value = valueElement.getAttribute(ATTR_VAL) 712 val pure = valueElement.getAttribute(ATTR_PURE) 713 return if (pure != null && pure.isNotEmpty()) { 714 PsiAnnotationItem.create( 715 codebase, XmlBackedAnnotationItem( 716 codebase, name, 717 listOf( 718 XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value), 719 XmlBackedAnnotationAttribute(ATTR_PURE, pure) 720 ) 721 ) 722 ) 723 } else { 724 PsiAnnotationItem.create( 725 codebase, XmlBackedAnnotationItem( 726 codebase, name, 727 listOf(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) 728 ) 729 ) 730 } 731 } 732 733 isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NONNULL") 734 735 isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE") 736 737 else -> { 738 val children = getChildren(annotationElement) 739 if (children.isEmpty()) { 740 return codebase.createAnnotation("@$name") 741 } 742 val attributes = mutableListOf<XmlBackedAnnotationAttribute>() 743 for (valueElement in children) { 744 attributes.add( 745 XmlBackedAnnotationAttribute( 746 valueElement.getAttribute(ATTR_NAME) ?: continue, 747 valueElement.getAttribute(ATTR_VAL) ?: continue 748 ) 749 ) 750 } 751 return PsiAnnotationItem.create(codebase, XmlBackedAnnotationItem(codebase, name, attributes)) 752 } 753 } 754 } 755 isNonNullnull756 private fun isNonNull(name: String): Boolean { 757 return name == IDEA_NOTNULL || 758 name == ANDROID_NOTNULL || 759 name == ANDROIDX_NONNULL || 760 name == SUPPORT_NOTNULL 761 } 762 isNullablenull763 private fun isNullable(name: String): Boolean { 764 return name == IDEA_NULLABLE || 765 name == ANDROID_NULLABLE || 766 name == ANDROIDX_NULLABLE || 767 name == SUPPORT_NULLABLE 768 } 769 unescapeXmlnull770 private fun unescapeXml(escaped: String): String { 771 var workingString = escaped.replace(QUOT_ENTITY, "\"") 772 workingString = workingString.replace(LT_ENTITY, "<") 773 workingString = workingString.replace(GT_ENTITY, ">") 774 workingString = workingString.replace(APOS_ENTITY, "'") 775 workingString = workingString.replace(AMP_ENTITY, "&") 776 777 return workingString 778 } 779 } 780 781 // TODO: Replace with usage of DefaultAnnotationValue? 782 data class XmlBackedAnnotationAttribute( 783 override val name: String, 784 private val valueLiteral: String 785 ) : AnnotationAttribute { 786 override val value: AnnotationAttributeValue = DefaultAnnotationValue.create(valueLiteral) 787 toStringnull788 override fun toString(): String { 789 return "$name=$valueLiteral" 790 } 791 } 792 793 // TODO: Replace with usage of DefaultAnnotationAttribute? 794 class XmlBackedAnnotationItem( 795 codebase: Codebase, 796 private val qualifiedName: String, 797 private val attributes: List<XmlBackedAnnotationAttribute> = emptyList() 798 ) : DefaultAnnotationItem(codebase) { 799 originalNamenull800 override fun originalName(): String? = qualifiedName 801 override fun qualifiedName(): String? = AnnotationItem.mapName(codebase, qualifiedName) 802 803 override fun attributes() = attributes 804 805 override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String { 806 val qualifiedName = AnnotationItem.mapName(codebase, qualifiedName, null, target) ?: return "" 807 808 if (attributes.isEmpty()) { 809 return "@$qualifiedName" 810 } 811 812 val sb = StringBuilder(30) 813 sb.append("@") 814 sb.append(qualifiedName) 815 sb.append("(") 816 attributes.joinTo(sb) 817 sb.append(")") 818 819 return sb.toString() 820 } 821 } 822