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