• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.model
18 
19 import com.android.tools.metalava.reporter.BaselineKey
20 import com.android.tools.metalava.reporter.FileLocation
21 import com.android.tools.metalava.reporter.Reportable
22 import java.util.concurrent.atomic.AtomicInteger
23 
24 /**
25  * Represents a code element such as a package, a class, a method, a field, a parameter.
26  *
27  * This extra abstraction on top of PSI allows us to more model the API (and customize visibility,
28  * which cannot always be done by looking at a particular piece of code and examining visibility
29  * and @hide/@removed annotations: sometimes package private APIs are unhidden by being used in
30  * public APIs for example.
31  *
32  * The abstraction also lets us back the model by an alternative implementation read from signature
33  * files, to do compatibility checks.
34  */
35 interface Item : Reportable {
36     val codebase: Codebase
37 
38     /** Return the modifiers of this class */
39     @MetalavaApi val modifiers: ModifierList
40 
41     /**
42      * Whether this element was originally hidden with @hide/@Hide. The [hidden] property tracks
43      * whether it is *actually* hidden, since elements can be unhidden via show annotations, etc.
44      */
45     var originallyHidden: Boolean
46 
47     /**
48      * Whether this element has been hidden with @hide/@Hide (or after propagation, in some
49      * containing class/pkg)
50      */
51     var hidden: Boolean
52 
53     /** Whether this element will be printed in the signature file */
54     var emit: Boolean
55 
parentnull56     fun parent(): Item?
57 
58     /**
59      * Recursive check to see if this item or any of its parents (containing class, containing
60      * package) are hidden
61      */
62     fun hidden(): Boolean {
63         return hidden || parent()?.hidden() ?: false
64     }
65 
66     /**
67      * Recursive check to see if compatibility checks should be suppressed for this item or any of
68      * its parents (containing class, containing package).
69      */
isCompatibilitySuppressednull70     fun isCompatibilitySuppressed(): Boolean {
71         return hasSuppressCompatibilityMetaAnnotation() ||
72             parent()?.isCompatibilitySuppressed() ?: false
73     }
74 
75     /**
76      * Whether this element has been removed with @removed/@Remove (or after propagation, in some
77      * containing class)
78      */
79     var removed: Boolean
80 
81     /** True if this item has been marked deprecated. */
82     val originallyDeprecated: Boolean
83 
84     /**
85      * True if this item has been marked as deprecated or is a descendant of a non-package item that
86      * has been marked as deprecated.
87      */
88     val effectivelyDeprecated: Boolean
89 
90     /** True if this element is only intended for documentation */
91     var docOnly: Boolean
92 
93     /** True if this item is either hidden or removed */
isHiddenOrRemovednull94     fun isHiddenOrRemoved(): Boolean = hidden || removed
95 
96     /** Visits this element using the given [visitor] */
97     fun accept(visitor: ItemVisitor)
98 
99     /** Get a mutable version of modifiers for this item */
100     fun mutableModifiers(): MutableModifierList
101 
102     /**
103      * The javadoc/KDoc comment for this code element, if any. This is the original content of the
104      * documentation, including lexical tokens to begin, continue and end the comment (such as /+*).
105      * See [fullyQualifiedDocumentation] to look up the documentation with fully qualified
106      * references to classes.
107      */
108     var documentation: String
109 
110     /**
111      * Looks up docs for the first instance of a specific javadoc tag having the (optionally)
112      * provided value (e.g. parameter name).
113      */
114     fun findTagDocumentation(tag: String, value: String? = null): String?
115 
116     /**
117      * A rank used for sorting. This allows signature files etc to sort similar items by a natural
118      * order, if non-zero. (Even though in signature files the elements are normally sorted first
119      * logically (constructors, then methods, then fields) and then alphabetically, this lets us
120      * preserve the source ordering for example for overloaded methods of the same name, where it's
121      * not clear that an alphabetical order (of each parameter?) would be preferable.)
122      */
123     val sortingRank: Int
124 
125     /**
126      * Add the given text to the documentation.
127      *
128      * If the [tagSection] is null, add the comment to the initial text block of the description.
129      * Otherwise if it is "@return", add the comment to the return value. Otherwise the [tagSection]
130      * is taken to be the parameter name, and the comment added as parameter documentation for the
131      * given parameter.
132      */
133     fun appendDocumentation(comment: String, tagSection: String? = null, append: Boolean = true)
134 
135     val isPublic: Boolean
136     val isProtected: Boolean
137     val isInternal: Boolean
138     val isPackagePrivate: Boolean
139     val isPrivate: Boolean
140 
141     // make sure these are implemented so we can place in maps:
142     override fun equals(other: Any?): Boolean
143 
144     override fun hashCode(): Int
145 
146     /** Calls [toStringForItem]. */
147     override fun toString(): String
148 
149     /** Provides a string representation of the item, suitable for use while debugging. */
150     fun toStringForItem(): String
151 
152     /**
153      * Whether this item was loaded from the classpath (e.g. jar dependencies) rather than be
154      * declared as source
155      */
156     fun isFromClassPath(): Boolean = false
157 
158     /** Is this element declared in Java (rather than Kotlin) ? */
159     fun isJava(): Boolean = true
160 
161     /** Is this element declared in Kotlin (rather than Java) ? */
162     fun isKotlin() = !isJava()
163 
164     /** Determines whether this item will be shown as part of the API or not. */
165     val showability: Showability
166 
167     /**
168      * Returns true if this item has any show annotations.
169      *
170      * See [Showability.show]
171      */
172     fun hasShowAnnotation(): Boolean = showability.show()
173 
174     /**
175      * Returns true if this has any show single annotations.
176      *
177      * See [Showability.recursive]
178      */
179     fun hasShowSingleAnnotation(): Boolean = showability.showNonRecursive()
180 
181     /** Returns true if this modifier list contains any hide annotations */
182     fun hasHideAnnotation(): Boolean =
183         modifiers.codebase.annotationManager.hasHideAnnotations(modifiers)
184 
185     fun hasSuppressCompatibilityMetaAnnotation(): Boolean =
186         modifiers.hasSuppressCompatibilityMetaAnnotations()
187 
188     fun sourceFile(): SourceFile? {
189         var curr: Item? = this
190         while (curr != null) {
191             if (curr is ClassItem && curr.isTopLevelClass()) {
192                 return curr.getSourceFile()
193             }
194             curr = curr.parent()
195         }
196 
197         return null
198     }
199 
200     override val fileLocation: FileLocation
201         get() = FileLocation.UNKNOWN
202 
203     /**
204      * Returns the [documentation], but with fully qualified links (except for the same package, and
205      * when turning a relative reference into a fully qualified reference, use the javadoc syntax
206      * for continuing to display the relative text, e.g. instead of {@link java.util.List}, use
207      * {@link java.util.List List}.
208      */
fullyQualifiedDocumentationnull209     fun fullyQualifiedDocumentation(): String = documentation
210 
211     /** Expands the given documentation comment in the current name context */
212     fun fullyQualifiedDocumentation(documentation: String): String = documentation
213 
214     /**
215      * Produces a user visible description of this item, including a label such as "class" or
216      * "field"
217      */
218     fun describe(capitalize: Boolean = false) = describe(this, capitalize)
219 
220     /** Returns the package that contains this item. */
221     fun containingPackage(): PackageItem?
222 
223     /** Returns the class that contains this item. */
224     fun containingClass(): ClassItem?
225 
226     /**
227      * Returns the associated type, if any.
228      *
229      * i.e.
230      * * For a field, property or parameter, this is the type of the variable.
231      * * For a method, it's the return type.
232      * * For classes it's the declared class type, i.e. a class type using the type parameter types
233      *   as the type arguments.
234      * * For type parameters it's a [VariableTypeItem] reference the type parameter.
235      * * For packages and files, it's null.
236      */
237     fun type(): TypeItem?
238 
239     /**
240      * Find the [Item] in [codebase] that corresponds to this item, or `null` if there is no such
241      * item.
242      *
243      * If [superMethods] is true and this is a [MethodItem] then the returned [MethodItem], if any,
244      * could be in a [ClassItem] that does not correspond to the [MethodItem.containingClass], it
245      * could be from a super class or super interface. e.g. if the [codebase] contains something
246      * like:
247      * ```
248      *     public class Super {
249      *         public void method() {...}
250      *     }
251      *     public class Foo extends Super {}
252      * ```
253      *
254      * And this is called on `Foo.method()` then:
255      * * if [superMethods] is false this will return `null`.
256      * * if [superMethods] is true and [duplicate] is false, then this will return `Super.method()`.
257      * * if both [superMethods] and [duplicate] are true then this will return a duplicate of
258      *   `Super.method()` that has been added to `Foo` so it will be essentially `Foo.method()`.
259      *
260      * @param codebase the [Codebase] to search for a corresponding item.
261      * @param superMethods if true and this is a [MethodItem] then this method will search for super
262      *   methods. If this is a [ParameterItem] then the value of this parameter will be passed to
263      *   the [findCorrespondingItemIn] call which is used to find the [MethodItem] corresponding to
264      *   the [ParameterItem.containingMethod].
265      * @param duplicate if true, and this is a [MemberItem] (or [ParameterItem]) then the returned
266      *   [Item], if any, will be in the [ClassItem] that corresponds to the [Item.containingClass].
267      *   This should be `true` if the returned [Item] is going to be compared to the original [Item]
268      *   as the [Item.containingClass] can affect that comparison, e.g. the meaning of certain
269      *   modifiers.
270      */
271     fun findCorrespondingItemIn(
272         codebase: Codebase,
273         superMethods: Boolean = false,
274         duplicate: Boolean = false,
275     ): Item?
276 
277     /**
278      * Get the set of suppressed issues for this [Item].
279      *
280      * These are the values supplied to any of the [SUPPRESS_ANNOTATIONS] on this item. It DOES not
281      * include suppressed issues from the [parent].
282      */
283     override fun suppressedIssues(): Set<String>
284 
285     /** The [BaselineKey] for this. */
286     override val baselineKey
287         get() = BaselineKey.forElementId(baselineElementId())
288 
289     /**
290      * Get the baseline element id from which [baselineKey] is constructed.
291      *
292      * See [BaselineKey.forElementId] for more details.
293      */
294     fun baselineElementId(): String
295 
296     companion object {
297         fun describe(item: Item, capitalize: Boolean = false): String {
298             return when (item) {
299                 is PackageItem -> describe(item, capitalize = capitalize)
300                 is ClassItem -> describe(item, capitalize = capitalize)
301                 is FieldItem -> describe(item, capitalize = capitalize)
302                 is MethodItem ->
303                     describe(
304                         item,
305                         includeParameterNames = false,
306                         includeParameterTypes = true,
307                         capitalize = capitalize
308                     )
309                 is ParameterItem ->
310                     describe(
311                         item,
312                         includeParameterNames = true,
313                         includeParameterTypes = true,
314                         capitalize = capitalize
315                     )
316                 else -> item.toString()
317             }
318         }
319 
320         fun describe(
321             item: MethodItem,
322             includeParameterNames: Boolean = false,
323             includeParameterTypes: Boolean = false,
324             includeReturnValue: Boolean = false,
325             capitalize: Boolean = false
326         ): String {
327             val builder = StringBuilder()
328             if (item.isConstructor()) {
329                 builder.append(if (capitalize) "Constructor" else "constructor")
330             } else {
331                 builder.append(if (capitalize) "Method" else "method")
332             }
333             builder.append(' ')
334             if (includeReturnValue && !item.isConstructor()) {
335                 builder.append(item.returnType().toSimpleType())
336                 builder.append(' ')
337             }
338             appendMethodSignature(builder, item, includeParameterNames, includeParameterTypes)
339             return builder.toString()
340         }
341 
342         fun describe(
343             item: ParameterItem,
344             includeParameterNames: Boolean = false,
345             includeParameterTypes: Boolean = false,
346             capitalize: Boolean = false
347         ): String {
348             val builder = StringBuilder()
349             builder.append(if (capitalize) "Parameter" else "parameter")
350             builder.append(' ')
351             builder.append(item.name())
352             builder.append(" in ")
353             val method = item.containingMethod()
354             appendMethodSignature(builder, method, includeParameterNames, includeParameterTypes)
355             return builder.toString()
356         }
357 
358         private fun appendMethodSignature(
359             builder: StringBuilder,
360             item: MethodItem,
361             includeParameterNames: Boolean,
362             includeParameterTypes: Boolean
363         ) {
364             builder.append(item.containingClass().qualifiedName())
365             if (!item.isConstructor()) {
366                 builder.append('.')
367                 builder.append(item.name())
368             }
369             if (includeParameterNames || includeParameterTypes) {
370                 builder.append('(')
371                 var first = true
372                 for (parameter in item.parameters()) {
373                     if (first) {
374                         first = false
375                     } else {
376                         builder.append(',')
377                         if (includeParameterNames && includeParameterTypes) {
378                             builder.append(' ')
379                         }
380                     }
381                     if (includeParameterTypes) {
382                         builder.append(parameter.type().toSimpleType())
383                         if (includeParameterNames) {
384                             builder.append(' ')
385                         }
386                     }
387                     if (includeParameterNames) {
388                         builder.append(parameter.publicName() ?: parameter.name())
389                     }
390                 }
391                 builder.append(')')
392             }
393         }
394 
395         private fun describe(item: FieldItem, capitalize: Boolean = false): String {
396             return if (item.isEnumConstant()) {
397                 "${if (capitalize) "Enum" else "enum"} constant ${item.containingClass().qualifiedName()}.${item.name()}"
398             } else {
399                 "${if (capitalize) "Field" else "field"} ${item.containingClass().qualifiedName()}.${item.name()}"
400             }
401         }
402 
403         private fun describe(item: ClassItem, capitalize: Boolean = false): String {
404             return "${if (capitalize) "Class" else "class"} ${item.qualifiedName()}"
405         }
406 
407         private fun describe(item: PackageItem, capitalize: Boolean = false): String {
408             return "${if (capitalize) "Package" else "package"} ${item.qualifiedName()}"
409         }
410     }
411 }
412 
413 abstract class DefaultItem(
414     final override val fileLocation: FileLocation,
415     final override val modifiers: DefaultModifierList,
416 ) : Item {
417 
418     init {
419         @Suppress("LeakingThis")
420         modifiers.owner = this
421     }
422 
423     final override val sortingRank: Int = nextRank.getAndIncrement()
424 
425     final override val originallyDeprecated
426         // Delegate to the [ModifierList.isDeprecated] method so that changes to that will affect
427         // the value of this and [Item.effectivelyDeprecated] which delegates to this.
428         get() = modifiers.isDeprecated()
429 
mutableModifiersnull430     final override fun mutableModifiers(): MutableModifierList = modifiers
431 
432     override val isPublic: Boolean
433         get() = modifiers.isPublic()
434 
435     override val isProtected: Boolean
436         get() = modifiers.isProtected()
437 
438     override val isInternal: Boolean
439         get() = modifiers.getVisibilityLevel() == VisibilityLevel.INTERNAL
440 
441     override val isPackagePrivate: Boolean
442         get() = modifiers.isPackagePrivate()
443 
444     override val isPrivate: Boolean
445         get() = modifiers.isPrivate()
446 
447     final override var emit = true
448 
449     companion object {
450         private var nextRank = AtomicInteger()
451     }
452 
<lambda>null453     override val showability: Showability by lazy {
454         codebase.annotationManager.getShowabilityForItem(this)
455     }
456 
suppressedIssuesnull457     override fun suppressedIssues(): Set<String> {
458         return buildSet {
459             for (annotation in modifiers.annotations()) {
460                 val annotationName = annotation.qualifiedName
461                 if (annotationName != null && annotationName in SUPPRESS_ANNOTATIONS) {
462                     for (attribute in annotation.attributes) {
463                         // Assumption that all annotations in SUPPRESS_ANNOTATIONS only have
464                         // one attribute such as value/names that is varargs of String
465                         val value = attribute.value
466                         if (value is AnnotationArrayAttributeValue) {
467                             // Example: @SuppressLint({"RequiresFeature", "AllUpper"})
468                             for (innerValue in value.values) {
469                                 innerValue.value()?.toString()?.let { add(it) }
470                             }
471                         } else {
472                             // Example: @SuppressLint("RequiresFeature")
473                             value.value()?.toString()?.let { add(it) }
474                         }
475                     }
476                 }
477             }
478         }
479     }
480 
toStringnull481     final override fun toString() = toStringForItem()
482 }
483