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