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