• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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.detector.api.getChildren
47 import com.android.tools.metalava.model.AnnotationAttribute
48 import com.android.tools.metalava.model.AnnotationAttributeValue
49 import com.android.tools.metalava.model.AnnotationItem
50 import com.android.tools.metalava.model.AnnotationTarget
51 import com.android.tools.metalava.model.ClassItem
52 import com.android.tools.metalava.model.Codebase
53 import com.android.tools.metalava.model.DefaultAnnotationItem
54 import com.android.tools.metalava.model.DefaultAnnotationValue
55 import com.android.tools.metalava.model.Item
56 import com.android.tools.metalava.model.MethodItem
57 import com.android.tools.metalava.model.ModifierList
58 import com.android.tools.metalava.model.TypeItem
59 import com.android.tools.metalava.model.parseDocument
60 import com.android.tools.metalava.model.psi.PsiAnnotationItem
61 import com.android.tools.metalava.model.psi.PsiBasedCodebase
62 import com.android.tools.metalava.model.psi.PsiTypeItem
63 import com.android.tools.metalava.model.text.ApiFile
64 import com.android.tools.metalava.model.text.ApiParseException
65 import com.android.tools.metalava.model.visitors.ApiVisitor
66 import com.google.common.io.ByteStreams
67 import com.google.common.io.Closeables
68 import com.google.common.io.Files
69 import org.w3c.dom.Document
70 import org.w3c.dom.Element
71 import org.xml.sax.SAXParseException
72 import java.io.File
73 import java.io.FileInputStream
74 import java.io.IOException
75 import java.lang.reflect.Field
76 import java.util.jar.JarInputStream
77 import java.util.regex.Pattern
78 import java.util.zip.ZipEntry
79 import kotlin.text.Charsets.UTF_8
80 
81 /** Merges annotations into classes already registered in the given [Codebase] */
82 class AnnotationsMerger(
83     private val codebase: Codebase
84 ) {
85 
86     /** Merge annotations which will appear in the output API. */
87     fun mergeQualifierAnnotations(files: List<File>) {
88         mergeAll(
89             files,
90             ::mergeQualifierAnnotationsFromFile,
91             ::mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase
92         )
93     }
94 
95     /** Merge annotations which control what is included in the output API. */
96     fun mergeInclusionAnnotations(files: List<File>) {
97         mergeAll(
98             files,
99             {
100                 throw DriverException(
101                     "External inclusion annotations files must be .java, found ${it.path}"
102                 )
103             },
104             ::mergeInclusionAnnotationsFromCodebase
105         )
106     }
107 
108     private fun mergeAll(
109         mergeAnnotations: List<File>,
110         mergeFile: (File) -> Unit,
111         mergeJavaStubsCodebase: (PsiBasedCodebase) -> Unit
112     ) {
113         val javaStubFiles = mutableListOf<File>()
114         mergeAnnotations.forEach {
115             mergeFileOrDir(it, mergeFile, javaStubFiles)
116         }
117         if (javaStubFiles.isNotEmpty()) {
118             // Set up class path to contain our main sources such that we can
119             // resolve types in the stubs
120             val roots = mutableListOf<File>()
121             extractRoots(options.sources, roots)
122             roots.addAll(options.sourcePath)
123             val javaStubsCodebase = parseSources(
124                 javaStubFiles,
125                 "Codebase loaded from stubs",
126                 sourcePath = roots,
127                 classpath = options.classpath
128             )
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      */
139     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 
160     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 
184     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 
211     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 
227     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 
238     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 
248     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 
315     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 
345     internal fun error(message: String) {
346         // TODO: Integrate with metalava error facility
347         options.stderr.println("Error: $message")
348     }
349 
350     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 
363     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 
441     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>
456     private fun fixParameterString(parameters: String): String {
457         return parameters.replace("  ", " ").replace(", ", ",").replace("?super", "? super ")
458             .replace("?extends", "? extends ")
459     }
460 
461     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 
500     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 
515     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 
524     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 
537     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 `androidx.annotation.Nullable`. */
567     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,
584                     XmlBackedAnnotationItem(
585                         codebase,
586                         "androidx.annotation.IntRange",
587                         listOf(
588                             // Add "L" suffix to ensure that we don't for example interpret "-1" as
589                             // an integer -1 and then end up recording it as "ffffffff" instead of -1L
590                             XmlBackedAnnotationAttribute(
591                                 valName1,
592                                 value1 + (if (value1.last().isDigit()) "L" else "")
593                             ),
594                             XmlBackedAnnotationAttribute(
595                                 valName2,
596                                 value2 + (if (value2.last().isDigit()) "L" else "")
597                             )
598                         )
599                     )
600                 )
601             }
602             name == IDEA_MAGIC -> {
603                 val children = getChildren(annotationElement)
604                 assert(children.size == 1) { children.size }
605                 val valueElement = children[0]
606                 val valName = valueElement.getAttribute(ATTR_NAME)
607                 var value = valueElement.getAttribute(ATTR_VAL)
608                 val flagsFromClass = valName == "flagsFromClass"
609                 val flag = valName == "flags" || flagsFromClass
610                 if (valName == "valuesFromClass" || flagsFromClass) {
611                     // Not supported
612                     var found = false
613                     if (value.endsWith(DOT_CLASS)) {
614                         val clsName = value.substring(0, value.length - DOT_CLASS.length)
615                         val sb = StringBuilder()
616                         sb.append('{')
617 
618                         var reflectionFields: Array<Field>? = null
619                         try {
620                             val cls = Class.forName(clsName)
621                             reflectionFields = cls.declaredFields
622                         } catch (ignore: Exception) {
623                             // Class not available: not a problem. We'll rely on API filter.
624                             // It's mainly used for sorting anyway.
625                         }
626 
627                         // Attempt to sort in reflection order
628                         if (!found && reflectionFields != null) {
629                             val filterEmit = ApiVisitor().filterEmit
630 
631                             // Attempt with reflection
632                             var first = true
633                             for (field in reflectionFields) {
634                                 if (field.type == Integer.TYPE || field.type == Int::class.javaPrimitiveType) {
635                                     // Make sure this field is included in our API too
636                                     val fieldItem = codebase.findClass(clsName)?.findField(field.name)
637                                     if (fieldItem == null || !filterEmit.test(fieldItem)) {
638                                         continue
639                                     }
640 
641                                     if (first) {
642                                         first = false
643                                     } else {
644                                         sb.append(',').append(' ')
645                                     }
646                                     sb.append(clsName).append('.').append(field.name)
647                                 }
648                             }
649                         }
650                         sb.append('}')
651                         value = sb.toString()
652                         if (sb.length > 2) { // 2: { }
653                             found = true
654                         }
655                     }
656 
657                     if (!found) {
658                         return null
659                     }
660                 }
661 
662                 val attributes = mutableListOf<XmlBackedAnnotationAttribute>()
663                 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value))
664                 if (flag) {
665                     attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE))
666                 }
667                 return PsiAnnotationItem.create(
668                     codebase,
669                     XmlBackedAnnotationItem(
670                         codebase,
671                         if (valName == "stringValues") STRING_DEF_ANNOTATION.newName() else INT_DEF_ANNOTATION.newName(),
672                         attributes
673                     )
674                 )
675             }
676 
677             name == STRING_DEF_ANNOTATION.oldName() ||
678                 name == STRING_DEF_ANNOTATION.newName() ||
679                 name == ANDROID_STRING_DEF ||
680                 name == INT_DEF_ANNOTATION.oldName() ||
681                 name == INT_DEF_ANNOTATION.newName() ||
682                 name == ANDROID_INT_DEF -> {
683 
684                 val attributes = mutableListOf<XmlBackedAnnotationAttribute>()
685                 val parseChild: (Element) -> Unit = { child: Element ->
686                     val elementName = child.getAttribute(ATTR_NAME)
687                     val value = child.getAttribute(ATTR_VAL)
688                     when (elementName) {
689                         TYPE_DEF_VALUE_ATTRIBUTE -> {
690                             attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value))
691                         }
692                         TYPE_DEF_FLAG_ATTRIBUTE -> {
693                             if (VALUE_TRUE == value) {
694                                 attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE))
695                             }
696                         }
697                         else -> { error("Unrecognized element: " + elementName) }
698                     }
699                 }
700                 val children = getChildren(annotationElement)
701                 parseChild(children[0])
702                 if (children.size == 2) {
703                     parseChild(children[1])
704                 }
705                 val intDef = INT_DEF_ANNOTATION.oldName() == name ||
706                     INT_DEF_ANNOTATION.newName() == name ||
707                     ANDROID_INT_DEF == name
708                 return PsiAnnotationItem.create(
709                     codebase,
710                     XmlBackedAnnotationItem(
711                         codebase,
712                         if (intDef) INT_DEF_ANNOTATION.newName() else STRING_DEF_ANNOTATION.newName(), attributes
713                     )
714                 )
715             }
716 
717             name == IDEA_CONTRACT -> {
718                 val children = getChildren(annotationElement)
719                 val valueElement = children[0]
720                 val value = valueElement.getAttribute(ATTR_VAL)
721                 val pure = valueElement.getAttribute(ATTR_PURE)
722                 return if (pure != null && pure.isNotEmpty()) {
723                     PsiAnnotationItem.create(
724                         codebase,
725                         XmlBackedAnnotationItem(
726                             codebase, name,
727                             listOf(
728                                 XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value),
729                                 XmlBackedAnnotationAttribute(ATTR_PURE, pure)
730                             )
731                         )
732                     )
733                 } else {
734                     PsiAnnotationItem.create(
735                         codebase,
736                         XmlBackedAnnotationItem(
737                             codebase, name,
738                             listOf(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value))
739                         )
740                     )
741                 }
742             }
743 
744             isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NONNULL")
745 
746             isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE")
747 
748             else -> {
749                 val children = getChildren(annotationElement)
750                 if (children.isEmpty()) {
751                     return codebase.createAnnotation("@$name")
752                 }
753                 val attributes = mutableListOf<XmlBackedAnnotationAttribute>()
754                 for (valueElement in children) {
755                     attributes.add(
756                         XmlBackedAnnotationAttribute(
757                             valueElement.getAttribute(ATTR_NAME) ?: continue,
758                             valueElement.getAttribute(ATTR_VAL) ?: continue
759                         )
760                     )
761                 }
762                 return PsiAnnotationItem.create(codebase, XmlBackedAnnotationItem(codebase, name, attributes))
763             }
764         }
765     }
766 
767     private fun isNonNull(name: String): Boolean {
768         return name == IDEA_NOTNULL ||
769             name == ANDROID_NOTNULL ||
770             name == ANDROIDX_NONNULL ||
771             name == SUPPORT_NOTNULL
772     }
773 
774     private fun isNullable(name: String): Boolean {
775         return name == IDEA_NULLABLE ||
776             name == ANDROID_NULLABLE ||
777             name == ANDROIDX_NULLABLE ||
778             name == SUPPORT_NULLABLE
779     }
780 
781     private fun unescapeXml(escaped: String): String {
782         var workingString = escaped.replace(QUOT_ENTITY, "\"")
783         workingString = workingString.replace(LT_ENTITY, "<")
784         workingString = workingString.replace(GT_ENTITY, ">")
785         workingString = workingString.replace(APOS_ENTITY, "'")
786         workingString = workingString.replace(AMP_ENTITY, "&")
787 
788         return workingString
789     }
790 }
791 
792 // TODO: Replace with usage of DefaultAnnotationValue?
793 data class XmlBackedAnnotationAttribute(
794     override val name: String,
795     private val valueLiteral: String
796 ) : AnnotationAttribute {
797     override val value: AnnotationAttributeValue = DefaultAnnotationValue.create(valueLiteral)
798 
toStringnull799     override fun toString(): String {
800         return "$name=$valueLiteral"
801     }
802 }
803 
804 // TODO: Replace with usage of DefaultAnnotationAttribute?
805 class XmlBackedAnnotationItem(
806     codebase: Codebase,
807     override val originalName: String,
808     override val attributes: List<XmlBackedAnnotationAttribute> = emptyList()
809 ) : DefaultAnnotationItem(codebase) {
810     override val qualifiedName: String? = AnnotationItem.mapName(codebase, originalName)
811 
toSourcenull812     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
813         val qualifiedName = AnnotationItem.mapName(codebase, qualifiedName, null, target) ?: return ""
814 
815         if (attributes.isEmpty()) {
816             return "@$qualifiedName"
817         }
818 
819         val sb = StringBuilder(30)
820         sb.append("@")
821         sb.append(qualifiedName)
822         sb.append("(")
823         attributes.joinTo(sb)
824         sb.append(")")
825 
826         return sb.toString()
827     }
828 }
829