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