• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         writer.println(">")
70     }
71 
72     override fun afterVisitCodebase(codebase: Codebase) {
73         writer.println("</api>")
74     }
75 
76     override fun visitPackage(pkg: PackageItem) {
77         // Note: we apparently don't write package annotations anywhere
78         writer.println("<package name=\"${pkg.qualifiedName()}\"\n>")
79     }
80 
81     override fun afterVisitPackage(pkg: PackageItem) {
82         writer.println("</package>")
83     }
84 
85     override fun visitClass(cls: ClassItem) {
86         writer.print('<')
87         // XML format does not seem to special case annotations or enums
88         if (cls.isInterface()) {
89             writer.print("interface")
90         } else {
91             writer.print("class")
92         }
93         writer.print(" name=\"")
94         writer.print(cls.fullName())
95         // Note - to match doclava we don't write out the type parameter list
96         // (cls.typeParameterList()) in JDiff files!
97         writer.print("\"")
98 
99         writeSuperClassAttribute(cls)
100 
101         val modifiers = cls.modifiers
102         writer.print("\n abstract=\"")
103         writer.print(modifiers.isAbstract())
104         writer.print("\"\n static=\"")
105         writer.print(modifiers.isStatic())
106         writer.print("\"\n final=\"")
107         writer.print(modifiers.isFinal())
108         writer.print("\"\n deprecated=\"")
109         writer.print(deprecation(cls))
110         writer.print("\"\n visibility=\"")
111         writer.print(modifiers.getVisibilityModifiers())
112         writer.println("\"\n>")
113 
114         writeInterfaceList(cls)
115 
116         if (cls.isEnum() && compatibility.defaultEnumMethods) {
117             writer.println(
118                 """
119                 <method name="valueOf"
120                  return="${cls.qualifiedName()}"
121                  abstract="false"
122                  native="false"
123                  synchronized="false"
124                  static="true"
125                  final="false"
126                  deprecated="not deprecated"
127                  visibility="public"
128                 >
129                 <parameter name="null" type="java.lang.String">
130                 </parameter>
131                 </method>
132                 <method name="values"
133                  return="${cls.qualifiedName()}[]"
134                  abstract="false"
135                  native="false"
136                  synchronized="false"
137                  static="true"
138                  final="true"
139                  deprecated="not deprecated"
140                  visibility="public"
141                 >
142                 </method>""".trimIndent()
143             )
144         }
145     }
146 
147     fun deprecation(item: Item): String {
148         return if (item.deprecated) {
149             "deprecated"
150         } else {
151             "not deprecated"
152         }
153     }
154 
155     override fun afterVisitClass(cls: ClassItem) {
156         writer.print("</")
157         if (cls.isInterface()) {
158             writer.print("interface")
159         } else {
160             writer.print("class")
161         }
162         writer.println(">")
163     }
164 
165     override fun visitConstructor(constructor: ConstructorItem) {
166         val modifiers = constructor.modifiers
167         writer.print("<constructor name=\"")
168         writer.print(constructor.containingClass().fullName())
169         writer.print("\"\n type=\"")
170         writer.print(constructor.containingClass().qualifiedName())
171         writer.print("\"\n static=\"")
172         writer.print(modifiers.isStatic())
173         writer.print("\"\n final=\"")
174         writer.print(modifiers.isFinal())
175         writer.print("\"\n deprecated=\"")
176         writer.print(deprecation(constructor))
177         writer.print("\"\n visibility=\"")
178         writer.print(modifiers.getVisibilityModifiers())
179         writer.println("\"\n>")
180 
181         // Note - to match doclava we don't write out the type parameter list
182         // (constructor.typeParameterList()) in JDiff files!
183 
184         writeParameterList(constructor)
185         writeThrowsList(constructor)
186         writer.println("</constructor>")
187     }
188 
189     override fun visitField(field: FieldItem) {
190         if (field.isEnumConstant() && compatibility.xmlSkipEnumFields) {
191             return
192         }
193 
194         val modifiers = field.modifiers
195         val initialValue = field.initialValue(true)
196         val value = if (initialValue != null) {
197             if (initialValue is Char && compatibility.xmlCharAsInt) {
198                 initialValue.toInt().toString()
199             } else {
200                 escapeAttributeValue(CodePrinter.constantToSource(initialValue))
201             }
202         } else null
203 
204         writer.print("<field name=\"")
205         writer.print(field.name())
206         writer.print("\"\n type=\"")
207         writer.print(escapeAttributeValue(formatType(field.type())))
208         writer.print("\"\n transient=\"")
209         writer.print(modifiers.isTransient())
210         writer.print("\"\n volatile=\"")
211         writer.print(modifiers.isVolatile())
212         if (value != null) {
213             writer.print("\"\n value=\"")
214             writer.print(value)
215         } else if (compatibility.xmlShowArrayFieldsAsNull && (field.type().isArray())) {
216             writer.print("\"\n value=\"null")
217         }
218 
219         writer.print("\"\n static=\"")
220         writer.print(modifiers.isStatic())
221         writer.print("\"\n final=\"")
222         writer.print(modifiers.isFinal())
223         writer.print("\"\n deprecated=\"")
224         writer.print(deprecation(field))
225         writer.print("\"\n visibility=\"")
226         writer.print(modifiers.getVisibilityModifiers())
227         writer.println("\"\n>")
228 
229         writer.println("</field>")
230     }
231 
232     override fun visitProperty(property: PropertyItem) {
233         // Not supported by JDiff
234     }
235 
236     override fun visitMethod(method: MethodItem) {
237         val modifiers = method.modifiers
238 
239         if (method.containingClass().isAnnotationType() && compatibility.xmlSkipAnnotationMethods) {
240             return
241         }
242 
243         // Note - to match doclava we don't write out the type parameter list
244         // (method.typeParameterList()) in JDiff files!
245 
246         writer.print("<method name=\"")
247         writer.print(method.name())
248         method.returnType()?.let {
249             writer.print("\"\n return=\"")
250             writer.print(escapeAttributeValue(formatType(it)))
251         }
252         writer.print("\"\n abstract=\"")
253         writer.print(modifiers.isAbstract())
254         writer.print("\"\n native=\"")
255         writer.print(modifiers.isNative())
256         writer.print("\"\n synchronized=\"")
257         writer.print(modifiers.isSynchronized())
258         writer.print("\"\n static=\"")
259         writer.print(modifiers.isStatic())
260         writer.print("\"\n final=\"")
261         writer.print(modifiers.isFinal())
262         writer.print("\"\n deprecated=\"")
263         writer.print(deprecation(method))
264         writer.print("\"\n visibility=\"")
265         writer.print(modifiers.getVisibilityModifiers())
266         writer.println("\"\n>")
267 
268         writeParameterList(method)
269         writeThrowsList(method)
270         writer.println("</method>")
271     }
272 
273     private fun writeSuperClassAttribute(cls: ClassItem) {
274         if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
275             // Written in the interface section instead
276             return
277         }
278 
279         val superClass = if (preFiltered)
280             cls.superClassType()
281         else cls.filteredSuperClassType(filterReference)
282 
283         val superClassString =
284             when {
285                 cls.isAnnotationType() -> if (compatibility.xmlAnnotationAsObject) {
286                     JAVA_LANG_OBJECT
287                 } else {
288                     JAVA_LANG_ANNOTATION
289                 }
290                 superClass != null -> {
291                     // doclava seems to include java.lang.Object for classes but not interfaces
292                     if (!cls.isClass() && superClass.isJavaLangObject()) {
293                         return
294                     }
295                     escapeAttributeValue(
296                         formatType(
297                             superClass.toTypeString(
298                                 erased = compatibility.omitTypeParametersInInterfaces,
299                                 context = superClass.asClass()
300                             )
301                         )
302                     )
303                 }
304                 cls.isEnum() -> JAVA_LANG_ENUM
305                 else -> return
306             }
307         writer.print("\n extends=\"")
308         writer.print(superClassString)
309         writer.print("\"")
310     }
311 
312     private fun writeInterfaceList(cls: ClassItem) {
313         var interfaces = if (preFiltered)
314             cls.interfaceTypes().asSequence()
315         else cls.filteredInterfaceTypes(filterReference).asSequence()
316 
317         if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
318             val superClassType = cls.superClassType()
319             if (superClassType?.isJavaLangObject() == false) {
320                 interfaces += superClassType
321             }
322         }
323 
324         if (interfaces.any()) {
325             interfaces.sortedWith(TypeItem.comparator).forEach { item ->
326                 writer.print("<implements name=\"")
327                 val type = item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces, context = cls)
328                 writer.print(escapeAttributeValue(formatType(type)))
329                 writer.println("\">\n</implements>")
330             }
331         }
332     }
333 
334     private fun writeParameterList(method: MethodItem) {
335         method.parameters().asSequence().forEach { parameter ->
336             // NOTE: We report parameter name as "null" rather than the real name to match
337             // doclava's behavior
338             writer.print("<parameter name=\"null\" type=\"")
339             writer.print(escapeAttributeValue(formatType(parameter.type())))
340             writer.println("\">")
341             writer.println("</parameter>")
342         }
343     }
344 
345     private fun formatType(type: TypeItem): String = formatType(type.toTypeString())
346 
347     private fun formatType(typeString: String): String {
348         // In JDiff we always want to include spaces after commas; the API signature tests depend
349         // on this.
350         return typeString.replace(",", ", ").replace(",  ", ", ")
351     }
352 
353     private fun writeThrowsList(method: MethodItem) {
354         val throws = when {
355             preFiltered -> method.throwsTypes().asSequence()
356             compatibility.filterThrowsClasses -> method.filteredThrowsTypes(filterReference).asSequence()
357             else -> method.throwsTypes().asSequence()
358         }
359         if (throws.any()) {
360             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type ->
361                 writer.print("<exception name=\"")
362                 if (options.compatOutput) {
363                     writer.print(type.simpleName())
364                 } else {
365                     writer.print(type.fullName())
366                 }
367                 writer.print("\" type=\"")
368                 writer.print(type.qualifiedName())
369                 writer.println("\">")
370                 writer.println("</exception>")
371             }
372         }
373     }
374 
375     private fun escapeAttributeValue(s: String): String {
376         val escaped = XmlUtils.toXmlAttributeValue(s)
377         return if (compatibility.xmlEscapeGreaterThan && escaped.contains(">")) {
378             escaped.replace(">", "&gt;")
379         } else {
380             escaped
381         }
382     }
383 }