| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tools.metalava |
| |
| import com.android.tools.metalava.model.ClassItem |
| import com.android.tools.metalava.model.Codebase |
| import com.android.tools.metalava.model.ConstructorItem |
| import com.android.tools.metalava.model.FieldItem |
| import com.android.tools.metalava.model.Item |
| import com.android.tools.metalava.model.JAVA_LANG_ANNOTATION |
| import com.android.tools.metalava.model.JAVA_LANG_ENUM |
| import com.android.tools.metalava.model.MethodItem |
| import com.android.tools.metalava.model.PackageItem |
| import com.android.tools.metalava.model.PropertyItem |
| import com.android.tools.metalava.model.TypeItem |
| import com.android.tools.metalava.model.psi.CodePrinter |
| import com.android.tools.metalava.model.visitors.ApiVisitor |
| import com.android.utils.XmlUtils |
| import java.io.PrintWriter |
| import java.util.function.Predicate |
| |
| /** |
| * Writes out an XML format in the JDiff schema: See $ANDROID/external/jdiff/src/api.xsd (though |
| * limited to the same subset as generated by Doclava; and using the same conventions for the |
| * unspecified parts of the schema, such as what value to put in the deprecated string. It also uses |
| * the same XML formatting.) |
| * |
| * Known differences: Doclava seems to skip enum fields. We don't do that. Doclava seems to skip |
| * type parameters; we do the same. |
| */ |
| class JDiffXmlWriter( |
| private val writer: PrintWriter, |
| filterEmit: Predicate<Item>, |
| filterReference: Predicate<Item>, |
| private val preFiltered: Boolean, |
| private val apiName: String? = null, |
| showUnannotated: Boolean, |
| config: Config, |
| ) : |
| ApiVisitor( |
| visitConstructorsAsMethods = false, |
| nestInnerClasses = false, |
| inlineInheritedFields = true, |
| methodComparator = MethodItem.comparator, |
| fieldComparator = FieldItem.comparator, |
| filterEmit = filterEmit, |
| filterReference = filterReference, |
| showUnannotated = showUnannotated, |
| config = config, |
| ) { |
| override fun visitCodebase(codebase: Codebase) { |
| writer.print("<api") |
| |
| if (apiName != null) { |
| // See JDiff's XMLToAPI#nameAPI |
| writer.print(" name=\"") |
| writer.print(apiName) |
| writer.print("\"") |
| } |
| |
| // Specify metalava schema used for metalava:enumConstant |
| writer.print(" xmlns:metalava=\"http://www.android.com/metalava/\"") |
| |
| writer.println(">") |
| } |
| |
| override fun afterVisitCodebase(codebase: Codebase) { |
| writer.println("</api>") |
| } |
| |
| override fun visitPackage(pkg: PackageItem) { |
| // Note: we apparently don't write package annotations anywhere |
| writer.println("<package name=\"${pkg.qualifiedName()}\"\n>") |
| } |
| |
| override fun afterVisitPackage(pkg: PackageItem) { |
| writer.println("</package>") |
| } |
| |
| override fun visitClass(cls: ClassItem) { |
| writer.print('<') |
| // XML format does not seem to special case annotations or enums |
| if (cls.isInterface()) { |
| writer.print("interface") |
| } else { |
| writer.print("class") |
| } |
| writer.print(" name=\"") |
| writer.print(cls.fullName()) |
| // Note - to match doclava we don't write out the type parameter list |
| // (cls.typeParameterList()) in JDiff files! |
| writer.print("\"") |
| |
| writeSuperClassAttribute(cls) |
| |
| val modifiers = cls.modifiers |
| writer.print("\n abstract=\"") |
| writer.print(modifiers.isAbstract()) |
| writer.print("\"\n static=\"") |
| writer.print(modifiers.isStatic()) |
| writer.print("\"\n final=\"") |
| writer.print(modifiers.isFinal()) |
| writer.print("\"\n deprecated=\"") |
| writer.print(deprecation(cls)) |
| writer.print("\"\n visibility=\"") |
| writer.print(modifiers.getVisibilityModifiers()) |
| writer.println("\"\n>") |
| |
| writeInterfaceList(cls) |
| } |
| |
| fun deprecation(item: Item): String { |
| return if (item.deprecated) { |
| "deprecated" |
| } else { |
| "not deprecated" |
| } |
| } |
| |
| override fun afterVisitClass(cls: ClassItem) { |
| writer.print("</") |
| if (cls.isInterface()) { |
| writer.print("interface") |
| } else { |
| writer.print("class") |
| } |
| writer.println(">") |
| } |
| |
| override fun visitConstructor(constructor: ConstructorItem) { |
| val modifiers = constructor.modifiers |
| writer.print("<constructor name=\"") |
| writer.print(constructor.containingClass().fullName()) |
| writer.print("\"\n type=\"") |
| writer.print(constructor.containingClass().qualifiedName()) |
| writer.print("\"\n static=\"") |
| writer.print(modifiers.isStatic()) |
| writer.print("\"\n final=\"") |
| writer.print(modifiers.isFinal()) |
| writer.print("\"\n deprecated=\"") |
| writer.print(deprecation(constructor)) |
| writer.print("\"\n visibility=\"") |
| writer.print(modifiers.getVisibilityModifiers()) |
| writer.println("\"\n>") |
| |
| // Note - to match doclava we don't write out the type parameter list |
| // (constructor.typeParameterList()) in JDiff files! |
| |
| writeParameterList(constructor) |
| writeThrowsList(constructor) |
| writer.println("</constructor>") |
| } |
| |
| override fun visitField(field: FieldItem) { |
| val modifiers = field.modifiers |
| val initialValue = field.initialValue(true) |
| val value = |
| if (initialValue != null) { |
| XmlUtils.toXmlAttributeValue(CodePrinter.constantToSource(initialValue)) |
| } else null |
| |
| writer.print("<field name=\"") |
| writer.print(field.name()) |
| writer.print("\"\n type=\"") |
| writer.print(XmlUtils.toXmlAttributeValue(formatType(field.type()))) |
| writer.print("\"\n transient=\"") |
| writer.print(modifiers.isTransient()) |
| writer.print("\"\n volatile=\"") |
| writer.print(modifiers.isVolatile()) |
| if (value != null) { |
| writer.print("\"\n value=\"") |
| writer.print(value) |
| } |
| |
| writer.print("\"\n static=\"") |
| writer.print(modifiers.isStatic()) |
| writer.print("\"\n final=\"") |
| writer.print(modifiers.isFinal()) |
| writer.print("\"\n deprecated=\"") |
| writer.print(deprecation(field)) |
| writer.print("\"\n visibility=\"") |
| writer.print(modifiers.getVisibilityModifiers()) |
| writer.print("\"") |
| if (field.isEnumConstant()) { |
| // Metalava extension. JDiff doesn't support it. |
| writer.print("\n metalava:enumConstant=\"true\"") |
| } |
| writer.println("\n>\n</field>") |
| } |
| |
| override fun visitProperty(property: PropertyItem) { |
| // Not supported by JDiff |
| } |
| |
| override fun visitMethod(method: MethodItem) { |
| val modifiers = method.modifiers |
| |
| // Note - to match doclava we don't write out the type parameter list |
| // (method.typeParameterList()) in JDiff files! |
| |
| writer.print("<method name=\"") |
| writer.print(method.name()) |
| method.returnType().let { |
| writer.print("\"\n return=\"") |
| writer.print(XmlUtils.toXmlAttributeValue(formatType(it))) |
| } |
| writer.print("\"\n abstract=\"") |
| writer.print(modifiers.isAbstract()) |
| writer.print("\"\n native=\"") |
| writer.print(modifiers.isNative()) |
| writer.print("\"\n synchronized=\"") |
| writer.print(modifiers.isSynchronized()) |
| writer.print("\"\n static=\"") |
| writer.print(modifiers.isStatic()) |
| writer.print("\"\n final=\"") |
| writer.print(modifiers.isFinal()) |
| writer.print("\"\n deprecated=\"") |
| writer.print(deprecation(method)) |
| writer.print("\"\n visibility=\"") |
| writer.print(modifiers.getVisibilityModifiers()) |
| writer.println("\"\n>") |
| |
| writeParameterList(method) |
| writeThrowsList(method) |
| writer.println("</method>") |
| } |
| |
| private fun writeSuperClassAttribute(cls: ClassItem) { |
| val superClass = |
| if (preFiltered) cls.superClassType() else cls.filteredSuperClassType(filterReference) |
| |
| val superClassString = |
| when { |
| cls.isAnnotationType() -> JAVA_LANG_ANNOTATION |
| superClass != null -> { |
| // doclava seems to include java.lang.Object for classes but not interfaces |
| if (!cls.isClass() && superClass.isJavaLangObject()) { |
| return |
| } |
| XmlUtils.toXmlAttributeValue( |
| formatType(superClass.toTypeString(context = superClass.asClass())) |
| ) |
| } |
| cls.isEnum() -> JAVA_LANG_ENUM |
| else -> return |
| } |
| writer.print("\n extends=\"") |
| writer.print(superClassString) |
| writer.print("\"") |
| } |
| |
| private fun writeInterfaceList(cls: ClassItem) { |
| val interfaces = |
| if (preFiltered) cls.interfaceTypes().asSequence() |
| else cls.filteredInterfaceTypes(filterReference).asSequence() |
| |
| if (interfaces.any()) { |
| interfaces.sortedWith(TypeItem.comparator).forEach { item -> |
| writer.print("<implements name=\"") |
| val type = item.toTypeString(context = cls) |
| writer.print(XmlUtils.toXmlAttributeValue(formatType(type))) |
| writer.println("\">\n</implements>") |
| } |
| } |
| } |
| |
| private fun writeParameterList(method: MethodItem) { |
| method.parameters().asSequence().forEach { parameter -> |
| // NOTE: We report parameter name as "null" rather than the real name to match |
| // doclava's behavior |
| writer.print("<parameter name=\"null\" type=\"") |
| writer.print(XmlUtils.toXmlAttributeValue(formatType(parameter.type()))) |
| writer.println("\">") |
| writer.println("</parameter>") |
| } |
| } |
| |
| private fun formatType(type: TypeItem): String = formatType(type.toTypeString()) |
| |
| private fun formatType(typeString: String): String { |
| // In JDiff we always want to include spaces after commas; the API signature tests depend |
| // on this. |
| return typeString.replace(",", ", ").replace(", ", ", ") |
| } |
| |
| private fun writeThrowsList(method: MethodItem) { |
| val throws = |
| when { |
| preFiltered -> method.throwsTypes().asSequence() |
| else -> method.filteredThrowsTypes(filterReference).asSequence() |
| } |
| if (throws.any()) { |
| throws.sortedWith(ClassItem.fullNameComparator).forEach { type -> |
| writer.print("<exception name=\"") |
| writer.print(type.fullName()) |
| writer.print("\" type=\"") |
| writer.print(type.qualifiedName()) |
| writer.println("\">") |
| writer.println("</exception>") |
| } |
| } |
| } |
| } |