<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