• 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) {
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 
120     fun deprecation(item: Item): String {
121         return if (item.deprecated) {
122             "deprecated"
123         } else {
124             "not deprecated"
125         }
126     }
127 
128     override fun afterVisitClass(cls: ClassItem) {
129         writer.print("</")
130         if (cls.isInterface()) {
131             writer.print("interface")
132         } else {
133             writer.print("class")
134         }
135         writer.println(">")
136     }
137 
138     override fun visitConstructor(constructor: ConstructorItem) {
139         val modifiers = constructor.modifiers
140         writer.print("<constructor name=\"")
141         writer.print(constructor.containingClass().fullName())
142         writer.print("\"\n type=\"")
143         writer.print(constructor.containingClass().qualifiedName())
144         writer.print("\"\n static=\"")
145         writer.print(modifiers.isStatic())
146         writer.print("\"\n final=\"")
147         writer.print(modifiers.isFinal())
148         writer.print("\"\n deprecated=\"")
149         writer.print(deprecation(constructor))
150         writer.print("\"\n visibility=\"")
151         writer.print(modifiers.getVisibilityModifiers())
152         writer.println("\"\n>")
153 
154         // Note - to match doclava we don't write out the type parameter list
155         // (constructor.typeParameterList()) in JDiff files!
156 
157         writeParameterList(constructor)
158         writeThrowsList(constructor)
159         writer.println("</constructor>")
160     }
161 
162     override fun visitField(field: FieldItem) {
163         val modifiers = field.modifiers
164         val initialValue = field.initialValue(true)
165         val value = if (initialValue != null) {
166             XmlUtils.toXmlAttributeValue(CodePrinter.constantToSource(initialValue))
167         } else null
168 
169         writer.print("<field name=\"")
170         writer.print(field.name())
171         writer.print("\"\n type=\"")
172         writer.print(XmlUtils.toXmlAttributeValue(formatType(field.type())))
173         writer.print("\"\n transient=\"")
174         writer.print(modifiers.isTransient())
175         writer.print("\"\n volatile=\"")
176         writer.print(modifiers.isVolatile())
177         if (value != null) {
178             writer.print("\"\n value=\"")
179             writer.print(value)
180         }
181 
182         writer.print("\"\n static=\"")
183         writer.print(modifiers.isStatic())
184         writer.print("\"\n final=\"")
185         writer.print(modifiers.isFinal())
186         writer.print("\"\n deprecated=\"")
187         writer.print(deprecation(field))
188         writer.print("\"\n visibility=\"")
189         writer.print(modifiers.getVisibilityModifiers())
190         writer.print("\"")
191         if (field.isEnumConstant()) {
192             // Metalava extension. JDiff doesn't support it.
193             writer.print("\n metalava:enumConstant=\"true\"")
194         }
195         writer.println("\n>\n</field>")
196     }
197 
198     override fun visitProperty(property: PropertyItem) {
199         // Not supported by JDiff
200     }
201 
202     override fun visitMethod(method: MethodItem) {
203         val modifiers = method.modifiers
204 
205         // Note - to match doclava we don't write out the type parameter list
206         // (method.typeParameterList()) in JDiff files!
207 
208         writer.print("<method name=\"")
209         writer.print(method.name())
210         method.returnType()?.let {
211             writer.print("\"\n return=\"")
212             writer.print(XmlUtils.toXmlAttributeValue(formatType(it)))
213         }
214         writer.print("\"\n abstract=\"")
215         writer.print(modifiers.isAbstract())
216         writer.print("\"\n native=\"")
217         writer.print(modifiers.isNative())
218         writer.print("\"\n synchronized=\"")
219         writer.print(modifiers.isSynchronized())
220         writer.print("\"\n static=\"")
221         writer.print(modifiers.isStatic())
222         writer.print("\"\n final=\"")
223         writer.print(modifiers.isFinal())
224         writer.print("\"\n deprecated=\"")
225         writer.print(deprecation(method))
226         writer.print("\"\n visibility=\"")
227         writer.print(modifiers.getVisibilityModifiers())
228         writer.println("\"\n>")
229 
230         writeParameterList(method)
231         writeThrowsList(method)
232         writer.println("</method>")
233     }
234 
235     private fun writeSuperClassAttribute(cls: ClassItem) {
236         val superClass = if (preFiltered)
237             cls.superClassType()
238         else cls.filteredSuperClassType(filterReference)
239 
240         val superClassString =
241             when {
242                 cls.isAnnotationType() -> JAVA_LANG_ANNOTATION
243                 superClass != null -> {
244                     // doclava seems to include java.lang.Object for classes but not interfaces
245                     if (!cls.isClass() && superClass.isJavaLangObject()) {
246                         return
247                     }
248                     XmlUtils.toXmlAttributeValue(
249                         formatType(superClass.toTypeString(context = superClass.asClass()))
250                     )
251                 }
252                 cls.isEnum() -> JAVA_LANG_ENUM
253                 else -> return
254             }
255         writer.print("\n extends=\"")
256         writer.print(superClassString)
257         writer.print("\"")
258     }
259 
260     private fun writeInterfaceList(cls: ClassItem) {
261         var interfaces = if (preFiltered)
262             cls.interfaceTypes().asSequence()
263         else cls.filteredInterfaceTypes(filterReference).asSequence()
264 
265         if (interfaces.any()) {
266             interfaces.sortedWith(TypeItem.comparator).forEach { item ->
267                 writer.print("<implements name=\"")
268                 val type = item.toTypeString(context = cls)
269                 writer.print(XmlUtils.toXmlAttributeValue(formatType(type)))
270                 writer.println("\">\n</implements>")
271             }
272         }
273     }
274 
275     private fun writeParameterList(method: MethodItem) {
276         method.parameters().asSequence().forEach { parameter ->
277             // NOTE: We report parameter name as "null" rather than the real name to match
278             // doclava's behavior
279             writer.print("<parameter name=\"null\" type=\"")
280             writer.print(XmlUtils.toXmlAttributeValue(formatType(parameter.type())))
281             writer.println("\">")
282             writer.println("</parameter>")
283         }
284     }
285 
286     private fun formatType(type: TypeItem): String = formatType(type.toTypeString())
287 
288     private fun formatType(typeString: String): String {
289         // In JDiff we always want to include spaces after commas; the API signature tests depend
290         // on this.
291         return typeString.replace(",", ", ").replace(",  ", ", ")
292     }
293 
294     private fun writeThrowsList(method: MethodItem) {
295         val throws = when {
296             preFiltered -> method.throwsTypes().asSequence()
297             else -> method.filteredThrowsTypes(filterReference).asSequence()
298         }
299         if (throws.any()) {
300             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type ->
301                 writer.print("<exception name=\"")
302                 writer.print(type.fullName())
303                 writer.print("\" type=\"")
304                 writer.print(type.qualifiedName())
305                 writer.println("\">")
306                 writer.println("</exception>")
307             }
308         }
309     }
310 }
311