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