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