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