1 /*
<lambda>null2  * Copyright (C) 2018 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
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.Codebase
21 import com.android.tools.metalava.model.ConstructorItem
22 import com.android.tools.metalava.model.FieldItem
23 import com.android.tools.metalava.model.Item
24 import com.android.tools.metalava.model.MethodItem
25 import com.android.tools.metalava.model.PackageItem
26 import com.android.tools.metalava.model.PropertyItem
27 import com.android.tools.metalava.model.TypeItem
28 import com.android.tools.metalava.model.psi.CodePrinter
29 import com.android.tools.metalava.model.visitors.ApiVisitor
30 import com.android.utils.XmlUtils
31 import java.io.PrintWriter
32 import java.util.function.Predicate
33 
34 /**
35  * Writes out an XML format in the JDiff schema: See $ANDROID/external/jdiff/src/api.xsd
36  * (though limited to the same subset as generated by Doclava; and using the same
37  * conventions for the unspecified parts of the schema, such as what value to put
38  * in the deprecated string. It also uses the same XML formatting.)
39  *
40  * Known differences: Doclava seems to skip enum fields. We don't do that.
41  * Doclava seems to skip type parameters; we do the same.
42  */
43 class JDiffXmlWriter(
44     private val writer: PrintWriter,
45     filterEmit: Predicate<Item>,
46     filterReference: Predicate<Item>,
47     private val preFiltered: Boolean,
48     private val apiName: String? = null
49 ) : ApiVisitor(
50     visitConstructorsAsMethods = false,
51     nestInnerClasses = false,
52     inlineInheritedFields = true,
53     methodComparator = MethodItem.comparator,
54     fieldComparator = FieldItem.comparator,
55     filterEmit = filterEmit,
56     filterReference = filterReference,
57     showUnannotated = options.showUnannotated
58 ) {
59     override fun visitCodebase(codebase: Codebase) {
60         writer.print("<api")
61 
62         if (apiName != null && !options.compatOutput) {
63             // See JDiff's XMLToAPI#nameAPI
64             writer.print(" name=\"")
65             writer.print(apiName)
66             writer.print("\"")
67         }
68 
69         // Specify metalava schema used for metalava:enumConstant
70         writer.print(" xmlns:metalava=\"http://www.android.com/metalava/\"")
71 
72         writer.println(">")
73     }
74 
75     override fun afterVisitCodebase(codebase: Codebase) {
76         writer.println("</api>")
77     }
78 
79     override fun visitPackage(pkg: PackageItem) {
80         // Note: we apparently don't write package annotations anywhere
81         writer.println("<package name=\"${pkg.qualifiedName()}\"\n>")
82     }
83 
84     override fun afterVisitPackage(pkg: PackageItem) {
85         writer.println("</package>")
86     }
87 
88     override fun visitClass(cls: ClassItem) {
89         writer.print('<')
90         // XML format does not seem to special case annotations or enums
91         if (cls.isInterface()) {
92             writer.print("interface")
93         } else {
94             writer.print("class")
95         }
96         writer.print(" name=\"")
97         writer.print(cls.fullName())
98         // Note - to match doclava we don't write out the type parameter list
99         // (cls.typeParameterList()) in JDiff files!
100         writer.print("\"")
101 
102         writeSuperClassAttribute(cls)
103 
104         val modifiers = cls.modifiers
105         writer.print("\n abstract=\"")
106         writer.print(modifiers.isAbstract())
107         writer.print("\"\n static=\"")
108         writer.print(modifiers.isStatic())
109         writer.print("\"\n final=\"")
110         writer.print(modifiers.isFinal())
111         writer.print("\"\n deprecated=\"")
112         writer.print(deprecation(cls))
113         writer.print("\"\n visibility=\"")
114         writer.print(modifiers.getVisibilityModifiers())
115         writer.println("\"\n>")
116 
117         writeInterfaceList(cls)
118 
119         if (cls.isEnum() && compatibility.defaultEnumMethods) {
120             writer.println(
121                 """
122                 <method name="valueOf"
123                  return="${cls.qualifiedName()}"
124                  abstract="false"
125                  native="false"
126                  synchronized="false"
127                  static="true"
128                  final="false"
129                  deprecated="not deprecated"
130                  visibility="public"
131                 >
132                 <parameter name="null" type="java.lang.String">
133                 </parameter>
134                 </method>
135                 <method name="values"
136                  return="${cls.qualifiedName()}[]"
137                  abstract="false"
138                  native="false"
139                  synchronized="false"
140                  static="true"
141                  final="true"
142                  deprecated="not deprecated"
143                  visibility="public"
144                 >
145                 </method>""".trimIndent()
146             )
147         }
148     }
149 
150     fun deprecation(item: Item): String {
151         return if (item.deprecated) {
152             "deprecated"
153         } else {
154             "not deprecated"
155         }
156     }
157 
158     override fun afterVisitClass(cls: ClassItem) {
159         writer.print("</")
160         if (cls.isInterface()) {
161             writer.print("interface")
162         } else {
163             writer.print("class")
164         }
165         writer.println(">")
166     }
167 
168     override fun visitConstructor(constructor: ConstructorItem) {
169         val modifiers = constructor.modifiers
170         writer.print("<constructor name=\"")
171         writer.print(constructor.containingClass().fullName())
172         writer.print("\"\n type=\"")
173         writer.print(constructor.containingClass().qualifiedName())
174         writer.print("\"\n static=\"")
175         writer.print(modifiers.isStatic())
176         writer.print("\"\n final=\"")
177         writer.print(modifiers.isFinal())
178         writer.print("\"\n deprecated=\"")
179         writer.print(deprecation(constructor))
180         writer.print("\"\n visibility=\"")
181         writer.print(modifiers.getVisibilityModifiers())
182         writer.println("\"\n>")
183 
184         // Note - to match doclava we don't write out the type parameter list
185         // (constructor.typeParameterList()) in JDiff files!
186 
187         writeParameterList(constructor)
188         writeThrowsList(constructor)
189         writer.println("</constructor>")
190     }
191 
192     override fun visitField(field: FieldItem) {
193         if (field.isEnumConstant() && compatibility.xmlSkipEnumFields) {
194             return
195         }
196 
197         val modifiers = field.modifiers
198         val initialValue = field.initialValue(true)
199         val value = if (initialValue != null) {
200             if (initialValue is Char && compatibility.xmlCharAsInt) {
201                 initialValue.toInt().toString()
202             } else {
203                 escapeAttributeValue(CodePrinter.constantToSource(initialValue))
204             }
205         } else null
206 
207         writer.print("<field name=\"")
208         writer.print(field.name())
209         writer.print("\"\n type=\"")
210         writer.print(escapeAttributeValue(formatType(field.type())))
211         writer.print("\"\n transient=\"")
212         writer.print(modifiers.isTransient())
213         writer.print("\"\n volatile=\"")
214         writer.print(modifiers.isVolatile())
215         if (value != null) {
216             writer.print("\"\n value=\"")
217             writer.print(value)
218         } else if (compatibility.xmlShowArrayFieldsAsNull && (field.type().isArray())) {
219             writer.print("\"\n value=\"null")
220         }
221 
222         writer.print("\"\n static=\"")
223         writer.print(modifiers.isStatic())
224         writer.print("\"\n final=\"")
225         writer.print(modifiers.isFinal())
226         writer.print("\"\n deprecated=\"")
227         writer.print(deprecation(field))
228         writer.print("\"\n visibility=\"")
229         writer.print(modifiers.getVisibilityModifiers())
230         writer.print("\"")
231         if (field.isEnumConstant()) {
232             // Metalava extension. JDiff doesn't support it.
233             writer.print("\n metalava:enumConstant=\"true\"")
234         }
235         writer.println("\n>\n</field>")
236     }
237 
238     override fun visitProperty(property: PropertyItem) {
239         // Not supported by JDiff
240     }
241 
242     override fun visitMethod(method: MethodItem) {
243         val modifiers = method.modifiers
244 
245         if (method.containingClass().isAnnotationType() && compatibility.xmlSkipAnnotationMethods) {
246             return
247         }
248 
249         // Note - to match doclava we don't write out the type parameter list
250         // (method.typeParameterList()) in JDiff files!
251 
252         writer.print("<method name=\"")
253         writer.print(method.name())
254         method.returnType()?.let {
255             writer.print("\"\n return=\"")
256             writer.print(escapeAttributeValue(formatType(it)))
257         }
258         writer.print("\"\n abstract=\"")
259         writer.print(modifiers.isAbstract())
260         writer.print("\"\n native=\"")
261         writer.print(modifiers.isNative())
262         writer.print("\"\n synchronized=\"")
263         writer.print(modifiers.isSynchronized())
264         writer.print("\"\n static=\"")
265         writer.print(modifiers.isStatic())
266         writer.print("\"\n final=\"")
267         writer.print(modifiers.isFinal())
268         writer.print("\"\n deprecated=\"")
269         writer.print(deprecation(method))
270         writer.print("\"\n visibility=\"")
271         writer.print(modifiers.getVisibilityModifiers())
272         writer.println("\"\n>")
273 
274         writeParameterList(method)
275         writeThrowsList(method)
276         writer.println("</method>")
277     }
278 
279     private fun writeSuperClassAttribute(cls: ClassItem) {
280         if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
281             // Written in the interface section instead
282             return
283         }
284 
285         val superClass = if (preFiltered)
286             cls.superClassType()
287         else cls.filteredSuperClassType(filterReference)
288 
289         val superClassString =
290             when {
291                 cls.isAnnotationType() -> if (compatibility.xmlAnnotationAsObject) {
292                     JAVA_LANG_OBJECT
293                 } else {
294                     JAVA_LANG_ANNOTATION
295                 }
296                 superClass != null -> {
297                     // doclava seems to include java.lang.Object for classes but not interfaces
298                     if (!cls.isClass() && superClass.isJavaLangObject()) {
299                         return
300                     }
301                     escapeAttributeValue(
302                         formatType(
303                             superClass.toTypeString(
304                                 erased = compatibility.omitTypeParametersInInterfaces,
305                                 context = superClass.asClass()
306                             )
307                         )
308                     )
309                 }
310                 cls.isEnum() -> JAVA_LANG_ENUM
311                 else -> return
312             }
313         writer.print("\n extends=\"")
314         writer.print(superClassString)
315         writer.print("\"")
316     }
317 
318     private fun writeInterfaceList(cls: ClassItem) {
319         var interfaces = if (preFiltered)
320             cls.interfaceTypes().asSequence()
321         else cls.filteredInterfaceTypes(filterReference).asSequence()
322 
323         if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
324             val superClassType = cls.superClassType()
325             if (superClassType?.isJavaLangObject() == false) {
326                 interfaces += superClassType
327             }
328         }
329 
330         if (interfaces.any()) {
331             interfaces.sortedWith(TypeItem.comparator).forEach { item ->
332                 writer.print("<implements name=\"")
333                 val type = item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces, context = cls)
334                 writer.print(escapeAttributeValue(formatType(type)))
335                 writer.println("\">\n</implements>")
336             }
337         }
338     }
339 
340     private fun writeParameterList(method: MethodItem) {
341         method.parameters().asSequence().forEach { parameter ->
342             // NOTE: We report parameter name as "null" rather than the real name to match
343             // doclava's behavior
344             writer.print("<parameter name=\"null\" type=\"")
345             writer.print(escapeAttributeValue(formatType(parameter.type())))
346             writer.println("\">")
347             writer.println("</parameter>")
348         }
349     }
350 
351     private fun formatType(type: TypeItem): String = formatType(type.toTypeString())
352 
353     private fun formatType(typeString: String): String {
354         // In JDiff we always want to include spaces after commas; the API signature tests depend
355         // on this.
356         return typeString.replace(",", ", ").replace(",  ", ", ")
357     }
358 
359     private fun writeThrowsList(method: MethodItem) {
360         val throws = when {
361             preFiltered -> method.throwsTypes().asSequence()
362             compatibility.filterThrowsClasses -> method.filteredThrowsTypes(filterReference).asSequence()
363             else -> method.throwsTypes().asSequence()
364         }
365         if (throws.any()) {
366             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type ->
367                 writer.print("<exception name=\"")
368                 if (options.compatOutput) {
369                     writer.print(type.simpleName())
370                 } else {
371                     writer.print(type.fullName())
372                 }
373                 writer.print("\" type=\"")
374                 writer.print(type.qualifiedName())
375                 writer.println("\">")
376                 writer.println("</exception>")
377             }
378         }
379     }
380 
381     private fun escapeAttributeValue(s: String): String {
382         val escaped = XmlUtils.toXmlAttributeValue(s)
383         return if (compatibility.xmlEscapeGreaterThan && escaped.contains(">")) {
384             escaped.replace(">", "&gt;")
385         } else {
386             escaped
387         }
388     }
389 }