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