• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.ApiType
20 import com.android.tools.metalava.CodebaseComparator
21 import com.android.tools.metalava.ComparisonVisitor
22 import com.android.tools.metalava.FileFormat
23 import com.android.tools.metalava.JAVA_LANG_ANNOTATION
24 import com.android.tools.metalava.JAVA_LANG_ENUM
25 import com.android.tools.metalava.JAVA_LANG_OBJECT
26 import com.android.tools.metalava.JAVA_LANG_THROWABLE
27 import com.android.tools.metalava.model.AnnotationItem
28 import com.android.tools.metalava.model.ClassItem
29 import com.android.tools.metalava.model.Codebase
30 import com.android.tools.metalava.model.ConstructorItem
31 import com.android.tools.metalava.model.DefaultCodebase
32 import com.android.tools.metalava.model.DefaultModifierList
33 import com.android.tools.metalava.model.FieldItem
34 import com.android.tools.metalava.model.Item
35 import com.android.tools.metalava.model.MethodItem
36 import com.android.tools.metalava.model.PackageItem
37 import com.android.tools.metalava.model.PackageList
38 import com.android.tools.metalava.model.PropertyItem
39 import com.android.tools.metalava.model.TypeParameterList
40 import com.android.tools.metalava.model.visitors.ItemVisitor
41 import com.android.tools.metalava.model.visitors.TypeVisitor
42 import java.io.File
43 import java.util.ArrayList
44 import java.util.HashMap
45 import java.util.function.Predicate
46 import kotlin.math.min
47 
48 // Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures.
49 // (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.)
50 class TextCodebase(location: File) : DefaultCodebase(location) {
51     /**
52      * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable,
53      * ! suffix means unknown, and absence of a suffix means not nullable.
54      */
55     var kotlinStyleNulls = false
56 
57     private val mPackages = HashMap<String, TextPackageItem>(300)
58     private val mAllClasses = HashMap<String, TextClassItem>(30000)
59     private val mClassToSuper = HashMap<TextClassItem, String>(30000)
60     private val mClassToInterface = HashMap<TextClassItem, ArrayList<String>>(10000)
61 
62     override var description = "Codebase"
63     override var preFiltered: Boolean = true
64 
trustedApinull65     override fun trustedApi(): Boolean = true
66 
67     /**
68      * Signature file format version, if found. Type "GradleVersion" is misleading; it's just a convenient
69      * version class.
70      */
71     var format: FileFormat = FileFormat.V1 // not specifying format: assumed to be doclava, 1.0
72 
73     override fun getPackages(): PackageList {
74         val list = ArrayList<PackageItem>(mPackages.values)
75         list.sortWith(PackageItem.comparator)
76         return PackageList(this, list)
77     }
78 
sizenull79     override fun size(): Int {
80         return mPackages.size
81     }
82 
findClassnull83     override fun findClass(className: String): TextClassItem? {
84         return mAllClasses[className]
85     }
86 
resolveInterfacesnull87     private fun resolveInterfaces(all: List<TextClassItem>) {
88         for (cl in all) {
89             val interfaces = mClassToInterface[cl] ?: continue
90             for (interfaceName in interfaces) {
91                 getOrCreateClass(interfaceName, isInterface = true)
92                 cl.addInterface(obtainTypeFromString(interfaceName))
93             }
94         }
95     }
96 
supportsDocumentationnull97     override fun supportsDocumentation(): Boolean = false
98 
99     fun mapClassToSuper(classInfo: TextClassItem, superclass: String?) {
100         superclass?.let { mClassToSuper.put(classInfo, superclass) }
101     }
102 
mapClassToInterfacenull103     fun mapClassToInterface(classInfo: TextClassItem, iface: String) {
104         if (!mClassToInterface.containsKey(classInfo)) {
105             mClassToInterface[classInfo] = ArrayList()
106         }
107         mClassToInterface[classInfo]?.add(iface)
108     }
109 
implementsInterfacenull110     fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean {
111         return mClassToInterface[classInfo]?.contains(iface) ?: false
112     }
113 
addPackagenull114     fun addPackage(pInfo: TextPackageItem) {
115         // track the set of organized packages in the API
116         mPackages[pInfo.name()] = pInfo
117 
118         // accumulate a direct map of all the classes in the API
119         for (cl in pInfo.allClasses()) {
120             mAllClasses[cl.qualifiedName()] = cl as TextClassItem
121         }
122     }
123 
resolveSuperclassesnull124     private fun resolveSuperclasses(allClasses: List<TextClassItem>) {
125         for (cl in allClasses) {
126             // java.lang.Object has no superclass
127             if (cl.isJavaLangObject()) {
128                 continue
129             }
130             var scName: String? = mClassToSuper[cl]
131             if (scName == null) {
132                 scName = when {
133                     cl.isEnum() -> JAVA_LANG_ENUM
134                     cl.isAnnotationType() -> JAVA_LANG_ANNOTATION
135                     else -> {
136                         val existing = cl.superClassType()?.toTypeString()
137                         val s = existing ?: JAVA_LANG_OBJECT
138                         s // unnecessary variable, works around current compiler believing the expression to be nullable
139                     }
140                 }
141             }
142 
143             val superclass = getOrCreateClass(scName)
144             cl.setSuperClass(superclass, obtainTypeFromString(scName))
145         }
146     }
147 
resolveThrowsClassesnull148     private fun resolveThrowsClasses(all: List<TextClassItem>) {
149         for (cl in all) {
150             for (methodItem in cl.constructors()) {
151                 resolveThrowsClasses(methodItem)
152             }
153             for (methodItem in cl.methods()) {
154                 resolveThrowsClasses(methodItem)
155             }
156         }
157     }
158 
resolveThrowsClassesnull159     private fun resolveThrowsClasses(methodItem: MethodItem) {
160         val methodInfo = methodItem as TextMethodItem
161         val names = methodInfo.throwsTypeNames()
162         if (names.isNotEmpty()) {
163             val result = ArrayList<TextClassItem>()
164             for (exception in names) {
165                 var exceptionClass: TextClassItem? = mAllClasses[exception]
166                 if (exceptionClass == null) {
167                     // Exception not provided by this codebase. Inject a stub.
168                     exceptionClass = getOrCreateClass(exception)
169                     // Set super class to throwable?
170                     if (exception != JAVA_LANG_THROWABLE) {
171                         exceptionClass.setSuperClass(
172                             getOrCreateClass(JAVA_LANG_THROWABLE),
173                             TextTypeItem(this, JAVA_LANG_THROWABLE)
174                         )
175                     }
176                 }
177                 result.add(exceptionClass)
178             }
179             methodInfo.setThrowsList(result)
180         }
181     }
182 
resolveInnerClassesnull183     private fun resolveInnerClasses(packages: List<TextPackageItem>) {
184         for (pkg in packages) {
185             // make copy: we'll be removing non-top level classes during iteration
186             val classes = ArrayList(pkg.classList())
187             for (cls in classes) {
188                 val cl = cls as TextClassItem
189                 val name = cl.name
190                 var index = name.lastIndexOf('.')
191                 if (index != -1) {
192                     cl.name = name.substring(index + 1)
193                     val qualifiedName = cl.qualifiedName
194                     index = qualifiedName.lastIndexOf('.')
195                     assert(index != -1) { qualifiedName }
196                     val outerClassName = qualifiedName.substring(0, index)
197                     val outerClass = getOrCreateClass(outerClassName)
198                     cl.containingClass = outerClass
199                     outerClass.addInnerClass(cl)
200                 }
201             }
202         }
203 
204         for (pkg in packages) {
205             pkg.pruneClassList()
206         }
207     }
208 
registerClassnull209     fun registerClass(cls: TextClassItem) {
210         mAllClasses[cls.qualifiedName] = cls
211     }
212 
getOrCreateClassnull213     fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem {
214         val erased = TextTypeItem.eraseTypeArguments(name)
215         val cls = mAllClasses[erased]
216         if (cls != null) {
217             return cls
218         }
219         val newClass = if (isInterface) {
220             TextClassItem.createInterfaceStub(this, name)
221         } else {
222             TextClassItem.createClassStub(this, name)
223         }
224         mAllClasses[erased] = newClass
225         newClass.emit = false
226 
227         val fullName = newClass.fullName()
228         if (fullName.contains('.')) {
229             // We created a new inner class stub. We need to fully initialize it with outer classes, themselves
230             // possibly stubs
231             val outerName = erased.substring(0, erased.lastIndexOf('.'))
232             val outerClass = getOrCreateClass(outerName, false)
233             newClass.containingClass = outerClass
234             outerClass.addInnerClass(newClass)
235         } else {
236             // Add to package
237             val endIndex = erased.lastIndexOf('.')
238             val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else ""
239             val pkg = findPackage(pkgPath) ?: run {
240                 val newPkg = TextPackageItem(
241                     this,
242                     pkgPath,
243                     TextModifiers(this, DefaultModifierList.PUBLIC),
244                     SourcePositionInfo.UNKNOWN
245                 )
246                 addPackage(newPkg)
247                 newPkg.emit = false
248                 newPkg
249             }
250             newClass.setContainingPackage(pkg)
251             pkg.addClass(newClass)
252         }
253 
254         return newClass
255     }
256 
postProcessnull257     fun postProcess() {
258         val classes = mAllClasses.values.toList()
259         val packages = mPackages.values.toList()
260         resolveSuperclasses(classes)
261         resolveInterfaces(classes)
262         resolveThrowsClasses(classes)
263         resolveInnerClasses(packages)
264     }
265 
findPackagenull266     override fun findPackage(pkgName: String): TextPackageItem? {
267         return mPackages[pkgName]
268     }
269 
acceptnull270     override fun accept(visitor: ItemVisitor) {
271         getPackages().accept(visitor)
272     }
273 
acceptTypesnull274     override fun acceptTypes(visitor: TypeVisitor) {
275         getPackages().acceptTypes(visitor)
276     }
277 
compareWithnull278     override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) {
279         CodebaseComparator().compare(visitor, this, other, filter)
280     }
281 
createAnnotationnull282     override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem {
283         return TextBackedAnnotationItem(this, source, mapName)
284     }
285 
toStringnull286     override fun toString(): String {
287         return description
288     }
289 
unsupportednull290     override fun unsupported(desc: String?): Nothing {
291         error(desc ?: "Not supported for a signature-file based codebase")
292     }
293 
obtainTypeFromStringnull294     fun obtainTypeFromString(
295         type: String,
296         cl: TextClassItem,
297         methodTypeParameterList: TypeParameterList
298     ): TextTypeItem {
299         if (TextTypeItem.isLikelyTypeParameter(type)) {
300             val length = type.length
301             var nameEnd = length
302             for (i in 0 until length) {
303                 val c = type[i]
304                 if (c == '<' || c == '[' || c == '!' || c == '?') {
305                     nameEnd = i
306                     break
307                 }
308             }
309             val name = if (nameEnd == length) {
310                 type
311             } else {
312                 type.substring(0, nameEnd)
313             }
314 
315             val isMethodTypeVar = methodTypeParameterList.typeParameterNames().contains(name)
316             val isClassTypeVar = cl.typeParameterList().typeParameterNames().contains(name)
317 
318             if (isMethodTypeVar || isClassTypeVar) {
319                 // Confirm that it's a type variable
320                 // If so, create type variable WITHOUT placing it into the
321                 // cache, since we can't cache these; they can have different
322                 // inherited bounds etc
323                 return TextTypeItem(this, type)
324             }
325         }
326 
327         return obtainTypeFromString(type)
328     }
329 
330     companion object {
computeDeltanull331         fun computeDelta(
332             baseFile: File,
333             baseApi: Codebase,
334             signatureApi: Codebase
335         ): TextCodebase {
336             // Compute just the delta
337             val delta =
338                 TextCodebase(baseFile)
339             delta.description = "Delta between $baseApi and $signatureApi"
340 
341             CodebaseComparator().compare(
342                 object : ComparisonVisitor() {
343                     override fun added(new: PackageItem) {
344                         delta.addPackage(new as TextPackageItem)
345                     }
346 
347                     override fun added(new: ClassItem) {
348                         val pkg = getOrAddPackage(new.containingPackage().qualifiedName())
349                         pkg.addClass(new as TextClassItem)
350                     }
351 
352                     override fun added(new: ConstructorItem) {
353                         val cls = getOrAddClass(new.containingClass())
354                         cls.addConstructor(new as TextConstructorItem)
355                     }
356 
357                     override fun added(new: MethodItem) {
358                         val cls = getOrAddClass(new.containingClass())
359                         cls.addMethod(new as TextMethodItem)
360                     }
361 
362                     override fun added(new: FieldItem) {
363                         val cls = getOrAddClass(new.containingClass())
364                         cls.addField(new as TextFieldItem)
365                     }
366 
367                     override fun added(new: PropertyItem) {
368                         val cls = getOrAddClass(new.containingClass())
369                         cls.addProperty(new as TextPropertyItem)
370                     }
371 
372                     private fun getOrAddClass(fullClass: ClassItem): TextClassItem {
373                         val cls = delta.findClass(fullClass.qualifiedName())
374                         if (cls != null) {
375                             return cls
376                         }
377                         val textClass = fullClass as TextClassItem
378                         val newClass = TextClassItem(
379                             delta,
380                             SourcePositionInfo.UNKNOWN,
381                             textClass.modifiers,
382                             textClass.isInterface(),
383                             textClass.isEnum(),
384                             textClass.isAnnotationType(),
385                             textClass.qualifiedName,
386                             textClass.qualifiedName,
387                             textClass.name,
388                             textClass.annotations
389                         )
390                         val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName())
391                         pkg.addClass(newClass)
392                         newClass.setContainingPackage(pkg)
393                         delta.registerClass(newClass)
394                         return newClass
395                     }
396 
397                     private fun getOrAddPackage(pkgName: String): TextPackageItem {
398                         val pkg = delta.findPackage(pkgName)
399                         if (pkg != null) {
400                             return pkg
401                         }
402                         val newPkg = TextPackageItem(
403                             delta,
404                             pkgName,
405                             TextModifiers(delta, DefaultModifierList.PUBLIC),
406                             SourcePositionInfo.UNKNOWN
407                         )
408                         delta.addPackage(newPkg)
409                         return newPkg
410                     }
411                 },
412                 baseApi, signatureApi, ApiType.ALL.getReferenceFilter()
413             )
414 
415             delta.postProcess()
416             return delta
417         }
418     }
419 
420     // Copied from Converter:
421 
obtainTypeFromStringnull422     fun obtainTypeFromString(type: String): TextTypeItem {
423         return mTypesFromString.obtain(type) as TextTypeItem
424     }
425 
426     private val mTypesFromString = object : Cache(this) {
makenull427         override fun make(o: Any): Any {
428             val name = o as String
429 
430             // Reverse effect of TypeItem.shortenTypes(...)
431             if (implicitJavaLangType(name)) {
432                 return TextTypeItem(codebase, "java.lang.$name")
433             }
434 
435             return TextTypeItem(codebase, name)
436         }
437 
implicitJavaLangTypenull438         private fun implicitJavaLangType(s: String): Boolean {
439             if (s.length <= 1) {
440                 return false // Usually a type variable
441             }
442             if (s[1] == '[') {
443                 return false // Type variable plus array
444             }
445 
446             val dotIndex = s.indexOf('.')
447             val array = s.indexOf('[')
448             val generics = s.indexOf('<')
449             if (array == -1 && generics == -1) {
450                 return dotIndex == -1 && !TextTypeItem.isPrimitive(s)
451             }
452             val typeEnd =
453                 if (array != -1) {
454                     if (generics != -1) {
455                         min(array, generics)
456                     } else {
457                         array
458                     }
459                 } else {
460                     generics
461                 }
462 
463             // Allow dotted type in generic parameter, e.g. "Iterable<java.io.File>" -> return true
464             return (dotIndex == -1 || dotIndex > typeEnd) && !TextTypeItem.isPrimitive(s.substring(0, typeEnd).trim())
465         }
466     }
467 
468     private abstract class Cache(val codebase: TextCodebase) {
469 
470         protected var mCache = HashMap<Any, Any>()
471 
obtainnull472         internal fun obtain(o: Any?): Any? {
473             if (o == null) {
474                 return null
475             }
476             var r: Any? = mCache[o]
477             if (r == null) {
478                 r = make(o)
479                 mCache[o] = r
480             }
481             return r
482         }
483 
makenull484         protected abstract fun make(o: Any): Any
485     }
486 }
487