1 /*
2  * Copyright (C) 2023 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.model
18 
19 /** Provides support for managing annotations within Metalava. */
20 interface AnnotationManager {
21 
22     /** Get the [AnnotationInfo] for the specified [annotation]. */
getAnnotationInfonull23     fun getAnnotationInfo(annotation: AnnotationItem): AnnotationInfo
24 
25     /**
26      * Maps an annotation name to the name to be used internally.
27      *
28      * Annotations that should not be used internally are mapped to null.
29      */
30     fun normalizeInputName(qualifiedName: String?): String?
31 
32     /**
33      * Maps an annotation name to the name to be used in signatures/stubs/external annotation files.
34      * Annotations that should not be exported are mapped to null.
35      */
36     fun normalizeOutputName(
37         qualifiedName: String?,
38         target: AnnotationTarget = AnnotationTarget.SIGNATURE_FILE
39     ): String?
40 
41     /** Get the applicable targets for the annotation */
42     fun computeTargets(
43         annotation: AnnotationItem,
44         classFinder: (String) -> ClassItem?
45     ): Set<AnnotationTarget>
46 
47     /** Returns true if [annotationName] is the name of one of the show annotations. */
48     fun isShowAnnotationName(annotationName: String): Boolean = false
49 
50     /**
51      * Checks to see if this has any show for stubs purposes annotations.
52      *
53      * Returns true if it has, false otherwise.
54      */
55     fun hasAnyStubPurposesAnnotations(): Boolean = false
56 
57     /**
58      * Get the [Showability] for the supplied [Item].
59      *
60      * This combines the [Showability] of all the annotations of this item and returns the result.
61      *
62      * If the annotations on the item conflict then this could throw an exception or report an error
63      * as appropriate.
64      */
65     fun getShowabilityForItem(item: Item): Showability = Showability.NO_EFFECT
66 
67     /**
68      * Checks to see if the modifiers contain any hide annotations.
69      *
70      * See [AnnotationItem.isHideAnnotation]
71      */
72     fun hasHideAnnotations(modifiers: ModifierList): Boolean = false
73 
74     /**
75      * Checks to see if the modifiers contain any suppress compatibility annotations.
76      *
77      * Returns `true` if it does, `false` otherwise. If `true` then the owning item (and any
78      * contents) will have their compatibility checks suppressed but they may still be written to
79      * API files or stub JARs.
80      *
81      * "Suppress compatibility" meta-annotations allow Metalava to handle concepts like Jetpack
82      * experimental APIs, where developers can use the [RequiresOptIn] meta-annotation to mark
83      * feature sets with unstable APIs.
84      */
85     fun hasSuppressCompatibilityMetaAnnotations(modifiers: ModifierList): Boolean = false
86 
87     /** Determine how to handle typedef annotations, i.e. annotations like `@IntDef`. */
88     val typedefMode: TypedefMode
89 }
90 
91 /**
92  * The default empty [AnnotationInfo] used when a more applicable one cannot be created, e.g. when
93  * the [AnnotationItem.qualifiedName] is `null`.
94  */
95 private val noInfoAvailable = AnnotationInfo(qualifiedName = "")
96 
97 /** Base class for [AnnotationManager] instances. */
98 abstract class BaseAnnotationManager : AnnotationManager {
99 
100     /**
101      * A map from the annotation key (returned by [getKeyForAnnotationItem]) to the corresponding
102      * [AnnotationInfo] (returned by [computeAnnotationInfo]).
103      */
104     private val annotationKeyToInfo = mutableMapOf<String, AnnotationInfo>()
105 
106     override fun getAnnotationInfo(annotation: AnnotationItem): AnnotationInfo {
107         annotation.qualifiedName ?: return noInfoAvailable
108         val key = getKeyForAnnotationItem(annotation)
109         val existing = annotationKeyToInfo[key]
110         if (existing != null) {
111             return existing
112         }
113         val info = computeAnnotationInfo(annotation)
114         annotationKeyToInfo[key] = info
115         return info
116     }
117 
118     /**
119      * Construct a key that differentiates between all instances of the annotation class with the
120      * same qualified name as [annotationItem] that have different [AnnotationInfo].
121      *
122      * e.g. if annotation `A()` and `A(value=2)` would both produce the same [AnnotationInfo]
123      * (because the `value` attribute is ignored when computing it) then they should just use `A` as
124      * the key. However, if they would produce different [AnnotationInfo] objects (because the
125      * `value` attribute is used when computing it) then they should create a key that includes the
126      * `value`.
127      *
128      * Note: it is safe to use `annotationItem.qualifiedName!!` as [AnnotationItem.qualifiedName] is
129      * guaranteed not to be `null` when this method is called.
130      */
131     protected abstract fun getKeyForAnnotationItem(annotationItem: AnnotationItem): String
132 
133     /**
134      * Compute an [AnnotationInfo] from the [annotationItem].
135      *
136      * This should only use attributes of the [annotationItem] if they are included in the key
137      * returned by [getKeyForAnnotationItem] for this [annotationItem].
138      *
139      * Note: it is safe to use `annotationItem.qualifiedName!!` as [AnnotationItem.qualifiedName] is
140      * guaranteed not to be `null` when this method is called.
141      */
142     protected abstract fun computeAnnotationInfo(annotationItem: AnnotationItem): AnnotationInfo
143 }
144 
145 /**
146  * A no op implementation of [AnnotationManager] that is suitable for use by the deprecated,
147  * external use only `ApiFile.parseApi(String,String,Boolean?)` and the for test only
148  * `ApiFile.parseApi(String,String,ClassResolver?)` methods.
149  *
150  * This is used when loading an API signature from a text file and makes the following assumptions:
151  * * The annotation names are correct and do not need mapping into another form.
152  * * The annotations can be used in all stubs.
153  */
154 internal class NoOpAnnotationManager : BaseAnnotationManager() {
155 
getKeyForAnnotationItemnull156     override fun getKeyForAnnotationItem(annotationItem: AnnotationItem): String {
157         // Just use the qualified name as the key as [computeAnnotationInfo] does not use anything
158         // else.
159         return annotationItem.qualifiedName!!
160     }
161 
computeAnnotationInfonull162     override fun computeAnnotationInfo(annotationItem: AnnotationItem): AnnotationInfo {
163         return AnnotationInfo(annotationItem.qualifiedName!!)
164     }
165 
normalizeInputNamenull166     override fun normalizeInputName(qualifiedName: String?): String? {
167         return qualifiedName
168     }
169 
normalizeOutputNamenull170     override fun normalizeOutputName(qualifiedName: String?, target: AnnotationTarget): String? {
171         return qualifiedName
172     }
173 
computeTargetsnull174     override fun computeTargets(
175         annotation: AnnotationItem,
176         classFinder: (String) -> ClassItem?
177     ): Set<AnnotationTarget> = ANNOTATION_IN_ALL_STUBS
178 
179     override val typedefMode: TypedefMode = TypedefMode.NONE
180 }
181 
182 val noOpAnnotationManager: AnnotationManager = NoOpAnnotationManager()
183