blob: af2042dcec78ecfdd46419fb9adbe9b2d396a61f [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.metalava.model
import com.android.tools.metalava.model.visitors.ItemVisitor
import com.android.tools.metalava.model.visitors.TypeVisitor
import com.intellij.psi.PsiElement
/**
* Represents a code element such as a package, a class, a method, a field, a parameter.
*
* This extra abstraction on top of PSI allows us to more model the API (and customize
* visibility, which cannot always be done by looking at a particular piece of code and examining
* visibility and @hide/@removed annotations: sometimes package private APIs are unhidden by
* being used in public APIs for example.
*
* The abstraction also lets us back the model by an alternative implementation read from
* signature files, to do compatibility checks.
* */
interface Item {
val codebase: Codebase
/** Return the modifiers of this class */
val modifiers: ModifierList
/**
* Whether this element should be part of the API. The algorithm for this is complicated, so it can't
* be computed initially; we'll make passes over the source code to determine eligibility and mark all
* items as included or not.
*/
var included: Boolean
/**
* Whether this element was originally hidden with @hide/@Hide. The [hidden] property
* tracks whether it is *actually* hidden, since elements can be unhidden via show annotations, etc.
*/
var originallyHidden: Boolean
/** Whether this element has been hidden with @hide/@Hide (or after propagation, in some containing class/pkg) */
var hidden: Boolean
var emit: Boolean
fun parent(): Item?
/** Recursive check to see if this item or any of its parents (containing class, containing package) are hidden */
fun hidden(): Boolean {
return hidden || parent()?.hidden() ?: false
}
/** Whether this element has been removed with @removed/@Remove (or after propagation, in some containing class) */
var removed: Boolean
/** True if this element has been marked deprecated */
var deprecated: Boolean
/** True if this element is only intended for documentation */
var docOnly: Boolean
/** True if this item is either hidden or removed */
fun isHiddenOrRemoved(): Boolean = hidden || removed
/** Visits this element using the given [visitor] */
fun accept(visitor: ItemVisitor)
/** Visits all types in this item hierarchy */
fun acceptTypes(visitor: TypeVisitor)
/** Get a mutable version of modifiers for this item */
fun mutableModifiers(): MutableModifierList
/** The javadoc/KDoc comment for this code element, if any. This is
* the original content of the documentation, including lexical tokens
* to begin, continue and end the comment (such as /+*).
* See [fullyQualifiedDocumentation] to look up the documentation with
* fully qualified references to classes.
*/
var documentation: String
/** Looks up docs for a specific tag */
fun findTagDocumentation(tag: String): String?
/**
* A rank used for sorting. This allows signature files etc to
* sort similar items by a natural order, if non-zero.
* (Even though in signature files the elements are normally
* sorted first logically (constructors, then methods, then fields)
* and then alphabetically, this lets us preserve the source
* ordering for example for overloaded methods of the same name,
* where it's not clear that an alphabetical order (of each
* parameter?) would be preferable.)
*/
val sortingRank: Int
/**
* Add the given text to the documentation.
*
* If the [tagSection] is null, add the comment to the initial text block
* of the description. Otherwise if it is "@return", add the comment
* to the return value. Otherwise the [tagSection] is taken to be the
* parameter name, and the comment added as parameter documentation
* for the given parameter.
*/
fun appendDocumentation(comment: String, tagSection: String? = null, append: Boolean = true)
val isPublic: Boolean
val isProtected: Boolean
val isPackagePrivate: Boolean
val isPrivate: Boolean
// make sure these are implemented so we can place in maps:
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
/** Whether this member was cloned in from a super class or interface */
fun isCloned(): Boolean
/**
* Returns true if this item requires nullness information (e.g. for a method
* where either the return value or any of the parameters are non-primitives.
* Note that it doesn't consider whether it already has nullness annotations;
* for that see [hasNullnessInfo].
*/
fun requiresNullnessInfo(): Boolean = false
/**
* Returns true if this item requires nullness information and supplies it
* (for all items, e.g. if a method is partially annotated this method would
* still return false)
*/
fun hasNullnessInfo(): Boolean = false
/**
* Whether this item was loaded from the classpath (e.g. jar dependencies)
* rather than be declared as source
*/
fun isFromClassPath(): Boolean = false
/** Is this element declared in Java (rather than Kotlin) ? */
fun isJava(): Boolean = true
/** Is this element declared in Kotlin (rather than Java) ? */
fun isKotlin() = !isJava()
fun hasShowAnnotation(): Boolean = modifiers.hasShowAnnotation()
fun hasHideAnnotation(): Boolean = modifiers.hasHideAnnotations()
fun checkLevel(): Boolean {
return modifiers.checkLevel()
}
fun compilationUnit(): CompilationUnit? {
var curr: Item? = this
while (curr != null) {
if (curr is ClassItem && curr.isTopLevelClass()) {
return curr.getCompilationUnit()
}
curr = curr.parent()
}
return null
}
/** Returns the PSI element for this item, if any */
fun psi(): PsiElement? = null
/** Tag field used for DFS etc */
var tag: Boolean
/**
* Returns the [documentation], but with fully qualified links (except for the same package, and
* when turning a relative reference into a fully qualified reference, use the javadoc syntax
* for continuing to display the relative text, e.g. instead of {@link java.util.List}, use
* {@link java.util.List List}.
*/
fun fullyQualifiedDocumentation(): String = documentation
/** Expands the given documentation comment in the current name context */
fun fullyQualifiedDocumentation(documentation: String): String = documentation
/** Produces a user visible description of this item, including a label such as "class" or "field" */
fun describe(capitalize: Boolean = false) = describe(this, capitalize)
/**
* Returns the package that contains this item. If [strict] is false, this will return self
* if called on a package, otherwise it will return the containing package (e.g. "foo" for "foo.bar").
* The parameter is ignored on other item types.
*/
fun containingPackage(strict: Boolean = true): PackageItem?
/**
* Returns the class that contains this item. If [strict] is false, this will return self
* if called on a class, otherwise it will return the outer class, if any. The parameter is
* ignored on other item types.
*/
fun containingClass(strict: Boolean = true): ClassItem?
/**
* Returns the associated type if any. For example, for a field, property or parameter,
* this is the type of the variable; for a method, it's the return type.
* For packages, classes and compilation units, it's null.
*/
fun type(): TypeItem?
companion object {
fun describe(item: Item, capitalize: Boolean = false): String {
return when (item) {
is PackageItem -> describe(item, capitalize = capitalize)
is ClassItem -> describe(item, capitalize = capitalize)
is FieldItem -> describe(item, capitalize = capitalize)
is MethodItem -> describe(
item,
includeParameterNames = false,
includeParameterTypes = true,
capitalize = capitalize
)
is ParameterItem -> describe(
item,
includeParameterNames = true,
includeParameterTypes = true,
capitalize = capitalize
)
else -> item.toString()
}
}
fun describe(
item: MethodItem,
includeParameterNames: Boolean = false,
includeParameterTypes: Boolean = false,
includeReturnValue: Boolean = false,
capitalize: Boolean = false
): String {
val builder = StringBuilder()
if (item.isConstructor()) {
builder.append(if (capitalize) "Constructor" else "constructor")
} else {
builder.append(if (capitalize) "Method" else "method")
}
builder.append(' ')
if (includeReturnValue && !item.isConstructor()) {
builder.append(item.returnType()?.toSimpleType())
builder.append(' ')
}
appendMethodSignature(builder, item, includeParameterNames, includeParameterTypes)
return builder.toString()
}
fun describe(
item: ParameterItem,
includeParameterNames: Boolean = false,
includeParameterTypes: Boolean = false,
capitalize: Boolean = false
): String {
val builder = StringBuilder()
builder.append(if (capitalize) "Parameter" else "parameter")
builder.append(' ')
builder.append(item.name())
builder.append(" in ")
val method = item.containingMethod()
appendMethodSignature(builder, method, includeParameterNames, includeParameterTypes)
return builder.toString()
}
private fun appendMethodSignature(
builder: StringBuilder,
item: MethodItem,
includeParameterNames: Boolean,
includeParameterTypes: Boolean
) {
builder.append(item.containingClass().qualifiedName())
if (!item.isConstructor()) {
builder.append('.')
builder.append(item.name())
}
if (includeParameterNames || includeParameterTypes) {
builder.append('(')
var first = true
for (parameter in item.parameters()) {
if (first) {
first = false
} else {
builder.append(',')
if (includeParameterNames && includeParameterTypes) {
builder.append(' ')
}
}
if (includeParameterTypes) {
builder.append(parameter.type().toSimpleType())
if (includeParameterNames) {
builder.append(' ')
}
}
if (includeParameterNames) {
builder.append(parameter.publicName() ?: parameter.name())
}
}
builder.append(')')
}
}
private fun describe(item: FieldItem, capitalize: Boolean = false): String {
return if (item.isEnumConstant()) {
"${if (capitalize) "Enum" else "enum"} constant ${item.containingClass().qualifiedName()}.${item.name()}"
} else {
"${if (capitalize) "Field" else "field"} ${item.containingClass().qualifiedName()}.${item.name()}"
}
}
private fun describe(item: ClassItem, capitalize: Boolean = false): String {
return "${if (capitalize) "Class" else "class"} ${item.qualifiedName()}"
}
private fun describe(item: PackageItem, capitalize: Boolean = false): String {
return "${if (capitalize) "Package" else "package"} ${item.qualifiedName()}"
}
}
}
abstract class DefaultItem(override val sortingRank: Int = nextRank++) : Item {
override val isPublic: Boolean get() = modifiers.isPublic()
override val isProtected: Boolean get() = modifiers.isProtected()
override val isPackagePrivate: Boolean get() = modifiers.isPackagePrivate()
override val isPrivate: Boolean get() = modifiers.isPrivate()
override var emit = true
override var tag: Boolean = false
// TODO: Get rid of this; with the new predicate approach it's redundant (and
// storing it per element is problematic since the predicate sometimes includes
// methods from parent interfaces etc)
override var included: Boolean = true
companion object {
private var nextRank: Int = 1
}
}