• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 package com.android.tools.metalava
2 
3 import com.android.SdkConstants.ATTR_VALUE
4 import com.android.tools.metalava.model.AnnotationArrayAttributeValue
5 import com.android.tools.metalava.model.AnnotationAttribute
6 import com.android.tools.metalava.model.AnnotationItem
7 import com.android.tools.metalava.model.AnnotationSingleAttributeValue
8 import com.android.tools.metalava.model.DefaultAnnotationAttribute
9 
10 interface AnnotationFilter {
11     // tells whether an annotation is included by the filter
12     fun matches(annotation: AnnotationItem): Boolean
13     // tells whether an annotation is included by this filter
14     fun matches(annotationSource: String): Boolean
15 
16     // Returns a list of fully qualified annotation names that may be included by this filter.
17     // Note that this filter might incorporate parameters but this function strips them.
18     fun getIncludedAnnotationNames(): List<String>
19     // Returns true if [getIncludedAnnotationNames] includes the given qualified name
20     fun matchesAnnotationName(qualifiedName: String): Boolean
21     // Tells whether there exists an annotation that is accepted by this filter and that
22     // ends with the given suffix
23     fun matchesSuffix(annotationSuffix: String): Boolean
24     // Returns true if nothing is matched by this filter
25     fun isEmpty(): Boolean
26     // Returns true if some annotation is matched by this filter
27     fun isNotEmpty(): Boolean
28     // Returns the fully-qualified class name of the first annotation matched by this filter
29     fun firstQualifiedName(): String
30 }
31 
32 // Mutable implementation of AnnotationFilter
33 class MutableAnnotationFilter : AnnotationFilter {
34     private val inclusionExpressions = mutableListOf<AnnotationFilterEntry>()
35 
36     // Adds the given source as a fully qualified annotation name to match with this filter
37     // Can be "androidx.annotation.RestrictTo"
38     // Can be "androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP)"
39     // Note that the order of calls to this method could affect the return from
40     // {@link #firstQualifiedName} .
addnull41     fun add(source: String) {
42         inclusionExpressions.add(AnnotationFilterEntry.fromSource(source))
43     }
44 
matchesnull45     override fun matches(annotationSource: String): Boolean {
46         val annotationText = annotationSource.replace("@", "")
47         val wrapper = AnnotationFilterEntry.fromSource(annotationText)
48         return matches(wrapper)
49     }
50 
matchesnull51     override fun matches(annotation: AnnotationItem): Boolean {
52         if (annotation.qualifiedName == null) {
53             return false
54         }
55         val wrapper = AnnotationFilterEntry.fromAnnotationItem(annotation)
56         return matches(wrapper)
57     }
58 
matchesnull59     private fun matches(annotation: AnnotationFilterEntry): Boolean {
60         return inclusionExpressions.any { includedAnnotation ->
61             annotationsMatch(includedAnnotation, annotation)
62         }
63     }
64 
getIncludedAnnotationNamesnull65     override fun getIncludedAnnotationNames(): List<String> {
66         val annotationNames = mutableListOf<String>()
67         for (expression in inclusionExpressions) {
68             annotationNames.add(expression.qualifiedName)
69         }
70         return annotationNames
71     }
72 
73     /** Cache for [getIncludedAnnotationNames] since we call this method over and over again */
74     private var includedNames: List<String>? = null
75 
matchesAnnotationNamenull76     override fun matchesAnnotationName(qualifiedName: String): Boolean {
77         val includedNames = includedNames
78             ?: getIncludedAnnotationNames().also { includedNames = it }
79         return includedNames.contains(qualifiedName)
80     }
81 
matchesSuffixnull82     override fun matchesSuffix(annotationSuffix: String): Boolean {
83         return inclusionExpressions.any { included ->
84             included.qualifiedName.endsWith(annotationSuffix)
85         }
86     }
87 
isEmptynull88     override fun isEmpty(): Boolean {
89         return inclusionExpressions.isEmpty()
90     }
91 
isNotEmptynull92     override fun isNotEmpty(): Boolean {
93         return !isEmpty()
94     }
95 
firstQualifiedNamenull96     override fun firstQualifiedName(): String {
97         val inclusion = inclusionExpressions.first()
98         return inclusion.qualifiedName
99     }
100 
annotationsMatchnull101     private fun annotationsMatch(filter: AnnotationFilterEntry, existingAnnotation: AnnotationFilterEntry): Boolean {
102         if (filter.qualifiedName != existingAnnotation.qualifiedName) {
103             return false
104         }
105         if (filter.attributes.count() > existingAnnotation.attributes.count()) {
106             return false
107         }
108         for (attribute in filter.attributes) {
109             val existingValue = existingAnnotation.findAttribute(attribute.name)?.value
110             val existingValueSource = existingValue?.toSource()
111             val attributeValueSource = attribute.value.toSource()
112             if (attribute.name == "value") {
113                 // Special-case where varargs value annotation attribute can be specified with
114                 // either @Foo(BAR) or @Foo({BAR}) and they are equivalent.
115                 when {
116                     attribute.value is AnnotationSingleAttributeValue &&
117                         existingValue is AnnotationArrayAttributeValue -> {
118                         if (existingValueSource != "{$attributeValueSource}") return false
119                     }
120                     attribute.value is AnnotationArrayAttributeValue &&
121                         existingValue is AnnotationSingleAttributeValue -> {
122                         if ("{$existingValueSource}" != attributeValueSource) return false
123                     }
124                     else -> {
125                         if (existingValueSource != attributeValueSource) return false
126                     }
127                 }
128             } else {
129                 if (existingValueSource != attributeValueSource) {
130                     return false
131                 }
132             }
133         }
134         return true
135     }
136 }
137 
138 // An AnnotationFilterEntry filters for annotations having a certain qualifiedName and
139 // possibly certain attributes.
140 // An AnnotationFilterEntry doesn't necessarily have a Codebase like an AnnotationItem does
141 private class AnnotationFilterEntry(
142     val qualifiedName: String,
143     val attributes: List<AnnotationAttribute>
144 ) {
findAttributenull145     fun findAttribute(name: String?): AnnotationAttribute? {
146         val actualName = name ?: ATTR_VALUE
147         return attributes.firstOrNull { it.name == actualName }
148     }
149 
150     companion object {
fromSourcenull151         fun fromSource(source: String): AnnotationFilterEntry {
152             val text = source.replace("@", "")
153             val index = text.indexOf("(")
154 
155             val qualifiedName = if (index == -1) {
156                 text
157             } else {
158                 text.substring(0, index)
159             }
160 
161             val attributes: List<AnnotationAttribute> = if (index == -1) {
162                 emptyList()
163             } else {
164                 DefaultAnnotationAttribute.createList(
165                     text.substring(index + 1, text.lastIndexOf(')'))
166                 )
167             }
168             return AnnotationFilterEntry(qualifiedName, attributes)
169         }
170 
fromAnnotationItemnull171         fun fromAnnotationItem(annotationItem: AnnotationItem): AnnotationFilterEntry {
172             // Have to call toSource to resolve attribute values into fully qualified class names.
173             // For example: resolving RestrictTo(LIBRARY_GROUP) into
174             // RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP)
175             // In addition, toSource (with the default argument showDefaultAttrs=true) retrieves
176             // default attributes from the definition of the annotation. For example,
177             // @SystemApi actually is converted into @android.annotation.SystemApi(\
178             // client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,\
179             // process=android.annotation.SystemApi.Process.ALL)
180             return AnnotationFilterEntry.fromSource(annotationItem.toSource())
181         }
182     }
183 }
184