<lambda>null1 package com.android.tools.metalava
2
3 import com.android.SdkConstants.ATTR_VALUE
4 import com.android.tools.metalava.model.AnnotationAttribute
5 import com.android.tools.metalava.model.AnnotationItem
6 import com.android.tools.metalava.model.DefaultAnnotationAttribute
7
8 interface AnnotationFilter {
9 // tells whether an annotation is included by the filter
10 fun matches(annotation: AnnotationItem): Boolean
11 // tells whether an annotation is included by this filter
12 fun matches(annotationSource: String): Boolean
13
14 // Returns a list of fully qualified annotation names that may be included by this filter.
15 // Note that this filter might incorporate parameters but this function strips them.
16 fun getIncludedAnnotationNames(): List<String>
17 // Tells whether there exists an annotation that is accepted by this filter and that
18 // ends with the given suffix
19 fun matchesSuffix(annotationSuffix: String): Boolean
20 // Returns true if nothing is matched by this filter
21 fun isEmpty(): Boolean
22 // Returns true if some annotation is matched by this filter
23 fun isNotEmpty(): Boolean
24 // Returns the fully-qualified class name of the first annotation matched by this filter
25 fun firstQualifiedName(): String
26 }
27
28 // Mutable implementation of AnnotationFilter
29 class MutableAnnotationFilter : AnnotationFilter {
30 private val inclusionExpressions = mutableListOf<AnnotationFilterEntry>()
31
32 // Adds the given source as a fully qualified annotation name to match with this filter
33 // Can be "androidx.annotation.RestrictTo"
34 // Can be "androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP)"
35 // Note that the order of calls to this method could affect the return from
36 // {@link #firstQualifiedName} .
addnull37 fun add(source: String) {
38 inclusionExpressions.add(AnnotationFilterEntry.fromSource(source))
39 }
40
matchesnull41 override fun matches(annotationSource: String): Boolean {
42 val annotationText = annotationSource.replace("@", "")
43 val wrapper = AnnotationFilterEntry.fromSource(annotationText)
44 return matches(wrapper)
45 }
46
matchesnull47 override fun matches(annotation: AnnotationItem): Boolean {
48 if (annotation.qualifiedName() == null) {
49 return false
50 }
51 val wrapper = AnnotationFilterEntry.fromAnnotationItem(annotation)
52 return matches(wrapper)
53 }
54
matchesnull55 private fun matches(annotation: AnnotationFilterEntry): Boolean {
56 return inclusionExpressions.any { includedAnnotation ->
57 annotationsMatch(includedAnnotation, annotation)
58 }
59 }
60
getIncludedAnnotationNamesnull61 override fun getIncludedAnnotationNames(): List<String> {
62 val annotationNames = mutableListOf<String>()
63 for (expression in inclusionExpressions) {
64 annotationNames.add(expression.qualifiedName)
65 }
66 return annotationNames
67 }
68
matchesSuffixnull69 override fun matchesSuffix(annotationSuffix: String): Boolean {
70 return inclusionExpressions.any { included ->
71 included.qualifiedName.endsWith(annotationSuffix)
72 }
73 }
74
isEmptynull75 override fun isEmpty(): Boolean {
76 return inclusionExpressions.isEmpty()
77 }
78
isNotEmptynull79 override fun isNotEmpty(): Boolean {
80 return !isEmpty()
81 }
82
firstQualifiedNamenull83 override fun firstQualifiedName(): String {
84 val inclusion = inclusionExpressions.first()
85 return inclusion.qualifiedName
86 }
87
annotationsMatchnull88 private fun annotationsMatch(filter: AnnotationFilterEntry, existingAnnotation: AnnotationFilterEntry): Boolean {
89 if (filter.qualifiedName != existingAnnotation.qualifiedName) {
90 return false
91 }
92 if (filter.attributes.count() > existingAnnotation.attributes.count()) {
93 return false
94 }
95 for (attribute in filter.attributes) {
96 val existingValue = existingAnnotation.findAttribute(attribute.name)?.value?.toSource()
97 if (existingValue != attribute.value.toSource()) {
98 return false
99 }
100 }
101 return true
102 }
103 }
104
105 // An AnnotationFilterEntry filters for annotations having a certain qualifiedName and
106 // possibly certain attributes.
107 // An AnnotationFilterEntry doesn't necessarily have a Codebase like an AnnotationItem does
108 private class AnnotationFilterEntry(
109 val qualifiedName: String,
110 val attributes: List<AnnotationAttribute>
111 ) {
findAttributenull112 fun findAttribute(name: String?): AnnotationAttribute? {
113 val actualName = name ?: ATTR_VALUE
114 return attributes.firstOrNull { it.name == actualName }
115 }
116
117 companion object {
fromSourcenull118 fun fromSource(source: String): AnnotationFilterEntry {
119 val text = source.replace("@", "")
120 val index = text.indexOf("(")
121
122 val qualifiedName = if (index == -1) {
123 text
124 } else {
125 text.substring(0, index)
126 }
127
128 val attributes: List<AnnotationAttribute> = if (index == -1) {
129 emptyList()
130 } else {
131 DefaultAnnotationAttribute.createList(
132 text.substring(index + 1, text.lastIndexOf(')'))
133 )
134 }
135 return AnnotationFilterEntry(qualifiedName, attributes)
136 }
137
fromAnnotationItemnull138 fun fromAnnotationItem(annotationItem: AnnotationItem): AnnotationFilterEntry {
139 // Have to call toSource to resolve attribute values into fully qualified class names.
140 // For example: resolving RestrictTo(LIBRARY_GROUP) into
141 // RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP)
142 // In addition, toSource (with the default argument showDefaultAttrs=true) retrieves
143 // default attributes from the definition of the annotation. For example,
144 // @SystemApi actually is converted into @android.annotation.SystemApi(\
145 // client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,\
146 // process=android.annotation.SystemApi.Process.ALL)
147 return AnnotationFilterEntry.fromSource(annotationItem.toSource())
148 }
149 }
150 }
151