• 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]?.let {
108             if (!it.contains(iface)) it.add(iface)
109         }
110     }
111 
implementsInterfacenull112     fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean {
113         return mClassToInterface[classInfo]?.contains(iface) ?: false
114     }
115 
addPackagenull116     fun addPackage(pInfo: TextPackageItem) {
117         // track the set of organized packages in the API
118         mPackages[pInfo.name()] = pInfo
119 
120         // accumulate a direct map of all the classes in the API
121         for (cl in pInfo.allClasses()) {
122             mAllClasses[cl.qualifiedName()] = cl as TextClassItem
123         }
124     }
125 
resolveSuperclassesnull126     private fun resolveSuperclasses(allClasses: List<TextClassItem>) {
127         for (cl in allClasses) {
128             // java.lang.Object has no superclass
129             if (cl.isJavaLangObject()) {
130                 continue
131             }
132             var scName: String? = mClassToSuper[cl]
133             if (scName == null) {
134                 scName = when {
135                     cl.isEnum() -> JAVA_LANG_ENUM
136                     cl.isAnnotationType() -> JAVA_LANG_ANNOTATION
137                     else -> {
138                         val existing = cl.superClassType()?.toTypeString()
139                         val s = existing ?: JAVA_LANG_OBJECT
140                         s // unnecessary variable, works around current compiler believing the expression to be nullable
141                     }
142                 }
143             }
144 
145             val superclass = getOrCreateClass(scName)
146             cl.setSuperClass(superclass, obtainTypeFromString(scName))
147         }
148     }
149 
resolveThrowsClassesnull150     private fun resolveThrowsClasses(all: List<TextClassItem>) {
151         for (cl in all) {
152             for (methodItem in cl.constructors()) {
153                 resolveThrowsClasses(methodItem)
154             }
155             for (methodItem in cl.methods()) {
156                 resolveThrowsClasses(methodItem)
157             }
158         }
159     }
160 
resolveThrowsClassesnull161     private fun resolveThrowsClasses(methodItem: MethodItem) {
162         val methodInfo = methodItem as TextMethodItem
163         val names = methodInfo.throwsTypeNames()
164         if (names.isNotEmpty()) {
165             val result = ArrayList<TextClassItem>()
166             for (exception in names) {
167                 var exceptionClass: TextClassItem? = mAllClasses[exception]
168                 if (exceptionClass == null) {
169                     // Exception not provided by this codebase. Inject a stub.
170                     exceptionClass = getOrCreateClass(exception)
171                     // Set super class to throwable?
172                     if (exception != JAVA_LANG_THROWABLE) {
173                         exceptionClass.setSuperClass(
174                             getOrCreateClass(JAVA_LANG_THROWABLE),
175                             TextTypeItem(this, JAVA_LANG_THROWABLE)
176                         )
177                     }
178                 }
179                 result.add(exceptionClass)
180             }
181             methodInfo.setThrowsList(result)
182         }
183     }
184 
resolveInnerClassesnull185     private fun resolveInnerClasses(packages: List<TextPackageItem>) {
186         for (pkg in packages) {
187             // make copy: we'll be removing non-top level classes during iteration
188             val classes = ArrayList(pkg.classList())
189             for (cls in classes) {
190                 val cl = cls as TextClassItem
191                 val name = cl.name
192                 var index = name.lastIndexOf('.')
193                 if (index != -1) {
194                     cl.name = name.substring(index + 1)
195                     val qualifiedName = cl.qualifiedName
196                     index = qualifiedName.lastIndexOf('.')
197                     assert(index != -1) { qualifiedName }
198                     val outerClassName = qualifiedName.substring(0, index)
199                     val outerClass = getOrCreateClass(outerClassName)
200                     cl.containingClass = outerClass
201                     outerClass.addInnerClass(cl)
202                 }
203             }
204         }
205 
206         for (pkg in packages) {
207             pkg.pruneClassList()
208         }
209     }
210 
registerClassnull211     fun registerClass(cls: TextClassItem) {
212         mAllClasses[cls.qualifiedName] = cls
213     }
214 
getOrCreateClassnull215     fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem {
216         val erased = TextTypeItem.eraseTypeArguments(name)
217         val cls = mAllClasses[erased]
218         if (cls != null) {
219             return cls
220         }
221         val newClass = if (isInterface) {
222             TextClassItem.createInterfaceStub(this, name)
223         } else {
224             TextClassItem.createClassStub(this, name)
225         }
226         mAllClasses[erased] = newClass
227         newClass.emit = false
228 
229         val fullName = newClass.fullName()
230         if (fullName.contains('.')) {
231             // We created a new inner class stub. We need to fully initialize it with outer classes, themselves
232             // possibly stubs
233             val outerName = erased.substring(0, erased.lastIndexOf('.'))
234             val outerClass = getOrCreateClass(outerName, false)
235             newClass.containingClass = outerClass
236             outerClass.addInnerClass(newClass)
237         } else {
238             // Add to package
239             val endIndex = erased.lastIndexOf('.')
240             val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else ""
241             val pkg = findPackage(pkgPath) ?: run {
242                 val newPkg = TextPackageItem(
243                     this,
244                     pkgPath,
245                     TextModifiers(this, DefaultModifierList.PUBLIC),
246                     SourcePositionInfo.UNKNOWN
247                 )
248                 addPackage(newPkg)
249                 newPkg.emit = false
250                 newPkg
251             }
252             newClass.setContainingPackage(pkg)
253             pkg.addClass(newClass)
254         }
255 
256         return newClass
257     }
258 
postProcessnull259     fun postProcess() {
260         val classes = mAllClasses.values.toList()
261         val packages = mPackages.values.toList()
262         resolveSuperclasses(classes)
263         resolveInterfaces(classes)
264         resolveThrowsClasses(classes)
265         resolveInnerClasses(packages)
266     }
267 
findPackagenull268     override fun findPackage(pkgName: String): TextPackageItem? {
269         return mPackages[pkgName]
270     }
271 
acceptnull272     override fun accept(visitor: ItemVisitor) {
273         getPackages().accept(visitor)
274     }
275 
acceptTypesnull276     override fun acceptTypes(visitor: TypeVisitor) {
277         getPackages().acceptTypes(visitor)
278     }
279 
compareWithnull280     override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) {
281         CodebaseComparator().compare(visitor, this, other, filter)
282     }
283 
createAnnotationnull284     override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem {
285         return TextBackedAnnotationItem(this, source, mapName)
286     }
287 
toStringnull288     override fun toString(): String {
289         return description
290     }
291 
unsupportednull292     override fun unsupported(desc: String?): Nothing {
293         error(desc ?: "Not supported for a signature-file based codebase")
294     }
295 
obtainTypeFromStringnull296     fun obtainTypeFromString(
297         type: String,
298         cl: TextClassItem,
299         methodTypeParameterList: TypeParameterList
300     ): TextTypeItem {
301         if (TextTypeItem.isLikelyTypeParameter(type)) {
302             val length = type.length
303             var nameEnd = length
304             for (i in 0 until length) {
305                 val c = type[i]
306                 if (c == '<' || c == '[' || c == '!' || c == '?') {
307                     nameEnd = i
308                     break
309                 }
310             }
311             val name = if (nameEnd == length) {
312                 type
313             } else {
314                 type.substring(0, nameEnd)
315             }
316 
317             val isMethodTypeVar = methodTypeParameterList.typeParameterNames().contains(name)
318             val isClassTypeVar = cl.typeParameterList().typeParameterNames().contains(name)
319 
320             if (isMethodTypeVar || isClassTypeVar) {
321                 // Confirm that it's a type variable
322                 // If so, create type variable WITHOUT placing it into the
323                 // cache, since we can't cache these; they can have different
324                 // inherited bounds etc
325                 return TextTypeItem(this, type)
326             }
327         }
328 
329         return obtainTypeFromString(type)
330     }
331 
332     companion object {
computeDeltanull333         fun computeDelta(
334             baseFile: File,
335             baseApi: Codebase,
336             signatureApi: Codebase
337         ): TextCodebase {
338             // Compute just the delta
339             val delta =
340                 TextCodebase(baseFile)
341             delta.description = "Delta between $baseApi and $signatureApi"
342 
343             CodebaseComparator().compare(
344                 object : ComparisonVisitor() {
345                     override fun added(new: PackageItem) {
346                         delta.addPackage(new as TextPackageItem)
347                     }
348 
349                     override fun added(new: ClassItem) {
350                         val pkg = getOrAddPackage(new.containingPackage().qualifiedName())
351                         pkg.addClass(new as TextClassItem)
352                     }
353 
354                     override fun added(new: ConstructorItem) {
355                         val cls = getOrAddClass(new.containingClass())
356                         cls.addConstructor(new as TextConstructorItem)
357                     }
358 
359                     override fun added(new: MethodItem) {
360                         val cls = getOrAddClass(new.containingClass())
361                         cls.addMethod(new as TextMethodItem)
362                     }
363 
364                     override fun added(new: FieldItem) {
365                         val cls = getOrAddClass(new.containingClass())
366                         cls.addField(new as TextFieldItem)
367                     }
368 
369                     override fun added(new: PropertyItem) {
370                         val cls = getOrAddClass(new.containingClass())
371                         cls.addProperty(new as TextPropertyItem)
372                     }
373 
374                     private fun getOrAddClass(fullClass: ClassItem): TextClassItem {
375                         val cls = delta.findClass(fullClass.qualifiedName())
376                         if (cls != null) {
377                             return cls
378                         }
379                         val textClass = fullClass as TextClassItem
380                         val newClass = TextClassItem(
381                             delta,
382                             SourcePositionInfo.UNKNOWN,
383                             textClass.modifiers,
384                             textClass.isInterface(),
385                             textClass.isEnum(),
386                             textClass.isAnnotationType(),
387                             textClass.qualifiedName,
388                             textClass.qualifiedName,
389                             textClass.name,
390                             textClass.annotations
391                         )
392                         val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName())
393                         pkg.addClass(newClass)
394                         newClass.setContainingPackage(pkg)
395                         delta.registerClass(newClass)
396                         return newClass
397                     }
398 
399                     private fun getOrAddPackage(pkgName: String): TextPackageItem {
400                         val pkg = delta.findPackage(pkgName)
401                         if (pkg != null) {
402                             return pkg
403                         }
404                         val newPkg = TextPackageItem(
405                             delta,
406                             pkgName,
407                             TextModifiers(delta, DefaultModifierList.PUBLIC),
408                             SourcePositionInfo.UNKNOWN
409                         )
410                         delta.addPackage(newPkg)
411                         return newPkg
412                     }
413                 },
414                 baseApi, signatureApi, ApiType.ALL.getReferenceFilter()
415             )
416 
417             delta.postProcess()
418             return delta
419         }
420     }
421 
422     // Copied from Converter:
423 
obtainTypeFromStringnull424     fun obtainTypeFromString(type: String): TextTypeItem {
425         return mTypesFromString.obtain(type) as TextTypeItem
426     }
427 
428     private val mTypesFromString = object : Cache(this) {
makenull429         override fun make(o: Any): Any {
430             val name = o as String
431 
432             // Reverse effect of TypeItem.shortenTypes(...)
433             if (implicitJavaLangType(name)) {
434                 return TextTypeItem(codebase, "java.lang.$name")
435             }
436 
437             return TextTypeItem(codebase, name)
438         }
439 
implicitJavaLangTypenull440         private fun implicitJavaLangType(s: String): Boolean {
441             if (s.length <= 1) {
442                 return false // Usually a type variable
443             }
444             if (s[1] == '[') {
445                 return false // Type variable plus array
446             }
447 
448             val dotIndex = s.indexOf('.')
449             val array = s.indexOf('[')
450             val generics = s.indexOf('<')
451             if (array == -1 && generics == -1) {
452                 return dotIndex == -1 && !TextTypeItem.isPrimitive(s)
453             }
454             val typeEnd =
455                 if (array != -1) {
456                     if (generics != -1) {
457                         min(array, generics)
458                     } else {
459                         array
460                     }
461                 } else {
462                     generics
463                 }
464 
465             // Allow dotted type in generic parameter, e.g. "Iterable<java.io.File>" -> return true
466             return (dotIndex == -1 || dotIndex > typeEnd) && !TextTypeItem.isPrimitive(s.substring(0, typeEnd).trim())
467         }
468     }
469 
470     private abstract class Cache(val codebase: TextCodebase) {
471 
472         protected var mCache = HashMap<Any, Any>()
473 
obtainnull474         internal fun obtain(o: Any?): Any? {
475             if (o == null) {
476                 return null
477             }
478             var r: Any? = mCache[o]
479             if (r == null) {
480                 r = make(o)
481                 mCache[o] = r
482             }
483             return r
484         }
485 
makenull486         protected abstract fun make(o: Any): Any
487     }
488 }
489