• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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