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