1 /* <lambda>null2 * Copyright (C) 2020 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.model.text 18 19 import com.android.tools.metalava.model.AnnotationItem 20 import com.android.tools.metalava.model.AnnotationManager 21 import com.android.tools.metalava.model.ClassItem 22 import com.android.tools.metalava.model.ClassResolver 23 import com.android.tools.metalava.model.ClassTypeItem 24 import com.android.tools.metalava.model.DefaultAnnotationItem 25 import com.android.tools.metalava.model.DefaultCodebase 26 import com.android.tools.metalava.model.DefaultModifierList 27 import com.android.tools.metalava.model.Item 28 import com.android.tools.metalava.model.PackageItem 29 import com.android.tools.metalava.model.PackageList 30 import com.android.tools.metalava.reporter.FileLocation 31 import java.io.File 32 import java.util.ArrayList 33 import java.util.HashMap 34 35 // Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's 36 // data structures. 37 // (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.) 38 internal class TextCodebase( 39 location: File, 40 annotationManager: AnnotationManager, 41 private val classResolver: ClassResolver?, 42 ) : DefaultCodebase(location, "Codebase", true, annotationManager) { 43 private val packagesByName = HashMap<String, TextPackageItem>(300) 44 private val allClassesByName = HashMap<String, TextClassItem>(30000) 45 46 private val externalClassesByName = HashMap<String, ClassItem>() 47 48 override fun trustedApi(): Boolean = true 49 50 override fun getPackages(): PackageList { 51 val list = ArrayList<PackageItem>(packagesByName.values) 52 list.sortWith(PackageItem.comparator) 53 return PackageList(this, list) 54 } 55 56 override fun size(): Int { 57 return packagesByName.size 58 } 59 60 /** Find a class in this codebase, i.e. not classes loaded from the [classResolver]. */ 61 fun findClassInCodebase(className: String) = allClassesByName[className] 62 63 override fun findClass(className: String) = 64 allClassesByName[className] ?: externalClassesByName[className] 65 66 override fun resolveClass(className: String) = getOrCreateClass(className) 67 68 override fun supportsDocumentation(): Boolean = false 69 70 fun addPackage(pInfo: TextPackageItem) { 71 // track the set of organized packages in the API 72 packagesByName[pInfo.name()] = pInfo 73 74 // accumulate a direct map of all the classes in the API 75 for (cl in pInfo.allClasses()) { 76 allClassesByName[cl.qualifiedName()] = cl as TextClassItem 77 } 78 } 79 80 fun registerClass(classItem: TextClassItem) { 81 val qualifiedName = classItem.qualifiedName 82 val existing = allClassesByName.put(qualifiedName, classItem) 83 if (existing != null) { 84 error( 85 "Attempted to register $qualifiedName twice; once from ${existing.fileLocation.path} and this one from ${classItem.fileLocation.path}" 86 ) 87 } 88 89 addClass(classItem) 90 91 // A real class exists so a stub will not be created. 92 requiredStubKindForClass.remove(qualifiedName) 93 } 94 95 /** 96 * The [StubKind] required for each class which could not be found, defaults to [StubKind.CLASS] 97 * if not specified. 98 * 99 * Specific types, require a specific type of class, e.g. a type used in an `extends` clause of 100 * a concrete class requires a concrete class, whereas a type used in an `implements` clause of 101 * a concrete class, or an `extends` list of an interface requires an interface. 102 * 103 * Similarly, an annotation must be an annotation type and extends 104 * `java.lang.annotation.Annotation` and a `throws` type that is not a type parameter must be a 105 * concrete class that extends `java.lang.Throwable.` 106 * 107 * This contains information about the type use so that if a stub class is needed a class of the 108 * appropriate structure can be fabricated to avoid spurious issues being reported. 109 */ 110 private val requiredStubKindForClass = mutableMapOf<String, StubKind>() 111 112 /** 113 * Register that the class type requires a specific stub kind. 114 * 115 * If a concrete class already exists then this does nothing. Otherwise, this registers the 116 * [StubKind] for the [ClassTypeItem.qualifiedName], making sure that it does not conflict with 117 * any previous requirements. 118 */ 119 fun requireStubKindFor(classTypeItem: ClassTypeItem, stubKind: StubKind) { 120 val qualifiedName = classTypeItem.qualifiedName 121 122 // If a real class already exists then a stub will not need to be created. 123 if (allClassesByName[qualifiedName] != null) return 124 125 val existing = requiredStubKindForClass.put(qualifiedName, stubKind) 126 if (existing != null && existing != stubKind) { 127 error( 128 "Mismatching required stub kinds for $qualifiedName, found $existing and $stubKind" 129 ) 130 } 131 } 132 133 /** 134 * Gets an existing, or creates a new [ClassItem]. 135 * 136 * Tries to find [name] in [allClassesByName]. If not found, then if a [classResolver] is 137 * provided it will invoke that and return the [ClassItem] it returns if any. Otherwise, it will 138 * create an empty stub class of the [StubKind] specified in [requiredStubKindForClass] or 139 * [StubKind.CLASS] if no specific [StubKind] was required. 140 * 141 * Initializes outer classes and packages for the created class as needed. 142 * 143 * @param name the name of the class. 144 * @param isOuterClass if `true` then this is searching for an outer class of a class in this 145 * codebase, in which case this must only search classes in this codebase, otherwise it can 146 * search for external classes too. 147 */ 148 fun getOrCreateClass( 149 name: String, 150 isOuterClass: Boolean = false, 151 ): ClassItem { 152 // Check this codebase first, if found then return it. 153 allClassesByName[name]?.let { found -> 154 return found 155 } 156 157 // Only check for external classes if this is not searching for an outer class and there is 158 // a class resolver that will populate the external classes. 159 if (!isOuterClass && classResolver != null) { 160 // Check to see whether the class has already been retrieved from the resolver. If it 161 // has then return it. 162 externalClassesByName[name]?.let { found -> 163 return found 164 } 165 166 // Else try and resolve the class. 167 val classItem = classResolver.resolveClass(name) 168 if (classItem != null) { 169 // Save the class item, so it can be retrieved the next time this is loaded. This is 170 // needed because otherwise TextTypeItem.asClass would not work properly. 171 externalClassesByName[name] = classItem 172 return classItem 173 } 174 } 175 176 // Build a stub class of the required kind. 177 val requiredStubKind = requiredStubKindForClass.remove(name) ?: StubKind.CLASS 178 val stubClass = 179 StubClassBuilder.build(this, name) { 180 // Apply stub kind specific mutations to the stub class being built. 181 requiredStubKind.mutator(this) 182 } 183 184 registerClass(stubClass) 185 stubClass.emit = false 186 187 val fullName = stubClass.fullName() 188 if (fullName.contains('.')) { 189 // We created a new inner class stub. We need to fully initialize it with outer classes, 190 // themselves possibly stubs 191 val outerName = name.substring(0, name.lastIndexOf('.')) 192 // Pass classResolver = null, so it only looks in this codebase for the outer class. 193 val outerClass = getOrCreateClass(outerName, isOuterClass = true) 194 195 // It makes no sense for a Foo to come from one codebase and Foo.Bar to come from 196 // another. 197 if (outerClass.codebase != stubClass.codebase) { 198 throw IllegalStateException( 199 "Outer class $outerClass is from ${outerClass.codebase} but" + 200 " inner class $stubClass is from ${stubClass.codebase}" 201 ) 202 } 203 204 stubClass.containingClass = outerClass 205 outerClass.addInnerClass(stubClass) 206 } else { 207 // Add to package 208 val endIndex = name.lastIndexOf('.') 209 val pkgPath = if (endIndex != -1) name.substring(0, endIndex) else "" 210 val pkg = 211 findPackage(pkgPath) 212 ?: run { 213 val newPkg = 214 TextPackageItem( 215 this, 216 pkgPath, 217 DefaultModifierList(this, DefaultModifierList.PUBLIC), 218 FileLocation.UNKNOWN 219 ) 220 addPackage(newPkg) 221 newPkg.emit = false 222 newPkg 223 } 224 stubClass.setContainingPackage(pkg) 225 pkg.addClass(stubClass) 226 } 227 return stubClass 228 } 229 230 override fun findPackage(pkgName: String): TextPackageItem? { 231 return packagesByName[pkgName] 232 } 233 234 override fun createAnnotation( 235 source: String, 236 context: Item?, 237 ): AnnotationItem { 238 return DefaultAnnotationItem.create(this, source) 239 } 240 241 override fun toString(): String { 242 return description 243 } 244 245 override fun unsupported(desc: String?): Nothing { 246 error(desc ?: "Not supported for a signature-file based codebase") 247 } 248 } 249