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 parentnull41 fun parent(): SelectableItem? 42 43 /** 44 * Recursive check to see if compatibility checks should be suppressed for this item or any of 45 * its parents (containing class, containing package). 46 */ 47 fun isCompatibilitySuppressed(): Boolean { 48 return hasSuppressCompatibilityMetaAnnotation() || 49 parent()?.isCompatibilitySuppressed() ?: false 50 } 51 52 /** True if this item has been marked deprecated. */ 53 val originallyDeprecated: Boolean 54 55 /** 56 * True if this item has been marked as deprecated or is a descendant of a non-package item that 57 * has been marked as deprecated. 58 */ 59 val effectivelyDeprecated: Boolean 60 61 /** Visits this element using the given [visitor] */ acceptnull62 fun accept(visitor: ItemVisitor) 63 64 /** 65 * Mutate the [modifiers] list. 66 * 67 * Provides a [MutableModifierList] of the [modifiers] that can be modified by [mutator]. Once 68 * the mutator exits the [modifiers] will be updated. The [MutableModifierList] must not be 69 * accessed from outside [mutator]. 70 */ 71 fun mutateModifiers(mutator: MutableModifierList.() -> Unit) 72 73 /** 74 * The javadoc/KDoc comment for this code element, if any. This is the original content of the 75 * documentation, including lexical tokens to begin, continue and end the comment (such as /+*). 76 * See [ItemDocumentation.fullyQualifiedDocumentation] to look up the documentation with fully 77 * qualified references to classes. 78 */ 79 val documentation: ItemDocumentation 80 81 /** 82 * A rank used for sorting. This allows signature files etc to sort similar items by a natural 83 * order, if non-zero. (Even though in signature files the elements are normally sorted first 84 * logically (constructors, then methods, then fields) and then alphabetically, this lets us 85 * preserve the source ordering for example for overloaded methods of the same name, where it's 86 * not clear that an alphabetical order (of each parameter?) would be preferable.) 87 */ 88 val sortingRank: Int 89 90 /** 91 * Add the given text to the documentation. 92 * 93 * If the [tagSection] is null, add the comment to the initial text block of the description. 94 * 95 * If it is "@return", add the comment to the return value. 96 * 97 * Otherwise, the [tagSection] is taken to be the parameter name, and the comment added as 98 * parameter documentation for the given parameter. 99 */ 100 fun appendDocumentation(comment: String, tagSection: String? = null) 101 102 val isPublic: Boolean 103 val isProtected: Boolean 104 val isInternal: Boolean 105 val isPackagePrivate: Boolean 106 val isPrivate: Boolean 107 108 /** Calls [equalsToItem]. */ 109 override fun equals(other: Any?): Boolean 110 111 /** Calls [hashCodeForItem]. */ 112 override fun hashCode(): Int 113 114 /** Calls [toStringForItem]. */ 115 override fun toString(): String 116 117 /** 118 * Whether this [Item] is equal to [other]. 119 * 120 * This is implemented instead of [equals] because interfaces are not allowed to implement 121 * [equals]. Implementations of this will implement [equals] by calling this. 122 */ 123 fun equalsToItem(other: Any?): Boolean 124 125 /** 126 * Hashcode for this [Item]. 127 * 128 * This is implemented instead of [hashCode] because interfaces are not allowed to implement 129 * [hashCode]. Implementations of this will implement [hashCode] by calling this. 130 */ 131 fun hashCodeForItem(): Int 132 133 /** Provides a string representation of the item, suitable for use while debugging. */ 134 fun toStringForItem(): String 135 136 /** 137 * The language in which this was written, or [ItemLanguage.UNKNOWN] if not known, e.g. when 138 * created from a signature file. 139 */ 140 val itemLanguage: ItemLanguage 141 142 /** 143 * Is this element declared in Java (rather than Kotlin) ? 144 * 145 * See [itemLanguage]. 146 */ 147 fun isJava() = itemLanguage.isJava() 148 149 /** 150 * Is this element declared in Kotlin (rather than Java) ? 151 * 152 * See [itemLanguage]. 153 */ 154 fun isKotlin() = itemLanguage.isKotlin() 155 156 /** 157 * Returns true if this [Item]'s modifier list contains any suppress compatibility 158 * meta-annotations. 159 * 160 * Metalava will suppress compatibility checks for APIs which are within the scope of a 161 * "suppress compatibility" meta-annotation, but they may still be written to API files or stub 162 * JARs. 163 * 164 * "Suppress compatibility" meta-annotations allow Metalava to handle concepts like Jetpack 165 * experimental APIs, where developers can use the [RequiresOptIn] meta-annotation to mark 166 * feature sets with unstable APIs. 167 */ 168 fun hasSuppressCompatibilityMetaAnnotation(): Boolean = 169 codebase.annotationManager.hasSuppressCompatibilityMetaAnnotations(modifiers) 170 171 override val fileLocation: FileLocation 172 get() = FileLocation.UNKNOWN 173 174 /** 175 * Produces a user visible description of this item, including a label such as "class" or 176 * "field" 177 */ 178 fun describe(capitalize: Boolean = false) = describe(this, capitalize) 179 180 /** Returns the package that contains this item. */ 181 fun containingPackage(): PackageItem? 182 183 /** Returns the class that contains this item. */ 184 fun containingClass(): ClassItem? 185 186 /** 187 * Returns the associated type, if any. 188 * 189 * i.e. 190 * * For a field, property or parameter, this is the type of the variable. 191 * * For a method, it's the return type. 192 * * For classes it's the declared class type, i.e. a class type using the type parameter types 193 * as the type arguments. 194 * * For type parameters it's a [VariableTypeItem] reference the type parameter. 195 * * For packages and files, it's null. 196 * * For type aliases it's the underlying type for which the alias is an alternative name. 197 */ 198 fun type(): TypeItem? 199 200 /** 201 * Set the type of this. 202 * 203 * The [type] parameter must be of the same concrete type as returned by the [Item.type] method. 204 */ 205 fun setType(type: TypeItem) 206 207 /** 208 * Find the [Item] in [codebase] that corresponds to this item, or `null` if there is no such 209 * item. 210 * 211 * If [superMethods] is true and this is a [MethodItem] then the returned [MethodItem], if any, 212 * could be in a [ClassItem] that does not correspond to the [MethodItem.containingClass], it 213 * could be from a super class or super interface. e.g. if the [codebase] contains something 214 * like: 215 * ``` 216 * public class Super { 217 * public void method() {...} 218 * } 219 * public class Foo extends Super {} 220 * ``` 221 * 222 * And this is called on `Foo.method()` then: 223 * * if [superMethods] is false this will return `null`. 224 * * if [superMethods] is true and [duplicate] is false, then this will return `Super.method()`. 225 * * if both [superMethods] and [duplicate] are true then this will return a duplicate of 226 * `Super.method()` that has been added to `Foo` so it will be essentially `Foo.method()`. 227 * 228 * @param codebase the [Codebase] to search for a corresponding item. 229 * @param superMethods if true and this is a [MethodItem] then this method will search for super 230 * methods. If this is a [ParameterItem] then the value of this parameter will be passed to 231 * the [findCorrespondingItemIn] call which is used to find the [MethodItem] corresponding to 232 * the [ParameterItem.containingCallable]. 233 * @param duplicate if true, and this is a [MemberItem] (or [ParameterItem]) then the returned 234 * [Item], if any, will be in the [ClassItem] that corresponds to the [Item.containingClass]. 235 * This should be `true` if the returned [Item] is going to be compared to the original [Item] 236 * as the [Item.containingClass] can affect that comparison, e.g. the meaning of certain 237 * modifiers. 238 */ 239 fun findCorrespondingItemIn( 240 codebase: Codebase, 241 superMethods: Boolean = false, 242 duplicate: Boolean = false, 243 ): Item? 244 245 /** 246 * Get the set of suppressed issues for this [Item]. 247 * 248 * These are the values supplied to any of the [SUPPRESS_ANNOTATIONS] on this item. It DOES not 249 * include suppressed issues from the [parent]. 250 */ 251 override fun suppressedIssues(): Set<String> 252 253 /** The [BaselineKey] for this. */ 254 override val baselineKey 255 get() = BaselineKey.forElementId(baselineElementId()) 256 257 /** 258 * Get the baseline element id from which [baselineKey] is constructed. 259 * 260 * See [BaselineKey.forElementId] for more details. 261 */ 262 fun baselineElementId(): String 263 264 companion object { 265 fun describe(item: Item, capitalize: Boolean = false): String { 266 return when (item) { 267 is PackageItem -> describe(item, capitalize = capitalize) 268 is ClassItem -> describe(item, capitalize = capitalize) 269 is FieldItem -> describe(item, capitalize = capitalize) 270 is CallableItem -> 271 describe( 272 item, 273 includeParameterNames = false, 274 includeParameterTypes = true, 275 capitalize = capitalize 276 ) 277 is ParameterItem -> 278 describe( 279 item, 280 includeParameterNames = true, 281 includeParameterTypes = true, 282 capitalize = capitalize 283 ) 284 else -> item.toString() 285 } 286 } 287 288 fun describe( 289 item: CallableItem, 290 includeParameterNames: Boolean = false, 291 includeParameterTypes: Boolean = false, 292 includeReturnValue: Boolean = false, 293 capitalize: Boolean = false 294 ): String { 295 val builder = StringBuilder() 296 if (item.isConstructor()) { 297 builder.append(if (capitalize) "Constructor" else "constructor") 298 } else { 299 builder.append(if (capitalize) "Method" else "method") 300 } 301 builder.append(' ') 302 if (includeReturnValue && !item.isConstructor()) { 303 builder.append(item.returnType().toSimpleType()) 304 builder.append(' ') 305 } 306 appendCallableSignature(builder, item, includeParameterNames, includeParameterTypes) 307 return builder.toString() 308 } 309 310 fun describe( 311 item: ParameterItem, 312 includeParameterNames: Boolean = false, 313 includeParameterTypes: Boolean = false, 314 capitalize: Boolean = false 315 ): String { 316 val builder = StringBuilder() 317 builder.append(if (capitalize) "Parameter" else "parameter") 318 builder.append(' ') 319 builder.append(item.name()) 320 builder.append(" in ") 321 val callable = item.containingCallable() 322 appendCallableSignature(builder, callable, includeParameterNames, includeParameterTypes) 323 return builder.toString() 324 } 325 326 private fun appendCallableSignature( 327 builder: StringBuilder, 328 item: CallableItem, 329 includeParameterNames: Boolean, 330 includeParameterTypes: Boolean 331 ) { 332 builder.append(item.containingClass().qualifiedName()) 333 if (!item.isConstructor()) { 334 builder.append('.') 335 builder.append(item.name()) 336 } 337 if (includeParameterNames || includeParameterTypes) { 338 builder.append('(') 339 var first = true 340 for (parameter in item.parameters()) { 341 if (first) { 342 first = false 343 } else { 344 builder.append(',') 345 if (includeParameterNames && includeParameterTypes) { 346 builder.append(' ') 347 } 348 } 349 if (includeParameterTypes) { 350 builder.append(parameter.type().toSimpleType()) 351 if (includeParameterNames) { 352 builder.append(' ') 353 } 354 } 355 if (includeParameterNames) { 356 builder.append(parameter.publicName() ?: parameter.name()) 357 } 358 } 359 builder.append(')') 360 } 361 } 362 363 private fun describe(item: FieldItem, capitalize: Boolean = false): String { 364 return if (item.isEnumConstant()) { 365 "${if (capitalize) "Enum" else "enum"} constant ${item.containingClass().qualifiedName()}.${item.name()}" 366 } else { 367 "${if (capitalize) "Field" else "field"} ${item.containingClass().qualifiedName()}.${item.name()}" 368 } 369 } 370 371 private fun describe(item: ClassItem, capitalize: Boolean = false): String { 372 return "${if (capitalize) "Class" else "class"} ${item.qualifiedName()}" 373 } 374 375 private fun describe(item: PackageItem, capitalize: Boolean = false): String { 376 val suffix = item.qualifiedName().let { if (it.isEmpty()) "<root>" else it } 377 return "${if (capitalize) "Package" else "package"} $suffix" 378 } 379 } 380 } 381 382 /** Base [Item] implementation that is common to all models. */ 383 abstract class DefaultItem( 384 override val codebase: Codebase, 385 final override val fileLocation: FileLocation, 386 final override val itemLanguage: ItemLanguage, 387 modifiers: BaseModifierList, 388 documentationFactory: ItemDocumentationFactory, 389 ) : Item { 390 391 /** 392 * Create a [ItemDocumentation] appropriate for this [Item]. 393 * 394 * The leaking of `this` is safe as the implementations do not access anything that has not been 395 * initialized. 396 */ 397 final override val documentation = @Suppress("LeakingThis") documentationFactory(this) 398 399 /** 400 * The immutable [modifiers]. 401 * 402 * The supplied `modifiers` parameter could be either [MutableModifierList] or [ModifierList] 403 * but this requires a [ModifierList] so get one using [BaseModifierList.toImmutable]. 404 * 405 * The [ModifierList] that this references is immutable but the [mutateModifiers] method can be 406 * used to change the [ModifierList] to which this refers. 407 */ 408 final override var modifiers: ModifierList = modifiers.toImmutable() 409 private set 410 411 init { 412 if (!modifiers.isDeprecated() && documentation.hasTagSection("@deprecated")) { <lambda>null413 @Suppress("LeakingThis") mutateModifiers { setDeprecated(true) } 414 } 415 } 416 417 final override val sortingRank: Int = nextRank.getAndIncrement() 418 419 final override val originallyDeprecated 420 // Delegate to the [ModifierList.isDeprecated] method so that changes to that will affect 421 // the value of this and [Item.effectivelyDeprecated] which delegates to this. 422 get() = modifiers.isDeprecated() 423 mutateModifiersnull424 override fun mutateModifiers(mutator: MutableModifierList.() -> Unit) { 425 val mutable = modifiers.toMutable() 426 mutable.mutator() 427 modifiers = mutable.toImmutable() 428 } 429 430 final override val isPublic: Boolean 431 get() = modifiers.isPublic() 432 433 final override val isProtected: Boolean 434 get() = modifiers.isProtected() 435 436 final override val isInternal: Boolean 437 get() = modifiers.getVisibilityLevel() == VisibilityLevel.INTERNAL 438 439 final override val isPackagePrivate: Boolean 440 get() = modifiers.isPackagePrivate() 441 442 final override val isPrivate: Boolean 443 get() = modifiers.isPrivate() 444 445 companion object { 446 private var nextRank = AtomicInteger() 447 } 448 suppressedIssuesnull449 final override fun suppressedIssues(): Set<String> { 450 return buildSet { 451 for (annotation in modifiers.annotations()) { 452 val annotationName = annotation.qualifiedName 453 if (annotationName in SUPPRESS_ANNOTATIONS) { 454 for (attribute in annotation.attributes) { 455 // Assumption that all annotations in SUPPRESS_ANNOTATIONS only have 456 // one attribute such as value/names that is varargs of String 457 val value = attribute.legacyValue 458 if (value is AnnotationArrayAttributeValue) { 459 // Example: @SuppressLint({"RequiresFeature", "AllUpper"}) 460 for (innerValue in value.values) { 461 innerValue.value()?.toString()?.let { add(it) } 462 } 463 } else { 464 // Example: @SuppressLint("RequiresFeature") 465 value.value()?.toString()?.let { add(it) } 466 } 467 } 468 } 469 } 470 } 471 } 472 appendDocumentationnull473 final override fun appendDocumentation(comment: String, tagSection: String?) { 474 if (comment.isBlank()) { 475 return 476 } 477 478 // TODO: Figure out if an annotation should go on the return value, or on the method. 479 // For example; threading: on the method, range: on the return value. 480 // TODO: Find a good way to add or append to a given tag (@param <something>, @return, etc) 481 482 if (this is ParameterItem) { 483 // For parameters, the documentation goes into the surrounding method's documentation! 484 // Find the right parameter location! 485 val parameterName = name() 486 val target = containingCallable() 487 target.appendDocumentation(comment, parameterName) 488 return 489 } 490 491 documentation.appendDocumentation(comment, tagSection) 492 } 493 equalsnull494 final override fun equals(other: Any?) = equalsToItem(other) 495 496 final override fun hashCode() = hashCodeForItem() 497 498 final override fun toString() = toStringForItem() 499 } 500