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