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