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