• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.ApiVariantSelectors
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.ClassResolver
22 import com.android.tools.metalava.model.ClassTypeItem
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.Item
25 import com.android.tools.metalava.model.ItemLanguage
26 import com.android.tools.metalava.model.bestGuessAtFullName
27 import com.android.tools.metalava.model.item.DefaultClassItem
28 import com.android.tools.metalava.model.item.DefaultCodebase
29 import com.android.tools.metalava.model.item.DefaultCodebaseAssembler
30 import com.android.tools.metalava.model.item.DefaultCodebaseFactory
31 import com.android.tools.metalava.model.item.DefaultItemFactory
32 import com.android.tools.metalava.model.item.DefaultPackageItem
33 import com.android.tools.metalava.model.item.PackageDocs
34 import java.io.File
35 
36 internal class TextCodebaseAssembler(
37     codebaseFactory: DefaultCodebaseFactory,
38     private val classResolver: ClassResolver?,
39 ) : DefaultCodebaseAssembler() {
40 
41     internal val codebase = codebaseFactory(this)
42 
43     /** Creates [Item] instances for this. */
44     override val itemFactory =
45         DefaultItemFactory(
46             codebase = codebase,
47             // Signature files do not contain information about whether an item was originally
48             // created from Java or Kotlin.
49             defaultItemLanguage = ItemLanguage.UNKNOWN,
50             // Signature files have already been separated by API surface variants, so they can use
51             // the same immutable ApiVariantSelectors.
52             defaultVariantSelectorsFactory = ApiVariantSelectors.IMMUTABLE_FACTORY,
53         )
54 
55     fun initialize() {
56         // Make sure that it has a root package.
57         codebase.packageTracker.createInitialPackages(PackageDocs.EMPTY)
58     }
59 
60     override fun createClassFromUnderlyingModel(qualifiedName: String) =
61         getOrCreateClass(qualifiedName)
62 
63     /**
64      * The [StubKind] required for each class which could not be found, defaults to [StubKind.CLASS]
65      * if not specified.
66      *
67      * Specific types, require a specific type of class, e.g. a type used in an `extends` clause of
68      * a concrete class requires a concrete class, whereas a type used in an `implements` clause of
69      * a concrete class, or an `extends` list of an interface requires an interface.
70      *
71      * Similarly, an annotation must be an annotation type and extends
72      * `java.lang.annotation.Annotation` and a `throws` type that is not a type parameter must be a
73      * concrete class that extends `java.lang.Throwable.`
74      *
75      * This contains information about the type use so that if a stub class is needed a class of the
76      * appropriate structure can be fabricated to avoid spurious issues being reported.
77      */
78     private val requiredStubKindForClass = mutableMapOf<String, StubKind>()
79 
80     override fun newClassRegistered(classItem: DefaultClassItem) {
81         // A real class exists so a stub will not be created so the hint as to the kind of class
82         // that the stubs should be is no longer needed.
83         requiredStubKindForClass.remove(classItem.qualifiedName())
84     }
85 
86     /**
87      * Register that the class type requires a specific stub kind.
88      *
89      * If a concrete class already exists then this does nothing. Otherwise, this registers the
90      * [StubKind] for the [ClassTypeItem.qualifiedName], making sure that it does not conflict with
91      * any previous requirements.
92      */
93     fun requireStubKindFor(classTypeItem: ClassTypeItem, stubKind: StubKind) {
94         val qualifiedName = classTypeItem.qualifiedName
95 
96         // If a real class already exists then a stub will not need to be created.
97         if (codebase.findClass(qualifiedName) != null) return
98 
99         val existing = requiredStubKindForClass.put(qualifiedName, stubKind)
100         if (existing != null && existing != stubKind) {
101             error(
102                 "Mismatching required stub kinds for $qualifiedName, found $existing and $stubKind"
103             )
104         }
105     }
106 
107     /**
108      * Gets an existing, or creates a new [ClassItem].
109      *
110      * Tries to find [qualifiedName] in [codebase]. If not found, then if a [classResolver] is
111      * provided it will invoke that and return the [ClassItem] it returns if any. Otherwise, it will
112      * create an empty stub class of the [StubKind] specified in [requiredStubKindForClass] or
113      * [StubKind.CLASS] if no specific [StubKind] was required.
114      *
115      * Initializes outer classes and packages for the created class as needed.
116      *
117      * @param qualifiedName the fully qualified name of the class.
118      * @param isOuterClassOfClassInThisCodebase if `true` then this is searching for an outer class
119      *   of a class in this codebase, in which case this must only search classes in this codebase,
120      *   otherwise it can search for external classes too.
121      */
122     internal fun getOrCreateClass(
123         qualifiedName: String,
124         isOuterClassOfClassInThisCodebase: Boolean = false,
125     ): ClassItem {
126         // Check this codebase first, if found then return it.
127         codebase.findClass(qualifiedName)?.let {
128             return it
129         }
130 
131         // Only check for external classes if this is not searching for an outer class of a class in
132         // this codebase and there is a class resolver that will populate the external classes.
133         if (!isOuterClassOfClassInThisCodebase && classResolver != null) {
134             // Try and resolve the class, returning it if it was found.
135             classResolver.resolveClass(qualifiedName)?.let {
136                 return it
137             }
138         }
139 
140         val fullName = bestGuessAtFullName(qualifiedName)
141 
142         val outerClass =
143             if (fullName.contains('.')) {
144                 // We created a new nested class stub. We need to fully initialize it with outer
145                 // classes, themselves possibly stubs
146                 val outerName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.'))
147                 val outerClass = getOrCreateClass(outerName, isOuterClassOfClassInThisCodebase)
148 
149                 // As outerClass and stubClass are from the same codebase the outerClass must be a
150                 // DefaultClassItem so cast it to one so that the code below can use
151                 // DefaultClassItem methods.
152                 outerClass as DefaultClassItem
153             } else {
154                 null
155             }
156 
157         // Find/create package
158         val pkg =
159             if (outerClass == null) {
160                 val endIndex = qualifiedName.lastIndexOf('.')
161                 val pkgPath = if (endIndex != -1) qualifiedName.substring(0, endIndex) else ""
162                 codebase.findOrCreatePackage(pkgPath)
163             } else {
164                 outerClass.containingPackage() as DefaultPackageItem
165             }
166 
167         // Build a stub class of the required kind.
168         val requiredStubKind = requiredStubKindForClass.remove(qualifiedName) ?: StubKind.CLASS
169 
170         return StubClassBuilder.build(
171             assembler = this,
172             qualifiedName = qualifiedName,
173             containingClass = outerClass,
174             containingPackage = pkg,
175         ) {
176             // Apply stub kind specific mutations to the stub class being built.
177             requiredStubKind.mutator(this)
178         }
179     }
180 
181     companion object {
182         /** Create a [TextCodebaseAssembler]. */
183         fun createAssembler(
184             location: File,
185             description: String,
186             codebaseConfig: Codebase.Config,
187             classResolver: ClassResolver?,
188         ): TextCodebaseAssembler {
189             val assembler =
190                 TextCodebaseAssembler(
191                     codebaseFactory = { assembler ->
192                         DefaultCodebase(
193                             location = location,
194                             description = description,
195                             preFiltered = true,
196                             config = codebaseConfig,
197                             trustedApi = true,
198                             supportsDocumentation = false,
199                             assembler = assembler,
200                         )
201                     },
202                     classResolver = classResolver,
203                 )
204             assembler.initialize()
205 
206             return assembler
207         }
208     }
209 }
210