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