1 /* <lambda>null2 * Copyright (C) 2017 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.stub 18 19 import com.android.tools.metalava.actualItem 20 import com.android.tools.metalava.model.BaseItemVisitor 21 import com.android.tools.metalava.model.ClassItem 22 import com.android.tools.metalava.model.ConstructorItem 23 import com.android.tools.metalava.model.ExceptionTypeItem 24 import com.android.tools.metalava.model.FieldItem 25 import com.android.tools.metalava.model.Item 26 import com.android.tools.metalava.model.MethodItem 27 import com.android.tools.metalava.model.ModifierListWriter 28 import com.android.tools.metalava.model.PrimitiveTypeItem 29 import com.android.tools.metalava.model.TypeParameterList 30 import com.android.tools.metalava.model.VariableTypeItem 31 import java.io.PrintWriter 32 import java.util.function.Predicate 33 34 internal class JavaStubWriter( 35 private val writer: PrintWriter, 36 private val modifierListWriter: ModifierListWriter, 37 private val filterEmit: Predicate<Item>, 38 private val filterReference: Predicate<Item>, 39 private val preFiltered: Boolean = true, 40 private val config: StubWriterConfig, 41 ) : BaseItemVisitor() { 42 43 override fun visitClass(cls: ClassItem) { 44 if (cls.isTopLevelClass()) { 45 val qualifiedName = cls.containingPackage().qualifiedName() 46 if (qualifiedName.isNotBlank()) { 47 writer.println("package $qualifiedName;") 48 writer.println() 49 } 50 if (config.includeDocumentationInStubs) { 51 // All the classes referenced in the stubs are fully qualified, so no imports are 52 // needed. However, in some cases for javadoc, replacement with fully qualified name 53 // fails, and thus we need to include imports for the stubs to compile. 54 cls.getSourceFile()?.getImports(filterReference)?.let { 55 for (item in it) { 56 if (item.isMember) { 57 writer.println("import static ${item.pattern};") 58 } else { 59 writer.println("import ${item.pattern};") 60 } 61 } 62 writer.println() 63 } 64 } 65 } 66 67 appendDocumentation(cls, writer, config) 68 69 // "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked" 70 writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})") 71 72 appendModifiers(cls) 73 74 when { 75 cls.isAnnotationType() -> writer.print("@interface") 76 cls.isInterface() -> writer.print("interface") 77 cls.isEnum() -> writer.print("enum") 78 else -> writer.print("class") 79 } 80 81 writer.print(" ") 82 writer.print(cls.simpleName()) 83 84 generateTypeParameterList(typeList = cls.typeParameterList, addSpace = false) 85 generateSuperClassDeclaration(cls) 86 generateInterfaceList(cls) 87 writer.print(" {\n") 88 89 if (cls.isEnum()) { 90 var first = true 91 // Enums should preserve the original source order, not alphabetical etc. sort 92 for (field in cls.filteredFields(filterReference, true).sortedBy { it.sortingRank }) { 93 if (field.isEnumConstant()) { 94 if (first) { 95 first = false 96 } else { 97 writer.write(",\n") 98 } 99 appendDocumentation(field, writer, config) 100 101 // Append the modifier list even though the enum constant does not actually have 102 // modifiers as that will write the annotations which it does have and ignore 103 // the modifiers. 104 appendModifiers(field) 105 106 writer.write(field.name()) 107 } 108 } 109 writer.println(";") 110 } 111 112 generateMissingConstructors(cls) 113 } 114 115 override fun afterVisitClass(cls: ClassItem) { 116 writer.print("}\n\n") 117 } 118 119 private fun appendModifiers(item: Item) { 120 modifierListWriter.write(item.actualItem) 121 } 122 123 private fun generateSuperClassDeclaration(cls: ClassItem) { 124 if (cls.isEnum() || cls.isAnnotationType() || cls.isInterface()) { 125 // No extends statement for enums and annotations; it's implied by the "enum" and 126 // "@interface" keywords. Normal interfaces do support an extends statement but it is 127 // generated in [generateInterfaceList]. 128 return 129 } 130 131 val superClass = 132 if (preFiltered) cls.superClassType() else cls.filteredSuperClassType(filterReference) 133 134 if (superClass != null && !superClass.isJavaLangObject()) { 135 writer.print(" extends ") 136 writer.print(superClass.toTypeString()) 137 } 138 } 139 140 private fun generateInterfaceList(cls: ClassItem) { 141 if (cls.isAnnotationType()) { 142 // No extends statement for annotations; it's implied by the "@interface" keyword 143 return 144 } 145 146 val interfaces = 147 if (preFiltered) cls.interfaceTypes() else cls.filteredInterfaceTypes(filterReference) 148 149 if (interfaces.any()) { 150 val label = if (cls.isInterface()) " extends" else " implements" 151 writer.print(label) 152 interfaces.forEachIndexed { index, type -> 153 if (index > 0) { 154 writer.print(",") 155 } 156 writer.print(" ") 157 writer.print(type.toTypeString()) 158 } 159 } 160 } 161 162 private fun generateTypeParameterList(typeList: TypeParameterList, addSpace: Boolean) { 163 val typeListString = typeList.toString() 164 if (typeListString.isNotEmpty()) { 165 writer.print(typeListString) 166 167 if (addSpace) { 168 writer.print(' ') 169 } 170 } 171 } 172 173 override fun visitConstructor(constructor: ConstructorItem) { 174 writeConstructor(constructor, constructor.superConstructor) 175 } 176 177 private fun writeConstructor(constructor: MethodItem, superConstructor: MethodItem?) { 178 writer.println() 179 appendDocumentation(constructor, writer, config) 180 appendModifiers(constructor) 181 generateTypeParameterList(typeList = constructor.typeParameterList, addSpace = true) 182 writer.print(constructor.containingClass().simpleName()) 183 184 generateParameterList(constructor) 185 generateThrowsList(constructor) 186 187 writer.print(" { ") 188 189 writeConstructorBody(constructor, superConstructor) 190 writer.println(" }") 191 } 192 193 private fun writeConstructorBody(constructor: MethodItem, superConstructor: MethodItem?) { 194 // Find any constructor in parent that we can compile against 195 superConstructor?.let { it -> 196 val parameters = it.parameters() 197 if (parameters.isNotEmpty()) { 198 val includeCasts = 199 it.containingClass().constructors().filter { filterReference.test(it) }.size > 1 200 writer.print("super(") 201 parameters.forEachIndexed { index, parameter -> 202 if (index > 0) { 203 writer.write(", ") 204 } 205 val type = parameter.type() 206 if (type !is PrimitiveTypeItem) { 207 if (includeCasts) { 208 // Casting to the erased type could lead to unchecked warnings (which 209 // are suppressed) but avoids having to deal with parameterized types 210 // and ensures that casting to a vararg parameter uses an array type. 211 val typeString = type.toErasedTypeString() 212 writer.write("(") 213 if (type is VariableTypeItem) { 214 // The super constructor's parameter is a type variable: so see if 215 // it should be mapped back to a type specified by this class. e.g. 216 // Given: 217 // class Bar<T extends Number> { 218 // public Bar(int i) {} 219 // public Bar(T t) {} 220 // } 221 // class Foo extends Bar<Integer> { 222 // public Foo(Integer i) { super(i); } 223 // } 224 // 225 // The stub for Foo should use: 226 // super((Integer) i); 227 // Not: 228 // super((Number) i); 229 // 230 // However, if the super class is referenced as a raw type then 231 // there will be no mapping in which case fall back to the erased 232 // type which will use the type variable's lower bound. e.g. 233 // Given: 234 // class Foo extends Bar { 235 // public Foo(Integer i) { super(i); } 236 // } 237 // 238 // The stub for Foo should use: 239 // super((Number) i); 240 val map = 241 constructor 242 .containingClass() 243 .mapTypeVariables(it.containingClass()) 244 val cast = map[type.asTypeParameter]?.toTypeString() ?: typeString 245 writer.write(cast) 246 } else { 247 writer.write(typeString) 248 } 249 writer.write(")") 250 } 251 writer.write("null") 252 } else { 253 // Add cast for things like shorts and bytes 254 val typeString = type.toTypeString() 255 if ( 256 typeString != "boolean" && typeString != "int" && typeString != "long" 257 ) { 258 writer.write("(") 259 writer.write(typeString) 260 writer.write(")") 261 } 262 writer.write(type.defaultValueString()) 263 } 264 } 265 writer.print("); ") 266 } 267 } 268 269 writeThrowStub() 270 } 271 272 private fun generateMissingConstructors(cls: ClassItem) { 273 val clsStubConstructor = cls.stubConstructor 274 val constructors = cls.filteredConstructors(filterEmit) 275 // If the default stub constructor is not publicly visible then it won't be output during 276 // the normal visiting 277 // so visit it specially to ensure that it is output. 278 if (clsStubConstructor != null && !constructors.contains(clsStubConstructor)) { 279 visitConstructor(clsStubConstructor) 280 return 281 } 282 } 283 284 override fun visitMethod(method: MethodItem) { 285 writeMethod(method.containingClass(), method) 286 } 287 288 private fun writeMethod(containingClass: ClassItem, method: MethodItem) { 289 writer.println() 290 appendDocumentation(method, writer, config) 291 292 appendModifiers(method) 293 generateTypeParameterList(typeList = method.typeParameterList, addSpace = true) 294 295 val returnType = method.returnType() 296 writer.print(returnType.toTypeString(annotations = false, filter = filterReference)) 297 298 writer.print(' ') 299 writer.print(method.name()) 300 generateParameterList(method) 301 generateThrowsList(method) 302 303 if (containingClass.isAnnotationType()) { 304 val default = method.defaultValue() 305 if (default.isNotEmpty()) { 306 writer.print(" default ") 307 writer.print(default) 308 } 309 } 310 311 if (ModifierListWriter.requiresMethodBodyInStubs(method.actualItem)) { 312 writer.print(" { ") 313 writeThrowStub() 314 writer.println(" }") 315 } else { 316 writer.println(";") 317 } 318 } 319 320 override fun visitField(field: FieldItem) { 321 // Handled earlier in visitClass 322 if (field.isEnumConstant()) { 323 return 324 } 325 326 writer.println() 327 328 appendDocumentation(field, writer, config) 329 appendModifiers(field) 330 writer.print(field.type().toTypeString(annotations = false, filter = filterReference)) 331 writer.print(' ') 332 writer.print(field.name()) 333 val needsInitialization = 334 field.modifiers.isFinal() && 335 field.initialValue(true) == null && 336 field.containingClass().isClass() 337 field.writeValueWithSemicolon( 338 writer, 339 allowDefaultValue = !needsInitialization, 340 requireInitialValue = !needsInitialization 341 ) 342 writer.print("\n") 343 344 if (needsInitialization) { 345 if (field.modifiers.isStatic()) { 346 writer.print("static ") 347 } 348 writer.print("{ ${field.name()} = ${field.type().defaultValueString()}; }\n") 349 } 350 } 351 352 private fun writeThrowStub() { 353 writer.write("throw new RuntimeException(\"Stub!\");") 354 } 355 356 private fun generateParameterList(method: MethodItem) { 357 writer.print("(") 358 method.parameters().asSequence().forEachIndexed { i, parameter -> 359 if (i > 0) { 360 writer.print(", ") 361 } 362 appendModifiers(parameter) 363 writer.print( 364 parameter.type().toTypeString(annotations = false, filter = filterReference) 365 ) 366 writer.print(' ') 367 val name = parameter.publicName() ?: parameter.name() 368 writer.print(name) 369 } 370 writer.print(")") 371 } 372 373 private fun generateThrowsList(method: MethodItem) { 374 // Note that throws types are already sorted internally to help comparison matching 375 val throws = 376 if (preFiltered) { 377 method.throwsTypes().asSequence() 378 } else { 379 method.filteredThrowsTypes(filterReference).asSequence() 380 } 381 if (throws.any()) { 382 writer.print(" throws ") 383 throws.sortedWith(ExceptionTypeItem.fullNameComparator).forEachIndexed { i, type -> 384 if (i > 0) { 385 writer.print(", ") 386 } 387 writer.print(type.toTypeString()) 388 } 389 } 390 } 391 } 392