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