• 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.LT_ENTITY
28 import com.android.SdkConstants.QUOT_ENTITY
29 import com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE
30 import com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE
31 import com.android.tools.lint.annotations.Extractor.ANDROID_INT_DEF
32 import com.android.tools.lint.annotations.Extractor.ANDROID_NOTNULL
33 import com.android.tools.lint.annotations.Extractor.ANDROID_NULLABLE
34 import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF
35 import com.android.tools.lint.annotations.Extractor.ATTR_PURE
36 import com.android.tools.lint.annotations.Extractor.ATTR_VAL
37 import com.android.tools.lint.annotations.Extractor.IDEA_CONTRACT
38 import com.android.tools.lint.annotations.Extractor.IDEA_MAGIC
39 import com.android.tools.lint.annotations.Extractor.IDEA_NOTNULL
40 import com.android.tools.lint.annotations.Extractor.IDEA_NULLABLE
41 import com.android.tools.lint.annotations.Extractor.SUPPORT_NOTNULL
42 import com.android.tools.lint.annotations.Extractor.SUPPORT_NULLABLE
43 import com.android.tools.lint.detector.api.getChildren
44 import com.android.tools.metalava.cli.common.cliError
45 import com.android.tools.metalava.model.ANDROIDX_INT_DEF
46 import com.android.tools.metalava.model.ANDROIDX_NONNULL
47 import com.android.tools.metalava.model.ANDROIDX_NULLABLE
48 import com.android.tools.metalava.model.ANDROIDX_STRING_DEF
49 import com.android.tools.metalava.model.ANDROID_FLAGGED_API
50 import com.android.tools.metalava.model.ANNOTATION_VALUE_TRUE
51 import com.android.tools.metalava.model.AnnotationAttribute
52 import com.android.tools.metalava.model.AnnotationItem
53 import com.android.tools.metalava.model.ClassItem
54 import com.android.tools.metalava.model.Codebase
55 import com.android.tools.metalava.model.DefaultAnnotationAttribute
56 import com.android.tools.metalava.model.DefaultAnnotationItem
57 import com.android.tools.metalava.model.Item
58 import com.android.tools.metalava.model.PackageItem
59 import com.android.tools.metalava.model.SelectableItem
60 import com.android.tools.metalava.model.TraversingVisitor
61 import com.android.tools.metalava.model.TypeNullability
62 import com.android.tools.metalava.model.source.SourceParser
63 import com.android.tools.metalava.model.source.SourceSet
64 import com.android.tools.metalava.model.text.ApiFile
65 import com.android.tools.metalava.model.text.ApiParseException
66 import com.android.tools.metalava.model.text.SignatureFile
67 import com.android.tools.metalava.model.typeNullability
68 import com.android.tools.metalava.model.visitors.ApiVisitor
69 import com.android.tools.metalava.reporter.Issues
70 import com.android.tools.metalava.reporter.Reporter
71 import com.android.tools.metalava.xml.parseDocument
72 import com.google.common.io.Closeables
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 import org.w3c.dom.Document
82 import org.w3c.dom.Element
83 import org.xml.sax.SAXParseException
84 
85 /** Merges annotations into classes already registered in the given [Codebase] */
86 @Suppress("DEPRECATION")
87 class AnnotationsMerger(
88     private val sourceParser: SourceParser,
89     private val codebase: Codebase,
90     private val reporter: Reporter,
91 ) {
92 
93     /** Merge annotations which will appear in the output API. */
94     fun mergeQualifierAnnotationsFromFiles(files: List<File>) {
95         mergeAll(
96             files,
97             ::mergeQualifierAnnotationsFromFile,
98             ::mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase
99         )
100     }
101 
102     /** Merge annotations which control what is included in the output API. */
103     fun mergeInclusionAnnotationsFromFiles(files: List<File>) {
104         mergeAll(
105             files,
106             { cliError("External inclusion annotations files must be .java, found ${it.path}") },
107             ::mergeInclusionAnnotationsFromCodebase
108         )
109     }
110 
111     /**
112      * Given a list of directories containing various files, scan those files merging them into the
113      * [codebase].
114      *
115      * All `.java` files are collated and
116      */
117     private fun mergeAll(
118         mergeAnnotations: List<File>,
119         mergeFile: (File) -> Unit,
120         mergeJavaStubsCodebase: (Codebase) -> Unit
121     ) {
122         // Process each file (which are almost certainly directories) separately. That allows for a
123         // single Java class to merge in annotations from multiple separate files.
124         mergeAnnotations.forEach {
125             val javaStubFiles = mutableListOf<File>()
126             mergeFileOrDir(it, mergeFile, javaStubFiles)
127             if (javaStubFiles.isNotEmpty()) {
128                 // Set up class path to contain our main sources such that we can
129                 // resolve types in the stubs
130                 val roots =
131                     SourceSet(options.sources, options.sourcePath).extractRoots(reporter).sourcePath
132                 val javaStubsCodebase =
133                     sourceParser.parseSources(
134                         SourceSet(javaStubFiles, roots),
135                         "Codebase loaded from stubs",
136                         classPath = options.classpath,
137                         apiPackages = options.apiPackages,
138                         projectDescription = null,
139                     )
140                 mergeJavaStubsCodebase(javaStubsCodebase)
141             }
142         }
143     }
144 
145     /**
146      * Merges annotations from `file`, or from all the files under it if `file` is a directory. All
147      * files apart from Java stub files are merged using [mergeFile]. Java stub files are not merged
148      * by this method, instead they are added to [javaStubFiles] and should be merged later (so that
149      * all the Java stubs can be loaded as a single codebase).
150      */
151     private fun mergeFileOrDir(
152         file: File,
153         mergeFile: (File) -> Unit,
154         javaStubFiles: MutableList<File>
155     ) {
156         if (file.isDirectory) {
157             val files = file.listFiles()
158             if (files != null) {
159                 for (child in files) {
160                     mergeFileOrDir(child, mergeFile, javaStubFiles)
161                 }
162             }
163         } else if (file.isFile) {
164             if (file.path.endsWith(".java")) {
165                 javaStubFiles.add(file)
166             } else {
167                 mergeFile(file)
168             }
169         }
170     }
171 
172     private fun mergeQualifierAnnotationsFromFile(file: File) {
173         if (file.path.endsWith(DOT_JAR) || file.path.endsWith(DOT_ZIP)) {
174             mergeFromJar(file)
175         } else if (file.path.endsWith(DOT_XML)) {
176             try {
177                 val xml = file.readText()
178                 mergeQualifierAnnotationsFromXml(file.path, xml)
179             } catch (e: IOException) {
180                 error("I/O problem during transform: $e")
181             }
182         } else if (
183             file.path.endsWith(".txt") ||
184                 file.path.endsWith(".signatures") ||
185                 file.path.endsWith(".api")
186         ) {
187             try {
188                 // .txt: Old style signature files
189                 // Others: new signature files (e.g. kotlin-style nullness info)
190                 mergeQualifierAnnotationsFromSignatureFile(file)
191             } catch (e: IOException) {
192                 error("I/O problem during transform: $e")
193             }
194         }
195     }
196 
197     private fun mergeFromJar(jar: File) {
198         // Reads in an existing annotations jar and merges in entries found there
199         // with the annotations analyzed from source.
200         var zis: JarInputStream? = null
201         try {
202             val fis = FileInputStream(jar)
203             zis = JarInputStream(fis)
204             var entry: ZipEntry? = zis.nextEntry
205             while (entry != null) {
206                 if (entry.name.endsWith(".xml")) {
207                     val bytes = zis.readBytes()
208                     val xml = String(bytes, UTF_8)
209                     mergeQualifierAnnotationsFromXml(jar.path + ": " + entry, xml)
210                 }
211                 entry = zis.nextEntry
212             }
213         } catch (e: IOException) {
214             error("I/O problem during transform: $e")
215         } finally {
216             try {
217                 Closeables.close(zis, true /* swallowIOException */)
218             } catch (e: IOException) {
219                 // cannot happen
220             }
221         }
222     }
223 
224     private fun mergeQualifierAnnotationsFromXml(path: String, xml: String) {
225         try {
226             val document = parseDocument(xml, false)
227             mergeDocument(document)
228         } catch (e: Exception) {
229             var message = "Failed to merge $path: $e"
230             if (e is SAXParseException) {
231                 message = "Line ${e.lineNumber}:${e.columnNumber}: $message"
232             }
233             if (e !is IOException) {
234                 message += "\n" + e.stackTraceToString().prependIndent("  ")
235             }
236             error(message)
237         }
238     }
239 
240     private fun mergeQualifierAnnotationsFromSignatureFile(file: File) {
241         try {
242             val signatureCodebase =
243                 ApiFile.parseApi(
244                     SignatureFile.fromFiles(file),
245                     codebase.config,
246                     "Signature files for annotation merger: loaded from $file"
247                 )
248             mergeQualifierAnnotationsFromCodebase(signatureCodebase)
249         } catch (ex: ApiParseException) {
250             val message = "Unable to parse signature file $file: ${ex.message}"
251             cliError(message)
252         }
253     }
254 
255     private fun mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase(
256         javaStubsCodebase: Codebase
257     ) {
258         mergeQualifierAnnotationsFromCodebase(javaStubsCodebase)
259         if (options.validateNullabilityFromMergedStubs) {
260             options.nullabilityAnnotationsValidator?.validateAll(
261                 codebase,
262                 javaStubsCodebase.getTopLevelClassesFromSource().map(ClassItem::qualifiedName)
263             )
264         }
265     }
266 
267     private fun mergeQualifierAnnotationsFromCodebase(externalCodebase: Codebase) {
268         val visitor =
269             object : ComparisonVisitor() {
270                 override fun compareItems(old: Item, new: Item) {
271                     val itemAnnotations = old.modifiers.annotations()
272                     mergeQualifierAnnotations(itemAnnotations, new)
273                     old.type()?.let {
274                         // Ignore type annotations that are duplicates of the item's annotations.
275                         val typeAnnotations =
276                             it.modifiers.annotations.filter { it !in itemAnnotations }
277                         mergeQualifierAnnotations(typeAnnotations, new)
278                     }
279                 }
280 
281                 override fun removedItem(old: SelectableItem, from: SelectableItem?) {
282                     // Do not report missing items if there are no annotations to copy.
283                     if (old.modifiers.annotations().isEmpty()) {
284                         old.type()?.let { typeItem ->
285                             if (typeItem.modifiers.annotations.isEmpty()) return
286                         }
287                             ?: return
288                     }
289 
290                     reporter.report(
291                         Issues.UNMATCHED_MERGE_ANNOTATION,
292                         old,
293                         "qualifier annotations were given for $old but no matching item was found"
294                     )
295                 }
296             }
297 
298         CodebaseComparator().compare(visitor, externalCodebase, codebase)
299     }
300 
301     private fun mergeInclusionAnnotationsFromCodebase(externalCodebase: Codebase) {
302         val visitor =
303             object : TraversingVisitor() {
304                 override fun visitItem(item: Item): TraversalAction {
305                     // Find any show/hide annotations or FlaggedApi annotations to merge from the
306                     // external to the main codebase. If there are none to copy then return.
307                     val annotationsToMerge =
308                         item.modifiers.annotations().filter { annotation ->
309                             val qualifiedName = annotation.qualifiedName
310                             annotation.isShowabilityAnnotation() ||
311                                 qualifiedName == ANDROID_FLAGGED_API
312                         }
313                     if (annotationsToMerge.isEmpty()) {
314                         // Just because there are no annotations on an [Item] does not mean that
315                         // there will not be on the children so make sure to visit them as normal.
316                         return TraversalAction.CONTINUE
317                     }
318 
319                     // Find the item to which the annotations should be copied, reporting an issue
320                     // if it could not be found.
321                     val mainItem =
322                         item.findCorrespondingItemIn(codebase)
323                             ?: run {
324                                 reporter.report(
325                                     Issues.UNMATCHED_MERGE_ANNOTATION,
326                                     item,
327                                     "inclusion annotations were given for $item but no matching item was found"
328                                 )
329 
330                                 // If an [Item] cannot be found then there is no point in visiting
331                                 // its children as they will not be found either.
332                                 return TraversalAction.SKIP_CHILDREN
333                             }
334 
335                     // Merge the annotations to the main item, ignoring any that match, i.e. are of
336                     // the same type as, an existing annotation.
337                     mainItem.mutateModifiers {
338                         mutateAnnotations {
339                             for (annotation in annotationsToMerge) {
340                                 val qualifiedName = annotation.qualifiedName
341                                 if (none { it.qualifiedName == qualifiedName }) {
342                                     // TODO: This simply uses the AnnotationItem from the Codebase
343                                     //  being merged from in the Codebase being merged into. That is
344                                     //  not safe as the Codebases may be from different models.
345                                     add(annotation)
346                                 }
347                             }
348                         }
349                     }
350 
351                     return TraversalAction.CONTINUE
352                 }
353             }
354         externalCodebase.accept(visitor)
355     }
356 
357     internal fun error(message: String) {
358         reporter.report(Issues.INTERNAL_ERROR, reportable = null, message)
359     }
360 
361     internal fun warning(message: String) {
362         if (options.verbose) {
363             options.stdout.println("Warning: $message")
364         }
365     }
366 
367     @Suppress("PrivatePropertyName")
368     private val XML_SIGNATURE: Pattern =
369         Pattern.compile(
370             // Class (FieldName | Type? Name(ArgList) Argnum?)
371             // "(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)");
372             "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)"
373         )
374 
375     private fun mergeDocument(document: Document) {
376         val root = document.documentElement
377         val rootTag = root.tagName
378         assert(rootTag == "root") { rootTag }
379 
380         for (item in getChildren(root)) {
381             var signature: String? = item.getAttribute(ATTR_NAME)
382             if (signature == null || signature == "null") {
383                 continue // malformed item
384             }
385 
386             signature = unescapeXml(signature)
387 
388             val matcher = XML_SIGNATURE.matcher(signature)
389             if (matcher.matches()) {
390                 val containingClass = matcher.group(1)
391                 if (containingClass == null) {
392                     warning("Could not find class for $signature")
393                     continue
394                 }
395 
396                 val classItem = codebase.findClass(containingClass)
397                 if (classItem == null) {
398                     // Well known exceptions from IntelliJ's external annotations
399                     // we won't complain loudly about
400                     if (wellKnownIgnoredImport(containingClass)) {
401                         continue
402                     }
403 
404                     warning("Could not find class $containingClass; omitting annotation from merge")
405                     continue
406                 }
407 
408                 val methodName = matcher.group(5)
409                 if (methodName != null) {
410                     val parameters = matcher.group(6)
411                     val parameterIndex =
412                         if (matcher.group(7) != null) {
413                             Integer.parseInt(matcher.group(7).trim())
414                         } else {
415                             -1
416                         }
417                     mergeMethodOrParameter(
418                         item,
419                         containingClass,
420                         classItem,
421                         methodName,
422                         parameterIndex,
423                         parameters
424                     )
425                 } else {
426                     val fieldName = matcher.group(2)
427                     mergeField(item, containingClass, classItem, fieldName)
428                 }
429             } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) {
430                 // Must be just a class
431                 val containingClass = signature
432                 val classItem = codebase.findClass(containingClass)
433                 if (classItem == null) {
434                     if (wellKnownIgnoredImport(containingClass)) {
435                         continue
436                     }
437 
438                     warning("Could not find class $containingClass; omitting annotation from merge")
439                     continue
440                 }
441 
442                 mergeQualifierAnnotationsFromXmlElement(item, classItem)
443             } else {
444                 warning("No merge match for signature $signature")
445             }
446         }
447     }
448 
449     private fun wellKnownIgnoredImport(containingClass: String): Boolean {
450         if (
451             containingClass.startsWith("javax.swing.") ||
452                 containingClass.startsWith("javax.naming.") ||
453                 containingClass.startsWith("java.awt.") ||
454                 containingClass.startsWith("org.jdom.")
455         ) {
456             return true
457         }
458         return false
459     }
460 
461     // The parameter declaration used in XML files should not have duplicated spaces,
462     // and there should be no space after commas (we can't however strip out all spaces,
463     // since for example the spaces around the "extends" keyword needs to be there in
464     // types like Map<String,? extends Number>
465     private fun fixParameterString(parameters: String): String {
466         return parameters
467             .replace("  ", " ")
468             .replace(", ", ",")
469             .replace("?super", "? super ")
470             .replace("?extends", "? extends ")
471     }
472 
473     private fun mergeMethodOrParameter(
474         item: Element,
475         containingClass: String,
476         classItem: ClassItem,
477         methodName: String,
478         parameterIndex: Int,
479         parameters: String
480     ) {
481         @Suppress("NAME_SHADOWING") val parameters = fixParameterString(parameters)
482 
483         val callableItem = classItem.findCallable(methodName, parameters)
484         if (callableItem == null) {
485             if (wellKnownIgnoredImport(containingClass)) {
486                 return
487             }
488 
489             warning(
490                 "Could not find method $methodName($parameters) in $containingClass; omitting annotation from merge"
491             )
492             return
493         }
494 
495         if (parameterIndex != -1) {
496             val parameterItem = callableItem.parameters()[parameterIndex]
497             mergeQualifierAnnotationsFromXmlElement(item, parameterItem)
498         } else {
499             // Annotation on the method itself
500             mergeQualifierAnnotationsFromXmlElement(item, callableItem)
501         }
502     }
503 
504     private fun mergeField(
505         item: Element,
506         containingClass: String,
507         classItem: ClassItem,
508         fieldName: String
509     ) {
510         val fieldItem = classItem.findField(fieldName)
511         if (fieldItem == null) {
512             if (wellKnownIgnoredImport(containingClass)) {
513                 return
514             }
515 
516             warning(
517                 "Could not find field $fieldName in $containingClass; omitting annotation from merge"
518             )
519             return
520         }
521 
522         mergeQualifierAnnotationsFromXmlElement(item, fieldItem)
523     }
524 
525     private fun getAnnotationName(element: Element): String {
526         val tagName = element.tagName
527         assert(tagName == "annotation") { tagName }
528 
529         val qualifiedName = element.getAttribute(ATTR_NAME)
530         assert(qualifiedName.isNotEmpty())
531         return qualifiedName
532     }
533 
534     private fun mergeQualifierAnnotationsFromXmlElement(xmlElement: Element, item: Item) {
535         val annotationsToMerge =
536             getChildren(xmlElement).mapNotNull { annotationElement ->
537                 createAnnotation(annotationElement)
538             }
539 
540         mergeQualifierAnnotations(annotationsToMerge, item)
541     }
542 
543     /**
544      * Reads in annotation data from an XML item (using IntelliJ IDE's external annotations XML
545      * format) and creates a corresponding [AnnotationItem], performing some "translations" in the
546      * process (e.g. mapping from IntelliJ annotations like `org.jetbrains.annotations.Nullable` to
547      * `androidx.annotation.Nullable`.
548      */
549     private fun createAnnotation(annotationElement: Element): AnnotationItem? {
550         val tagName = annotationElement.tagName
551         assert(tagName == "annotation") { tagName }
552         val name = annotationElement.getAttribute(ATTR_NAME)
553         assert(name.isNotEmpty())
554         when {
555             name == "org.jetbrains.annotations.Range" -> {
556                 val children = getChildren(annotationElement)
557                 assert(children.size == 2) { children.size }
558                 val valueElement1 = children[0]
559                 val valueElement2 = children[1]
560                 val valName1 = valueElement1.getAttribute(ATTR_NAME)
561                 val value1 = valueElement1.getAttribute(ATTR_VAL)
562                 val valName2 = valueElement2.getAttribute(ATTR_NAME)
563                 val value2 = valueElement2.getAttribute(ATTR_VAL)
564                 return DefaultAnnotationItem.create(
565                     codebase,
566                     "androidx.annotation.IntRange",
567                     listOf(
568                         // Add "L" suffix to ensure that we don't for example interpret "-1" as
569                         // an integer -1 and then end up recording it as "ffffffff" instead of
570                         // -1L
571                         DefaultAnnotationAttribute.create(
572                             valName1,
573                             value1 + (if (value1.last().isDigit()) "L" else "")
574                         ),
575                         DefaultAnnotationAttribute.create(
576                             valName2,
577                             value2 + (if (value2.last().isDigit()) "L" else "")
578                         )
579                     ),
580                 )
581             }
582             name == IDEA_MAGIC -> {
583                 val children = getChildren(annotationElement)
584                 assert(children.size == 1) { children.size }
585                 val valueElement = children[0]
586                 val valName = valueElement.getAttribute(ATTR_NAME)
587                 var value = valueElement.getAttribute(ATTR_VAL)
588                 val flagsFromClass = valName == "flagsFromClass"
589                 val flag = valName == "flags" || flagsFromClass
590                 if (valName == "valuesFromClass" || flagsFromClass) {
591                     // Not supported
592                     var found = false
593                     if (value.endsWith(DOT_CLASS)) {
594                         val clsName = value.substring(0, value.length - DOT_CLASS.length)
595                         val sb = StringBuilder()
596                         sb.append('{')
597 
598                         var reflectionFields: Array<Field>? = null
599                         try {
600                             val cls = Class.forName(clsName)
601                             reflectionFields = cls.declaredFields
602                         } catch (ignore: Exception) {
603                             // Class not available: not a problem. We'll rely on API filter.
604                             // It's mainly used for sorting anyway.
605                         }
606 
607                         // Attempt to sort in reflection order
608                         if (!found && reflectionFields != null) {
609                             val filterEmit =
610                                 ApiVisitor.defaultEmitFilter(
611                                     @Suppress("DEPRECATION") options.apiPredicateConfig,
612                                 )
613 
614                             // Attempt with reflection
615                             var first = true
616                             for (field in reflectionFields) {
617                                 if (
618                                     field.type == Integer.TYPE ||
619                                         field.type == Int::class.javaPrimitiveType
620                                 ) {
621                                     // Make sure this field is included in our API too
622                                     val fieldItem =
623                                         codebase.findClass(clsName)?.findField(field.name)
624                                     if (fieldItem == null || !filterEmit.test(fieldItem)) {
625                                         continue
626                                     }
627 
628                                     if (first) {
629                                         first = false
630                                     } else {
631                                         sb.append(',').append(' ')
632                                     }
633                                     sb.append(clsName).append('.').append(field.name)
634                                 }
635                             }
636                         }
637                         sb.append('}')
638                         value = sb.toString()
639                         if (sb.length > 2) { // 2: { }
640                             found = true
641                         }
642                     }
643 
644                     if (!found) {
645                         return null
646                     }
647                 }
648 
649                 val attributes = mutableListOf<AnnotationAttribute>()
650                 attributes.add(DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value))
651                 if (flag) {
652                     attributes.add(
653                         DefaultAnnotationAttribute.create(
654                             TYPE_DEF_FLAG_ATTRIBUTE,
655                             ANNOTATION_VALUE_TRUE
656                         )
657                     )
658                 }
659                 return DefaultAnnotationItem.create(
660                     codebase,
661                     if (valName == "stringValues") ANDROIDX_STRING_DEF else ANDROIDX_INT_DEF,
662                     attributes,
663                 )
664             }
665             name == ANDROIDX_STRING_DEF ||
666                 name == ANDROID_STRING_DEF ||
667                 name == ANDROIDX_INT_DEF ||
668                 name == ANDROID_INT_DEF -> {
669                 val attributes = mutableListOf<AnnotationAttribute>()
670                 val parseChild: (Element) -> Unit = { child: Element ->
671                     val elementName = child.getAttribute(ATTR_NAME)
672                     val value = child.getAttribute(ATTR_VAL)
673                     when (elementName) {
674                         TYPE_DEF_VALUE_ATTRIBUTE -> {
675                             attributes.add(
676                                 DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value)
677                             )
678                         }
679                         TYPE_DEF_FLAG_ATTRIBUTE -> {
680                             if (ANNOTATION_VALUE_TRUE == value) {
681                                 attributes.add(
682                                     DefaultAnnotationAttribute.create(
683                                         TYPE_DEF_FLAG_ATTRIBUTE,
684                                         ANNOTATION_VALUE_TRUE
685                                     )
686                                 )
687                             }
688                         }
689                         else -> {
690                             error("Unrecognized element: " + elementName)
691                         }
692                     }
693                 }
694                 val children = getChildren(annotationElement)
695                 parseChild(children[0])
696                 if (children.size == 2) {
697                     parseChild(children[1])
698                 }
699                 val intDef = ANDROIDX_INT_DEF == name || ANDROID_INT_DEF == name
700                 return DefaultAnnotationItem.create(
701                     codebase,
702                     if (intDef) ANDROIDX_INT_DEF else ANDROIDX_STRING_DEF,
703                     attributes,
704                 )
705             }
706             name == IDEA_CONTRACT -> {
707                 val children = getChildren(annotationElement)
708                 val valueElement = children[0]
709                 val value = valueElement.getAttribute(ATTR_VAL)
710                 val pure = valueElement.getAttribute(ATTR_PURE)
711                 return if (pure != null && pure.isNotEmpty()) {
712                     DefaultAnnotationItem.create(
713                         codebase,
714                         name,
715                         listOf(
716                             DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value),
717                             DefaultAnnotationAttribute.create(ATTR_PURE, pure)
718                         ),
719                     )
720                 } else {
721                     DefaultAnnotationItem.create(
722                         codebase,
723                         name,
724                         listOf(DefaultAnnotationAttribute.create(TYPE_DEF_VALUE_ATTRIBUTE, value)),
725                     )
726                 }
727             }
728             isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NONNULL")
729             isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE")
730             else -> {
731                 val children = getChildren(annotationElement)
732                 if (children.isEmpty()) {
733                     return codebase.createAnnotation("@$name")
734                 }
735                 val attributes = mutableListOf<AnnotationAttribute>()
736                 for (valueElement in children) {
737                     attributes.add(
738                         DefaultAnnotationAttribute.create(
739                             valueElement.getAttribute(ATTR_NAME) ?: continue,
740                             valueElement.getAttribute(ATTR_VAL) ?: continue
741                         )
742                     )
743                 }
744                 return DefaultAnnotationItem.create(codebase, name, attributes)
745             }
746         }
747     }
748 
749     private fun isNonNull(name: String): Boolean {
750         return name == IDEA_NOTNULL ||
751             name == ANDROID_NOTNULL ||
752             name == ANDROIDX_NONNULL ||
753             name == SUPPORT_NOTNULL
754     }
755 
756     private fun isNullable(name: String): Boolean {
757         return name == IDEA_NULLABLE ||
758             name == ANDROID_NULLABLE ||
759             name == ANDROIDX_NULLABLE ||
760             name == SUPPORT_NULLABLE
761     }
762 
763     /** Merge qualifier annotations in [annotations] into the [Item.modifiers] of [item]. */
764     private fun mergeQualifierAnnotations(annotations: List<AnnotationItem>, item: Item) {
765         if (annotations.isEmpty()) return
766 
767         // Check to make sure that the annotations are not adding a conflicting type nullability.
768         val nullabilityBefore = item.modifiers.annotations().typeNullability
769         if (nullabilityBefore != null) {
770             val mergeNullability = annotations.typeNullability
771             if (mergeNullability != null && mergeNullability != nullabilityBefore) {
772                 when (nullabilityBefore) {
773                     TypeNullability.NULLABLE ->
774                         reporter.report(
775                             Issues.INCONSISTENT_MERGE_ANNOTATION,
776                             item,
777                             "Merge conflict, has @Nullable (or equivalent) attempting to merge" +
778                                 " @NonNull (or equivalent)"
779                         )
780                     TypeNullability.NONNULL ->
781                         reporter.report(
782                             Issues.INCONSISTENT_MERGE_ANNOTATION,
783                             item,
784                             "Merge conflict, has @NonNull (or equivalent) attempting to merge" +
785                                 " @Nullable (or equivalent)"
786                         )
787                     else -> {}
788                 }
789             }
790         }
791 
792         item.mutateModifiers {
793             mutateAnnotations {
794                 for (annotation in annotations) {
795                     // If the item already has nullness annotations then ignore any others.
796                     if (nullabilityBefore != null && annotation.isNullnessAnnotation()) continue
797 
798                     // Ignore annotation that has the same type as an existing annotation.
799                     val qualifiedName = annotation.qualifiedName
800                     if (any { it.qualifiedName == qualifiedName }) continue
801 
802                     val annotationToMerge =
803                         item.codebase.createAnnotation(
804                             annotation.toSource(showDefaultAttrs = false),
805                             item,
806                         )
807                             ?: continue
808 
809                     add(annotationToMerge)
810                 }
811             }
812         }
813 
814         // Update the type nullability from the annotations, if necessary.
815         //
816         // Nullability annotations do not make sense on class definitions or in package-info.java
817         // files and in fact many nullability annotations do not support targeting them at all. Some
818         // nullability checkers do support annotating packages and classes with annotations to set
819         // the default nullability for unannotated types but Metalava does not currently support
820         // them. If it did then they would need special treatment here anyway so, for now we just
821         // ignore them.
822         if (item is ClassItem || item is PackageItem) return
823 
824         // Check to make sure that the item has a type.
825         val typeItem = item.type() ?: return
826 
827         // If the nullability after is null then nullability annotations cannot have been added so
828         // there is nothing to check.
829         val nullabilityAfter = item.modifiers.annotations().typeNullability ?: return
830 
831         // If the type nullability has changed then update the type nullability to match.
832         if (nullabilityAfter != nullabilityBefore) {
833             // Finally, duplicate the type with the new nullability.
834             item.setType(typeItem.substitute(nullabilityAfter))
835         }
836     }
837 
838     private fun unescapeXml(escaped: String): String {
839         var workingString = escaped.replace(QUOT_ENTITY, "\"")
840         workingString = workingString.replace(LT_ENTITY, "<")
841         workingString = workingString.replace(GT_ENTITY, ">")
842         workingString = workingString.replace(APOS_ENTITY, "'")
843         workingString = workingString.replace(AMP_ENTITY, "&")
844 
845         return workingString
846     }
847 }
848