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