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