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