1 /* <lambda>null2 * Copyright (C) 2024 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 java.io.Writer 20 21 class ModifierListWriter 22 private constructor( 23 private val writer: Writer, 24 /** 25 * Can be one of [AnnotationTarget.SIGNATURE_FILE], [AnnotationTarget.SDK_STUBS_FILE] or 26 * [AnnotationTarget.DOC_STUBS_FILE]. 27 */ 28 private val target: AnnotationTarget, 29 private val runtimeAnnotationsOnly: Boolean = false, 30 private val skipNullnessAnnotations: Boolean = false, 31 private val language: Language = Language.JAVA, 32 ) { 33 companion object { 34 fun forSignature( 35 writer: Writer, 36 skipNullnessAnnotations: Boolean, 37 ) = 38 ModifierListWriter( 39 writer = writer, 40 target = AnnotationTarget.SIGNATURE_FILE, 41 skipNullnessAnnotations = skipNullnessAnnotations, 42 ) 43 44 fun forStubs( 45 writer: Writer, 46 docStubs: Boolean, 47 runtimeAnnotationsOnly: Boolean = false, 48 language: Language = Language.JAVA, 49 ) = 50 ModifierListWriter( 51 writer = writer, 52 target = 53 if (docStubs) AnnotationTarget.DOC_STUBS_FILE 54 else AnnotationTarget.SDK_STUBS_FILE, 55 runtimeAnnotationsOnly = runtimeAnnotationsOnly, 56 skipNullnessAnnotations = language == Language.KOTLIN, 57 language = language, 58 ) 59 60 /** 61 * Checks whether the `abstract` modifier should be ignored on the method item when 62 * generating stubs. 63 * 64 * Methods that are in annotations are implicitly `abstract`. Methods in an enum can be 65 * `abstract` which requires them to be implemented in each Enum constant but the stubs do 66 * not generate overrides in the enum constants so the method needs to be concrete otherwise 67 * the stubs will not compile. 68 */ 69 private fun mustIgnoreAbstractInStubs(methodItem: MethodItem): Boolean { 70 val containingClass = methodItem.containingClass() 71 72 // Need to filter out abstract from the modifiers list and turn it into 73 // a concrete method to make the stub compile 74 return containingClass.isEnum() || containingClass.isAnnotationType() 75 } 76 77 /** 78 * Checks whether the method requires a body to be generated in the stubs. 79 * * Methods that are annotations are implicitly `abstract` but the body is provided by the 80 * runtime, so they never need bodies. 81 * * Native methods never need bodies. 82 * * Abstract methods do not need bodies unless they are enums in which case see 83 * [mustIgnoreAbstractInStubs] for an explanation as to why they need bodies. 84 */ 85 fun requiresMethodBodyInStubs(methodItem: MethodItem): Boolean { 86 val modifiers = methodItem.modifiers 87 val containingClass = methodItem.containingClass() 88 89 val isEnum = containingClass.isEnum() 90 val isAnnotation = containingClass.isAnnotationType() 91 92 return (!modifiers.isAbstract() || isEnum) && !isAnnotation && !modifiers.isNative() 93 } 94 } 95 96 /** Write the modifier list (possibly including annotations) to the supplied [writer]. */ 97 fun write(item: Item) { 98 writeAnnotations(item) 99 writeKeywords(item) 100 } 101 102 /** Write the modifier keywords. */ 103 fun writeKeywords(item: Item, normalize: Boolean = false) { 104 if ( 105 item is PackageItem || 106 (target != AnnotationTarget.SIGNATURE_FILE && 107 item is FieldItem && 108 item.isEnumConstant()) 109 ) { 110 // Packages and enum constants (in a stubs file) use a modifier list, but only 111 // annotations apply. 112 return 113 } 114 115 // Kotlin order: 116 // https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers 117 118 // Abstract: should appear in interfaces if in compat mode 119 val classItem = item as? ClassItem 120 val methodItem = item as? MethodItem 121 122 val list = item.modifiers 123 val visibilityLevel = list.getVisibilityLevel() 124 val modifier = 125 if (language == Language.JAVA) { 126 visibilityLevel.javaSourceCodeModifier 127 } else { 128 visibilityLevel.kotlinSourceCodeModifier 129 } 130 if (modifier.isNotEmpty()) { 131 writer.write("$modifier ") 132 } 133 134 val isInterface = 135 classItem?.isInterface() == true || methodItem?.containingClass()?.isInterface() == true 136 137 val isAbstract = list.isAbstract() 138 val ignoreAbstract = 139 isAbstract && 140 target != AnnotationTarget.SIGNATURE_FILE && 141 methodItem?.let { mustIgnoreAbstractInStubs(methodItem) } ?: false 142 143 if ( 144 isAbstract && 145 !ignoreAbstract && 146 classItem?.isEnum() != true && 147 classItem?.isAnnotationType() != true && 148 !isInterface 149 ) { 150 writer.write("abstract ") 151 } 152 153 if (list.isDefault() && item !is ParameterItem) { 154 writer.write("default ") 155 } 156 157 if (list.isStatic() && (classItem == null || !classItem.isEnum())) { 158 writer.write("static ") 159 } 160 161 when (language) { 162 Language.JAVA -> { 163 if ( 164 list.isFinal() && 165 // Don't show final on parameters: that's an implementation detail 166 item !is ParameterItem && 167 // Don't add final on enum or enum members as they are implicitly final. 168 classItem?.isEnum() != true && 169 // If normalizing and the current item is a method and its containing class 170 // is final then do not write out the final keyword. 171 (!normalize || methodItem?.containingClass()?.modifiers?.isFinal() != true) 172 ) { 173 writer.write("final ") 174 } 175 } 176 Language.KOTLIN -> { 177 if (!list.isFinal()) { 178 writer.write("open ") 179 } 180 } 181 } 182 183 if (list.isSealed()) { 184 writer.write("sealed ") 185 } 186 187 if (list.isSuspend()) { 188 writer.write("suspend ") 189 } 190 191 if (list.isInline()) { 192 writer.write("inline ") 193 } 194 195 if (list.isValue()) { 196 writer.write("value ") 197 } 198 199 if (list.isInfix()) { 200 writer.write("infix ") 201 } 202 203 if (list.isOperator()) { 204 writer.write("operator ") 205 } 206 207 if (list.isTransient()) { 208 writer.write("transient ") 209 } 210 211 if (list.isVolatile()) { 212 writer.write("volatile ") 213 } 214 215 if (list.isSynchronized() && target.isStubsFile()) { 216 writer.write("synchronized ") 217 } 218 219 if (list.isNative() && (target.isStubsFile() || isSignaturePolymorphic(item))) { 220 writer.write("native ") 221 } 222 223 if (list.isFunctional()) { 224 writer.write("fun ") 225 } 226 227 if (language == Language.KOTLIN) { 228 if (list.isData()) { 229 writer.write("data ") 230 } 231 } 232 } 233 234 private fun writeAnnotations(item: Item) { 235 // Generate annotations on separate lines in stub files for packages, classes and 236 // methods and also for enum constants. 237 val separateLines = 238 target != AnnotationTarget.SIGNATURE_FILE && 239 when (item) { 240 is MethodItem, 241 is ClassItem, 242 is PackageItem -> true 243 is FieldItem -> item.isEnumConstant() 244 else -> false 245 } 246 247 // Do not write deprecate or suppress compatibility annotations on a package. 248 if (item !is PackageItem) { 249 val writeDeprecated = 250 when { 251 // Do not write @Deprecated for a removed item unless it was explicitly marked 252 // as deprecated. 253 item.removed -> item.originallyDeprecated 254 // Do not write @Deprecated for a parameter unless it was explicitly marked 255 // as deprecated. 256 item is ParameterItem -> item.originallyDeprecated 257 // Do not write @Deprecated for a field if it was inherited from another class 258 // and was not explicitly qualified. 259 item is FieldItem -> 260 if (item.inheritedFromAncestor) item.originallyDeprecated 261 else item.effectivelyDeprecated 262 else -> item.effectivelyDeprecated 263 } 264 if (writeDeprecated) { 265 writer.write("@Deprecated") 266 writer.write(if (separateLines) "\n" else " ") 267 } 268 269 if (item.hasSuppressCompatibilityMetaAnnotation()) { 270 writer.write("@$SUPPRESS_COMPATIBILITY_ANNOTATION") 271 writer.write(if (separateLines) "\n" else " ") 272 } 273 } 274 275 val list = item.modifiers 276 var annotations = list.annotations() 277 278 // Ensure stable signature file order 279 if (annotations.size > 1) { 280 annotations = annotations.sortedBy { it.qualifiedName } 281 } 282 283 if (annotations.isNotEmpty()) { 284 // Omit common packages in signature files. 285 val omitCommonPackages = target == AnnotationTarget.SIGNATURE_FILE 286 var index = -1 287 for (annotation in annotations) { 288 index++ 289 290 if (runtimeAnnotationsOnly && annotation.retention != AnnotationRetention.RUNTIME) { 291 continue 292 } 293 294 var printAnnotation = annotation 295 if (!annotation.targets.contains(target)) { 296 continue 297 } else if ((annotation.isNullnessAnnotation())) { 298 if (skipNullnessAnnotations) { 299 continue 300 } 301 } else if (annotation.qualifiedName == "java.lang.Deprecated") { 302 // Special cased in stubs and signature files: emitted first 303 continue 304 } else { 305 val typedefMode = list.codebase.annotationManager.typedefMode 306 if (typedefMode == TypedefMode.INLINE) { 307 val typedef = annotation.findTypedefAnnotation() 308 if (typedef != null) { 309 printAnnotation = typedef 310 } 311 } else if ( 312 typedefMode == TypedefMode.REFERENCE && 313 annotation.targets === ANNOTATION_SIGNATURE_ONLY && 314 annotation.findTypedefAnnotation() != null 315 ) { 316 // For annotation references, only include the simple name 317 writer.write("@") 318 writer.write( 319 annotation.resolve()?.simpleName() ?: annotation.qualifiedName!! 320 ) 321 if (separateLines) { 322 writer.write("\n") 323 } else { 324 writer.write(" ") 325 } 326 continue 327 } 328 } 329 330 val source = printAnnotation.toSource(target, showDefaultAttrs = false) 331 332 if (omitCommonPackages) { 333 writer.write(AnnotationItem.shortenAnnotation(source)) 334 } else { 335 writer.write(source) 336 } 337 if (separateLines) { 338 writer.write("\n") 339 } else { 340 writer.write(" ") 341 } 342 } 343 } 344 } 345 346 /** The set of classes that may contain polymorphic methods. */ 347 private val polymorphicHandleTypes = 348 setOf( 349 "java.lang.invoke.MethodHandle", 350 "java.lang.invoke.VarHandle", 351 ) 352 353 /** 354 * Check to see whether a native item is actually a method with a polymorphic signature. 355 * 356 * The java compiler treats methods with polymorphic signatures specially. It identifies a 357 * method as being polymorphic according to the rules defined in JLS 15.12.3. See 358 * https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.12.3 for the latest (at 359 * time of writing rules). They state: 360 * 361 * A method is signature polymorphic if all of the following are true: 362 * * It is declared in the [java.lang.invoke.MethodHandle] class or the 363 * [java.lang.invoke.VarHandle] class. 364 * * It has a single variable arity parameter (§8.4.1) whose declared type is Object[]. 365 * * It is native. 366 * 367 * The latter point means that the `native` modifier is an important part of a polymorphic 368 * method's signature even though Metalava generally views the `native` modifier as an 369 * implementation detail that should not be part of the API. So, if this method returns `true` 370 * then the `native` modifier will be output to API signatures. 371 */ 372 private fun isSignaturePolymorphic(item: Item): Boolean { 373 return item is MethodItem && 374 item.containingClass().qualifiedName() in polymorphicHandleTypes && 375 item.parameters().let { parameters -> 376 parameters.size == 1 && 377 parameters[0].let { parameter -> 378 parameter.isVarArgs() && 379 // Check type is java.lang.Object[] 380 parameter.type().let { type -> 381 type is ArrayTypeItem && 382 type.componentType.let { componentType -> 383 componentType is ClassTypeItem && 384 componentType.qualifiedName == "java.lang.Object" 385 } 386 } 387 } 388 } 389 } 390 } 391 392 /** 393 * Synthetic annotation used to mark an API as suppressed for compatibility checks. 394 * 395 * This is added automatically when an API has a meta-annotation that suppresses compatibility but 396 * is defined outside the source set and may not always be available on the classpath. 397 * 398 * Because this is used in API files, it needs to maintain compatibility. 399 */ 400 const val SUPPRESS_COMPATIBILITY_ANNOTATION = "SuppressCompatibility" 401