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