• 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.NullnessMigration.Companion.findNullnessAnnotation
20 import com.android.tools.metalava.RECENTLY_NONNULL
21 import com.android.tools.metalava.RECENTLY_NULLABLE
22 import com.android.tools.metalava.model.visitors.ItemVisitor
23 import com.android.tools.metalava.model.visitors.TypeVisitor
24 import com.intellij.psi.PsiElement
25 import java.util.concurrent.atomic.AtomicInteger
26 
27 /**
28  * Represents a code element such as a package, a class, a method, a field, a parameter.
29  *
30  * This extra abstraction on top of PSI allows us to more model the API (and customize
31  * visibility, which cannot always be done by looking at a particular piece of code and examining
32  * visibility and @hide/@removed annotations: sometimes package private APIs are unhidden by
33  * being used in public APIs for example.
34  *
35  * The abstraction also lets us back the model by an alternative implementation read from
36  * signature files, to do compatibility checks.
37  * */
38 interface Item {
39     val codebase: Codebase
40 
41     /** Return the modifiers of this class */
42     val modifiers: ModifierList
43 
44     /**
45      * Whether this element was originally hidden with @hide/@Hide. The [hidden] property
46      * tracks whether it is *actually* hidden, since elements can be unhidden via show annotations, etc.
47      */
48     var originallyHidden: Boolean
49 
50     /** Whether this element has been hidden with @hide/@Hide (or after propagation, in some containing class/pkg) */
51     var hidden: Boolean
52 
53     var emit: Boolean
54 
parentnull55     fun parent(): Item?
56 
57     /** Recursive check to see if this item or any of its parents (containing class, containing package) are hidden */
58     fun hidden(): Boolean {
59         return hidden || parent()?.hidden() ?: false
60     }
61 
62     /** Whether this element has been removed with @removed/@Remove (or after propagation, in some containing class) */
63     var removed: Boolean
64 
65     /** True if this element has been marked deprecated */
66     var deprecated: Boolean
67 
68     /** True if this element is only intended for documentation */
69     var docOnly: Boolean
70 
71     /**
72      * True if this is a synthetic element, such as the generated "value" and "valueOf" methods
73      * in enums
74      */
75     val synthetic: Boolean
76 
77     /** True if this item is either hidden or removed */
isHiddenOrRemovednull78     fun isHiddenOrRemoved(): Boolean = hidden || removed
79 
80     /** Visits this element using the given [visitor] */
81     fun accept(visitor: ItemVisitor)
82 
83     /** Visits all types in this item hierarchy */
84     fun acceptTypes(visitor: TypeVisitor)
85 
86     /** Get a mutable version of modifiers for this item */
87     fun mutableModifiers(): MutableModifierList
88 
89     /** The javadoc/KDoc comment for this code element, if any. This is
90      * the original content of the documentation, including lexical tokens
91      * to begin, continue and end the comment (such as /+*).
92      * See [fullyQualifiedDocumentation] to look up the documentation with
93      * fully qualified references to classes.
94      */
95     var documentation: String
96 
97     /**
98      * Looks up docs for the first instance of a specific javadoc tag having
99      * the (optionally) provided value (e.g. parameter name).
100      */
101     fun findTagDocumentation(tag: String, value: String? = null): String?
102 
103     /**
104      * A rank used for sorting. This allows signature files etc to
105      * sort similar items by a natural order, if non-zero.
106      * (Even though in signature files the elements are normally
107      * sorted first logically (constructors, then methods, then fields)
108      * and then alphabetically, this lets us preserve the source
109      * ordering for example for overloaded methods of the same name,
110      * where it's not clear that an alphabetical order (of each
111      * parameter?) would be preferable.)
112      */
113     val sortingRank: Int
114 
115     /**
116      * Add the given text to the documentation.
117      *
118      * If the [tagSection] is null, add the comment to the initial text block
119      * of the description. Otherwise if it is "@return", add the comment
120      * to the return value. Otherwise the [tagSection] is taken to be the
121      * parameter name, and the comment added as parameter documentation
122      * for the given parameter.
123      */
124     fun appendDocumentation(comment: String, tagSection: String? = null, append: Boolean = true)
125 
126     val isPublic: Boolean
127     val isProtected: Boolean
128     val isInternal: Boolean
129     val isPackagePrivate: Boolean
130     val isPrivate: Boolean
131 
132     // make sure these are implemented so we can place in maps:
133     override fun equals(other: Any?): Boolean
134 
135     override fun hashCode(): Int
136 
137     /** Whether this member was cloned in from a super class or interface */
138     fun isCloned(): Boolean
139 
140     /**
141      * Returns true if this item requires nullness information (e.g. for a method
142      * where either the return value or any of the parameters are non-primitives.
143      * Note that it doesn't consider whether it already has nullness annotations;
144      * for that see [hasNullnessInfo].
145      */
146     fun requiresNullnessInfo(): Boolean = false
147 
148     /**
149      * Returns true if this item requires nullness information and supplies it
150      * (for all items, e.g. if a method is partially annotated this method would
151      * still return false)
152      */
153     fun hasNullnessInfo(): Boolean = false
154 
155     /**
156      * Whether this item was loaded from the classpath (e.g. jar dependencies)
157      * rather than be declared as source
158      */
159     fun isFromClassPath(): Boolean = false
160 
161     /** Is this element declared in Java (rather than Kotlin) ? */
162     fun isJava(): Boolean = true
163 
164     /** Is this element declared in Kotlin (rather than Java) ? */
165     fun isKotlin() = !isJava()
166 
167     fun hasShowAnnotation(): Boolean = modifiers.hasShowAnnotation()
168     fun onlyShowForStubPurposes(): Boolean = modifiers.onlyShowForStubPurposes()
169     fun hasHideAnnotation(): Boolean = modifiers.hasHideAnnotations()
170     fun hasHideMetaAnnotation(): Boolean = modifiers.hasHideMetaAnnotations()
171 
172     /**
173      * Same as [hasShowAnnotation], except if it's a method, take into account super methods'
174      * annotations.
175      *
176      * Unlike classes or fields, methods implicitly inherits visibility annotations, and for
177      * some visibility calculation we need to take it into account.
178      * (See ShowAnnotationTest.`Methods inherit showAnnotations but fields and classes don't`.)
179      */
180     fun hasShowAnnotationInherited(): Boolean = hasShowAnnotation()
181 
182     /**
183      * Same as [onlyShowForStubPurposes], except if it's a method,
184      * take into account super methods' annotations.
185      *
186      * Unlike classes or fields, methods implicitly inherits visibility annotations, and for
187      * some visibility calculation we need to take it into account.
188      * (See ShowAnnotationTest.`Methods inherit showAnnotations but fields and classes don't`.)
189      */
190     fun onlyShowForStubPurposesInherited(): Boolean = onlyShowForStubPurposes()
191 
192     fun checkLevel(): Boolean {
193         return modifiers.checkLevel()
194     }
195 
sourceFilenull196     fun sourceFile(): SourceFileItem? {
197         var curr: Item? = this
198         while (curr != null) {
199             if (curr is ClassItem && curr.isTopLevelClass()) {
200                 return curr.getSourceFile()
201             }
202             curr = curr.parent()
203         }
204 
205         return null
206     }
207 
208     /** Returns the PSI element for this item, if any */
psinull209     fun psi(): PsiElement? = null
210 
211     /** Tag field used for DFS etc */
212     var tag: Boolean
213 
214     /**
215      * Returns the [documentation], but with fully qualified links (except for the same package, and
216      * when turning a relative reference into a fully qualified reference, use the javadoc syntax
217      * for continuing to display the relative text, e.g. instead of {@link java.util.List}, use
218      * {@link java.util.List List}.
219      */
220     fun fullyQualifiedDocumentation(): String = documentation
221 
222     /** Expands the given documentation comment in the current name context */
223     fun fullyQualifiedDocumentation(documentation: String): String = documentation
224 
225     /** Produces a user visible description of this item, including a label such as "class" or "field" */
226     fun describe(capitalize: Boolean = false) = describe(this, capitalize)
227 
228     /**
229      * Returns the package that contains this item. If [strict] is false, this will return self
230      * if called on a package, otherwise it will return the containing package (e.g. "foo" for "foo.bar").
231      * The parameter is ignored on other item types.
232      */
233     fun containingPackage(strict: Boolean = true): PackageItem?
234 
235     /**
236      * Returns the class that contains this item. If [strict] is false, this will return self
237      * if called on a class, otherwise it will return the outer class, if any. The parameter is
238      * ignored on other item types.
239      */
240     fun containingClass(strict: Boolean = true): ClassItem?
241 
242     /**
243      * Returns the associated type if any. For example, for a field, property or parameter,
244      * this is the type of the variable; for a method, it's the return type.
245      * For packages, classes and files, it's null.
246      */
247     fun type(): TypeItem?
248 
249     /**
250      * Marks the nullability of this Item as Recent.
251      * That is, replaces @Nullable/@NonNull with @RecentlyNullable/@RecentlyNonNull
252      */
253     fun markRecent() {
254         val annotation = findNullnessAnnotation(this) ?: return
255         // Nullness information change: Add migration annotation
256         val annotationClass = if (annotation.isNullable()) RECENTLY_NULLABLE else RECENTLY_NONNULL
257 
258         val modifiers = mutableModifiers()
259         modifiers.removeAnnotation(annotation)
260 
261         // Don't map annotation names - this would turn newly non null back into non null
262         modifiers.addAnnotation(codebase.createAnnotation("@$annotationClass", this, mapName = false))
263     }
264 
265     companion object {
describenull266         fun describe(item: Item, capitalize: Boolean = false): String {
267             return when (item) {
268                 is PackageItem -> describe(item, capitalize = capitalize)
269                 is ClassItem -> describe(item, capitalize = capitalize)
270                 is FieldItem -> describe(item, capitalize = capitalize)
271                 is MethodItem -> describe(
272                     item,
273                     includeParameterNames = false,
274                     includeParameterTypes = true,
275                     capitalize = capitalize
276                 )
277                 is ParameterItem -> describe(
278                     item,
279                     includeParameterNames = true,
280                     includeParameterTypes = true,
281                     capitalize = capitalize
282                 )
283                 else -> item.toString()
284             }
285         }
286 
describenull287         fun describe(
288             item: MethodItem,
289             includeParameterNames: Boolean = false,
290             includeParameterTypes: Boolean = false,
291             includeReturnValue: Boolean = false,
292             capitalize: Boolean = false
293         ): String {
294             val builder = StringBuilder()
295             if (item.isConstructor()) {
296                 builder.append(if (capitalize) "Constructor" else "constructor")
297             } else {
298                 builder.append(if (capitalize) "Method" else "method")
299             }
300             builder.append(' ')
301             if (includeReturnValue && !item.isConstructor()) {
302                 builder.append(item.returnType()?.toSimpleType())
303                 builder.append(' ')
304             }
305             appendMethodSignature(builder, item, includeParameterNames, includeParameterTypes)
306             return builder.toString()
307         }
308 
describenull309         fun describe(
310             item: ParameterItem,
311             includeParameterNames: Boolean = false,
312             includeParameterTypes: Boolean = false,
313             capitalize: Boolean = false
314         ): String {
315             val builder = StringBuilder()
316             builder.append(if (capitalize) "Parameter" else "parameter")
317             builder.append(' ')
318             builder.append(item.name())
319             builder.append(" in ")
320             val method = item.containingMethod()
321             appendMethodSignature(builder, method, includeParameterNames, includeParameterTypes)
322             return builder.toString()
323         }
324 
appendMethodSignaturenull325         private fun appendMethodSignature(
326             builder: StringBuilder,
327             item: MethodItem,
328             includeParameterNames: Boolean,
329             includeParameterTypes: Boolean
330         ) {
331             builder.append(item.containingClass().qualifiedName())
332             if (!item.isConstructor()) {
333                 builder.append('.')
334                 builder.append(item.name())
335             }
336             if (includeParameterNames || includeParameterTypes) {
337                 builder.append('(')
338                 var first = true
339                 for (parameter in item.parameters()) {
340                     if (first) {
341                         first = false
342                     } else {
343                         builder.append(',')
344                         if (includeParameterNames && includeParameterTypes) {
345                             builder.append(' ')
346                         }
347                     }
348                     if (includeParameterTypes) {
349                         builder.append(parameter.type().toSimpleType())
350                         if (includeParameterNames) {
351                             builder.append(' ')
352                         }
353                     }
354                     if (includeParameterNames) {
355                         builder.append(parameter.publicName() ?: parameter.name())
356                     }
357                 }
358                 builder.append(')')
359             }
360         }
361 
describenull362         private fun describe(item: FieldItem, capitalize: Boolean = false): String {
363             return if (item.isEnumConstant()) {
364                 "${if (capitalize) "Enum" else "enum"} constant ${item.containingClass().qualifiedName()}.${item.name()}"
365             } else {
366                 "${if (capitalize) "Field" else "field"} ${item.containingClass().qualifiedName()}.${item.name()}"
367             }
368         }
369 
describenull370         private fun describe(item: ClassItem, capitalize: Boolean = false): String {
371             return "${if (capitalize) "Class" else "class"} ${item.qualifiedName()}"
372         }
373 
describenull374         private fun describe(item: PackageItem, capitalize: Boolean = false): String {
375             return "${if (capitalize) "Package" else "package"} ${item.qualifiedName()}"
376         }
377     }
378 }
379 
380 abstract class DefaultItem(override val sortingRank: Int = nextRank.getAndIncrement()) : Item {
381     override val isPublic: Boolean get() = modifiers.isPublic()
382     override val isProtected: Boolean get() = modifiers.isProtected()
383     override val isInternal: Boolean
384         get() = modifiers.getVisibilityLevel() == VisibilityLevel.INTERNAL
385     override val isPackagePrivate: Boolean get() = modifiers.isPackagePrivate()
386     override val isPrivate: Boolean get() = modifiers.isPrivate()
387 
388     override var emit = true
389     override var tag: Boolean = false
390 
391     companion object {
392         private var nextRank = AtomicInteger()
393     }
394 }
395