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 && !options.compatOutput) { 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 if (cls.isEnum() && compatibility.defaultEnumMethods) { 120 writer.println( 121 """ 122 <method name="valueOf" 123 return="${cls.qualifiedName()}" 124 abstract="false" 125 native="false" 126 synchronized="false" 127 static="true" 128 final="false" 129 deprecated="not deprecated" 130 visibility="public" 131 > 132 <parameter name="null" type="java.lang.String"> 133 </parameter> 134 </method> 135 <method name="values" 136 return="${cls.qualifiedName()}[]" 137 abstract="false" 138 native="false" 139 synchronized="false" 140 static="true" 141 final="true" 142 deprecated="not deprecated" 143 visibility="public" 144 > 145 </method>""".trimIndent() 146 ) 147 } 148 } 149 150 fun deprecation(item: Item): String { 151 return if (item.deprecated) { 152 "deprecated" 153 } else { 154 "not deprecated" 155 } 156 } 157 158 override fun afterVisitClass(cls: ClassItem) { 159 writer.print("</") 160 if (cls.isInterface()) { 161 writer.print("interface") 162 } else { 163 writer.print("class") 164 } 165 writer.println(">") 166 } 167 168 override fun visitConstructor(constructor: ConstructorItem) { 169 val modifiers = constructor.modifiers 170 writer.print("<constructor name=\"") 171 writer.print(constructor.containingClass().fullName()) 172 writer.print("\"\n type=\"") 173 writer.print(constructor.containingClass().qualifiedName()) 174 writer.print("\"\n static=\"") 175 writer.print(modifiers.isStatic()) 176 writer.print("\"\n final=\"") 177 writer.print(modifiers.isFinal()) 178 writer.print("\"\n deprecated=\"") 179 writer.print(deprecation(constructor)) 180 writer.print("\"\n visibility=\"") 181 writer.print(modifiers.getVisibilityModifiers()) 182 writer.println("\"\n>") 183 184 // Note - to match doclava we don't write out the type parameter list 185 // (constructor.typeParameterList()) in JDiff files! 186 187 writeParameterList(constructor) 188 writeThrowsList(constructor) 189 writer.println("</constructor>") 190 } 191 192 override fun visitField(field: FieldItem) { 193 if (field.isEnumConstant() && compatibility.xmlSkipEnumFields) { 194 return 195 } 196 197 val modifiers = field.modifiers 198 val initialValue = field.initialValue(true) 199 val value = if (initialValue != null) { 200 if (initialValue is Char && compatibility.xmlCharAsInt) { 201 initialValue.toInt().toString() 202 } else { 203 escapeAttributeValue(CodePrinter.constantToSource(initialValue)) 204 } 205 } else null 206 207 writer.print("<field name=\"") 208 writer.print(field.name()) 209 writer.print("\"\n type=\"") 210 writer.print(escapeAttributeValue(formatType(field.type()))) 211 writer.print("\"\n transient=\"") 212 writer.print(modifiers.isTransient()) 213 writer.print("\"\n volatile=\"") 214 writer.print(modifiers.isVolatile()) 215 if (value != null) { 216 writer.print("\"\n value=\"") 217 writer.print(value) 218 } else if (compatibility.xmlShowArrayFieldsAsNull && (field.type().isArray())) { 219 writer.print("\"\n value=\"null") 220 } 221 222 writer.print("\"\n static=\"") 223 writer.print(modifiers.isStatic()) 224 writer.print("\"\n final=\"") 225 writer.print(modifiers.isFinal()) 226 writer.print("\"\n deprecated=\"") 227 writer.print(deprecation(field)) 228 writer.print("\"\n visibility=\"") 229 writer.print(modifiers.getVisibilityModifiers()) 230 writer.print("\"") 231 if (field.isEnumConstant()) { 232 // Metalava extension. JDiff doesn't support it. 233 writer.print("\n metalava:enumConstant=\"true\"") 234 } 235 writer.println("\n>\n</field>") 236 } 237 238 override fun visitProperty(property: PropertyItem) { 239 // Not supported by JDiff 240 } 241 242 override fun visitMethod(method: MethodItem) { 243 val modifiers = method.modifiers 244 245 if (method.containingClass().isAnnotationType() && compatibility.xmlSkipAnnotationMethods) { 246 return 247 } 248 249 // Note - to match doclava we don't write out the type parameter list 250 // (method.typeParameterList()) in JDiff files! 251 252 writer.print("<method name=\"") 253 writer.print(method.name()) 254 method.returnType()?.let { 255 writer.print("\"\n return=\"") 256 writer.print(escapeAttributeValue(formatType(it))) 257 } 258 writer.print("\"\n abstract=\"") 259 writer.print(modifiers.isAbstract()) 260 writer.print("\"\n native=\"") 261 writer.print(modifiers.isNative()) 262 writer.print("\"\n synchronized=\"") 263 writer.print(modifiers.isSynchronized()) 264 writer.print("\"\n static=\"") 265 writer.print(modifiers.isStatic()) 266 writer.print("\"\n final=\"") 267 writer.print(modifiers.isFinal()) 268 writer.print("\"\n deprecated=\"") 269 writer.print(deprecation(method)) 270 writer.print("\"\n visibility=\"") 271 writer.print(modifiers.getVisibilityModifiers()) 272 writer.println("\"\n>") 273 274 writeParameterList(method) 275 writeThrowsList(method) 276 writer.println("</method>") 277 } 278 279 private fun writeSuperClassAttribute(cls: ClassItem) { 280 if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) { 281 // Written in the interface section instead 282 return 283 } 284 285 val superClass = if (preFiltered) 286 cls.superClassType() 287 else cls.filteredSuperClassType(filterReference) 288 289 val superClassString = 290 when { 291 cls.isAnnotationType() -> if (compatibility.xmlAnnotationAsObject) { 292 JAVA_LANG_OBJECT 293 } else { 294 JAVA_LANG_ANNOTATION 295 } 296 superClass != null -> { 297 // doclava seems to include java.lang.Object for classes but not interfaces 298 if (!cls.isClass() && superClass.isJavaLangObject()) { 299 return 300 } 301 escapeAttributeValue( 302 formatType( 303 superClass.toTypeString( 304 erased = compatibility.omitTypeParametersInInterfaces, 305 context = superClass.asClass() 306 ) 307 ) 308 ) 309 } 310 cls.isEnum() -> JAVA_LANG_ENUM 311 else -> return 312 } 313 writer.print("\n extends=\"") 314 writer.print(superClassString) 315 writer.print("\"") 316 } 317 318 private fun writeInterfaceList(cls: ClassItem) { 319 var interfaces = if (preFiltered) 320 cls.interfaceTypes().asSequence() 321 else cls.filteredInterfaceTypes(filterReference).asSequence() 322 323 if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) { 324 val superClassType = cls.superClassType() 325 if (superClassType?.isJavaLangObject() == false) { 326 interfaces += superClassType 327 } 328 } 329 330 if (interfaces.any()) { 331 interfaces.sortedWith(TypeItem.comparator).forEach { item -> 332 writer.print("<implements name=\"") 333 val type = item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces, context = cls) 334 writer.print(escapeAttributeValue(formatType(type))) 335 writer.println("\">\n</implements>") 336 } 337 } 338 } 339 340 private fun writeParameterList(method: MethodItem) { 341 method.parameters().asSequence().forEach { parameter -> 342 // NOTE: We report parameter name as "null" rather than the real name to match 343 // doclava's behavior 344 writer.print("<parameter name=\"null\" type=\"") 345 writer.print(escapeAttributeValue(formatType(parameter.type()))) 346 writer.println("\">") 347 writer.println("</parameter>") 348 } 349 } 350 351 private fun formatType(type: TypeItem): String = formatType(type.toTypeString()) 352 353 private fun formatType(typeString: String): String { 354 // In JDiff we always want to include spaces after commas; the API signature tests depend 355 // on this. 356 return typeString.replace(",", ", ").replace(", ", ", ") 357 } 358 359 private fun writeThrowsList(method: MethodItem) { 360 val throws = when { 361 preFiltered -> method.throwsTypes().asSequence() 362 compatibility.filterThrowsClasses -> method.filteredThrowsTypes(filterReference).asSequence() 363 else -> method.throwsTypes().asSequence() 364 } 365 if (throws.any()) { 366 throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type -> 367 writer.print("<exception name=\"") 368 if (options.compatOutput) { 369 writer.print(type.simpleName()) 370 } else { 371 writer.print(type.fullName()) 372 } 373 writer.print("\" type=\"") 374 writer.print(type.qualifiedName()) 375 writer.println("\">") 376 writer.println("</exception>") 377 } 378 } 379 } 380 381 private fun escapeAttributeValue(s: String): String { 382 val escaped = XmlUtils.toXmlAttributeValue(s) 383 return if (compatibility.xmlEscapeGreaterThan && escaped.contains(">")) { 384 escaped.replace(">", ">") 385 } else { 386 escaped 387 } 388 } 389 }