• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.text
18 
19 import com.android.tools.metalava.model.CallableItem
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.ClassTypeItem
22 import com.android.tools.metalava.model.ConstructorItem
23 import com.android.tools.metalava.model.DelegatedVisitor
24 import com.android.tools.metalava.model.ExceptionTypeItem
25 import com.android.tools.metalava.model.FieldItem
26 import com.android.tools.metalava.model.Item
27 import com.android.tools.metalava.model.MethodItem
28 import com.android.tools.metalava.model.ModifierListWriter
29 import com.android.tools.metalava.model.PackageItem
30 import com.android.tools.metalava.model.PropertyItem
31 import com.android.tools.metalava.model.StripJavaLangPrefix
32 import com.android.tools.metalava.model.TypeItem
33 import com.android.tools.metalava.model.TypeParameterList
34 import com.android.tools.metalava.model.TypeStringConfiguration
35 import com.android.tools.metalava.model.text.FileFormat.TypeArgumentSpacing
36 import com.android.tools.metalava.model.visitors.ApiPredicate
37 import com.android.tools.metalava.model.visitors.ApiType
38 import com.android.tools.metalava.model.visitors.ApiVisitor
39 import com.android.tools.metalava.model.visitors.FilteringApiVisitor
40 import java.io.PrintWriter
41 
42 class SignatureWriter(
43     private val writer: PrintWriter,
44     private var emitHeader: EmitFileHeader = EmitFileHeader.ALWAYS,
45     private val fileFormat: FileFormat,
46 ) : DelegatedVisitor {
47 
48     init {
49         // If a header must always be written out (even if the file is empty) then write it here.
50         if (emitHeader == EmitFileHeader.ALWAYS) {
51             writer.print(fileFormat.header())
52         }
53     }
54 
55     private val modifierListWriter =
56         ModifierListWriter.forSignature(
57             writer = writer,
58             skipNullnessAnnotations = fileFormat.kotlinStyleNulls,
59         )
60 
61     internal fun write(text: String) {
62         // If a header must only be written out when the file is not empty then write it here as
63         // this is not called
64         if (emitHeader == EmitFileHeader.IF_NONEMPTY_FILE) {
65             writer.print(fileFormat.header())
66             // Remember that the header was written out, so it will not be written again.
67             emitHeader = EmitFileHeader.NEVER
68         }
69         writer.print(text)
70     }
71 
72     override fun visitPackage(pkg: PackageItem) {
73         write("package ")
74         writeModifiers(pkg)
75         write("${pkg.qualifiedName()} {\n\n")
76     }
77 
78     override fun afterVisitPackage(pkg: PackageItem) {
79         write("}\n\n")
80     }
81 
82     override fun visitConstructor(constructor: ConstructorItem) {
83         write("    ctor ")
84         writeModifiers(constructor)
85         writeTypeParameterList(constructor.typeParameterList, addSpace = true)
86         write(constructor.containingClass().fullName())
87         writeParameterList(constructor)
88         writeThrowsList(constructor)
89         write(";\n")
90     }
91 
92     override fun visitField(field: FieldItem) {
93         val name = if (field.isEnumConstant()) "enum_constant" else "field"
94         write("    ")
95         write(name)
96         write(" ")
97         writeModifiers(field)
98 
99         if (fileFormat.kotlinNameTypeOrder) {
100             // Kotlin style: write the name of the field, then the type.
101             write(field.name())
102             write(": ")
103             writeType(field.type())
104         } else {
105             // Java style: write the type, then the name of the field.
106             writeType(field.type())
107             write(" ")
108             write(field.name())
109         }
110 
111         field.writeValueWithSemicolon(
112             writer,
113             allowDefaultValue = false,
114             requireInitialValue = false
115         )
116         write("\n")
117     }
118 
119     override fun visitProperty(property: PropertyItem) {
120         write("    property ")
121         writeModifiers(property)
122         writeTypeParameterList(property.typeParameterList, addSpace = true)
123         if (fileFormat.kotlinNameTypeOrder) {
124             // Kotlin style: write the name of the property, then the type.
125             property.receiver?.let {
126                 writeType(it)
127                 write(".")
128             }
129             write(property.name())
130             write(": ")
131             writeType(property.type())
132         } else {
133             // Java style: write the type, then the name of the property.
134             writeType(property.type())
135             write(" ")
136             property.receiver?.let {
137                 writeType(it)
138                 write(".")
139             }
140             write(property.name())
141         }
142         write(";\n")
143     }
144 
145     override fun visitMethod(method: MethodItem) {
146         write("    method ")
147         writeModifiers(method)
148         writeTypeParameterList(method.typeParameterList, addSpace = true)
149 
150         if (fileFormat.kotlinNameTypeOrder) {
151             // Kotlin style: write the name of the method and the parameters, then the type.
152             write(method.name())
153             writeParameterList(method)
154             write(": ")
155             writeType(method.returnType())
156         } else {
157             // Java style: write the type, then the name of the method and the parameters.
158             writeType(method.returnType())
159             write(" ")
160             write(method.name())
161             writeParameterList(method)
162         }
163 
164         writeThrowsList(method)
165 
166         if (method.containingClass().isAnnotationType()) {
167             val default = method.legacyDefaultValue()
168             if (default.isNotEmpty()) {
169                 write(" default ")
170                 write(default)
171             }
172         }
173 
174         write(";\n")
175     }
176 
177     override fun visitClass(cls: ClassItem) {
178         write("  ")
179 
180         writeModifiers(cls)
181 
182         if (cls.isAnnotationType()) {
183             write("@interface")
184         } else if (cls.isInterface()) {
185             write("interface")
186         } else if (cls.isEnum()) {
187             write("enum")
188         } else {
189             write("class")
190         }
191         write(" ")
192         write(cls.fullName())
193         writeTypeParameterList(cls.typeParameterList, addSpace = false)
194         writeSuperClassStatement(cls)
195         writeInterfaceList(cls)
196 
197         write(" {\n")
198     }
199 
200     override fun afterVisitClass(cls: ClassItem) {
201         write("  }\n\n")
202     }
203 
204     private fun writeModifiers(item: Item) {
205         modifierListWriter.write(item, normalizeFinal = fileFormat.normalizeFinalModifier)
206     }
207 
208     private fun writeSuperClassStatement(cls: ClassItem) {
209         if (cls.isEnum() || cls.isAnnotationType() || cls.isInterface()) {
210             return
211         }
212 
213         /** Get the super class type, ignoring java.lang.Object. */
214         val superClassType = cls.superClassType()
215         if (superClassType == null || superClassType.isJavaLangObject()) return
216 
217         write(" extends")
218         writeExtendsOrImplementsType(superClassType)
219     }
220 
221     /**
222      * Legacy [TypeStringConfiguration] when writing super types in [writeExtendsOrImplementsType].
223      */
224     private val legacySuperTypeStringConfiguration =
225         TypeStringConfiguration(
226             annotations = fileFormat.includeTypeUseAnnotations,
227             kotlinStyleNulls = fileFormat.kotlinStyleNulls,
228         )
229 
230     private fun writeExtendsOrImplementsType(typeItem: TypeItem) {
231         write(" ")
232 
233         if (fileFormat.stripJavaLangPrefix != StripJavaLangPrefix.LEGACY) {
234             writeType(typeItem)
235         } else {
236             val superClassString = typeItem.toTypeString(legacySuperTypeStringConfiguration)
237             write(superClassString)
238         }
239     }
240 
241     private fun writeInterfaceList(cls: ClassItem) {
242         if (cls.isAnnotationType()) {
243             return
244         }
245 
246         // There is no need to sort the interface types as that is done by the `interfaceTypes()`
247         // method, using the `interfaceListAccessor(...)` method.
248         val orderedInterfaces = cls.interfaceTypes()
249         if (orderedInterfaces.isEmpty()) return
250 
251         val label = if (cls.isInterface()) " extends" else " implements"
252         write(label)
253 
254         orderedInterfaces.forEach { typeItem -> writeExtendsOrImplementsType(typeItem) }
255     }
256 
257     /** [TypeStringConfiguration] for use when writing types in [writeTypeParameterList]. */
258     private val typeParameterItemStringConfiguration =
259         TypeStringConfiguration(
260             spaceBetweenTypeArguments = fileFormat.typeArgumentSpacing != TypeArgumentSpacing.NONE,
261             stripJavaLangPrefix =
262                 // Only strip `java.lang.` prefix if always requested. That is because the LEGACY
263                 // behavior is not to strip `java.lang.` prefix in bounds.
264                 when (fileFormat.stripJavaLangPrefix) {
265                     StripJavaLangPrefix.ALWAYS -> StripJavaLangPrefix.ALWAYS
266                     else -> StripJavaLangPrefix.NEVER
267                 },
268         )
269 
270     private fun writeTypeParameterList(typeList: TypeParameterList, addSpace: Boolean) {
271         val typeListString = typeList.toSource(typeParameterItemStringConfiguration)
272         if (typeListString.isNotEmpty()) {
273             write(typeListString)
274             if (addSpace) {
275                 write(" ")
276             }
277         }
278     }
279 
280     private fun writeParameterList(callable: CallableItem) {
281         write("(")
282         var writtenParams = 0
283         callable.parameters().asSequence().forEach { parameter ->
284             if (writtenParams > 0) {
285                 write(", ")
286             }
287             if (parameter.hasDefaultValue() && fileFormat.includeDefaultParameterValues) {
288                 // Indicate the parameter has a default.
289                 write("optional ")
290             }
291             writeModifiers(parameter)
292 
293             if (fileFormat.kotlinNameTypeOrder) {
294                 // Kotlin style: the parameter must have a name (use `_` if it doesn't have a public
295                 // name). Write the name and then the type.
296                 val name = parameter.publicName() ?: "_"
297                 write(name)
298                 write(": ")
299                 writeType(parameter.type())
300             } else {
301                 // Java style: write the type, then the name if it has a public name.
302                 writeType(parameter.type())
303                 val name = parameter.publicName()
304                 if (name != null) {
305                     write(" ")
306                     write(name)
307                 }
308             }
309 
310             writtenParams++
311         }
312         write(")")
313     }
314 
315     /** [TypeStringConfiguration] for use when writing types in [writeType]. */
316     private val typeStringConfiguration =
317         TypeStringConfiguration(
318             annotations = fileFormat.includeTypeUseAnnotations,
319             kotlinStyleNulls = fileFormat.kotlinStyleNulls,
320             spaceBetweenTypeArguments = fileFormat.typeArgumentSpacing == TypeArgumentSpacing.SPACE,
321             stripJavaLangPrefix = fileFormat.stripJavaLangPrefix,
322         )
323 
324     private fun writeType(type: TypeItem?) {
325         type ?: return
326 
327         var typeString = type.toTypeString(typeStringConfiguration)
328 
329         // Strip androidx.annotation. prefix from annotations.
330         typeString = TypeItem.shortenTypes(typeString)
331 
332         write(typeString)
333     }
334 
335     private fun writeThrowsList(callable: CallableItem) {
336         val throws = callable.throwsTypes()
337         if (throws.isNotEmpty()) {
338             write(" throws ")
339             throws.sortedWith(ExceptionTypeItem.fullNameComparator).forEachIndexed { i, type ->
340                 if (i > 0) {
341                     write(", ")
342                 }
343                 if (fileFormat.stripJavaLangPrefix != StripJavaLangPrefix.LEGACY) writeType(type)
344                 else write(type.toTypeString())
345             }
346         }
347     }
348 }
349 
350 enum class EmitFileHeader {
351     ALWAYS,
352     NEVER,
353     IF_NONEMPTY_FILE
354 }
355 
356 /**
357  * Get the filtered list of [ClassItem.interfaceTypes], in the correct legacy order.
358  *
359  * Historically, on interface classes its first implemented interface type was stored in the
360  * [ClassItem.superClassType] and if it was not filtered out it was always written out first in the
361  * signature files, while the rest of the interface types were sorted by their [ClassItem.fullName].
362  * This implements that behavior.
363  */
getInterfacesInOrdernull364 private fun getInterfacesInOrder(
365     classItem: ClassItem,
366     filteredInterfaceTypes: List<ClassTypeItem>,
367     unfilteredInterfaceTypes: List<ClassTypeItem>,
368 ): List<ClassTypeItem> {
369     // Sort before prepending the super class (if this is an interface) as the super class
370     // always comes first because it was previously written out by writeSuperClassStatement.
371     @Suppress("DEPRECATION")
372     val sortedInterfaces = filteredInterfaceTypes.sortedWith(TypeItem.partialComparator)
373 
374     // Combine the super class and interfaces into a full list of them.
375     if (classItem.isInterface()) {
376         // Previously, when the first interface in the extends list was stored in
377         // superClass, if that interface was visible in the signature then it would always
378         // be first even though the other interfaces are sorted in alphabetical order. This
379         // implements similar logic.
380         val firstUnfilteredInterfaceType = unfilteredInterfaceTypes.first()
381 
382         // Check to see whether the first unfiltered interface type is in the sorted set of
383         // interfaces. If it is, and it is not the first then it needs moving to the beginning.
384         val index = sortedInterfaces.indexOf(firstUnfilteredInterfaceType)
385         if (index > 0) {
386             // Create a mutable list and move the first unfiltered interface type to the beginning.
387             return sortedInterfaces.toMutableList().also { mutable ->
388                 // Remove it from its existing position.
389                 mutable.removeAt(index)
390 
391                 // Add it at the beginning.
392                 mutable.add(0, firstUnfilteredInterfaceType)
393             }
394         }
395     }
396 
397     return sortedInterfaces
398 }
399 
400 /**
401  * Create an [ApiVisitor] that will filter the [Item] to which is applied according to the supplied
402  * parameters and in a manner appropriate for writing signatures, e.g. flattening nested classes. It
403  * will delegate any visitor calls that pass through its filter to this [SignatureWriter] instance.
404  */
createFilteringVisitorForSignaturesnull405 fun createFilteringVisitorForSignatures(
406     delegate: DelegatedVisitor,
407     fileFormat: FileFormat,
408     apiType: ApiType,
409     preFiltered: Boolean,
410     showUnannotated: Boolean,
411     apiPredicateConfig: ApiPredicate.Config,
412 ): ApiVisitor {
413     val apiFilters = apiType.getApiFilters(apiPredicateConfig)
414 
415     val (interfaceListSorter, interfaceListComparator) =
416         if (fileFormat.sortWholeExtendsList) Pair(null, TypeItem.totalComparator)
417         else Pair(::getInterfacesInOrder, null)
418     return FilteringApiVisitor(
419         delegate = delegate,
420         inlineInheritedFields = true,
421         callableComparator = fileFormat.overloadedMethodOrder.comparator,
422         interfaceListSorter = interfaceListSorter,
423         interfaceListComparator = interfaceListComparator,
424         apiFilters = apiFilters,
425         preFiltered = preFiltered,
426         showUnannotated = showUnannotated,
427     )
428 }
429