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