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