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.compatibility
28 import com.android.tools.metalava.model.AnnotationItem
29 import com.android.tools.metalava.model.ClassItem
30 import com.android.tools.metalava.model.Codebase
31 import com.android.tools.metalava.model.ConstructorItem
32 import com.android.tools.metalava.model.DefaultCodebase
33 import com.android.tools.metalava.model.DefaultModifierList
34 import com.android.tools.metalava.model.FieldItem
35 import com.android.tools.metalava.model.Item
36 import com.android.tools.metalava.model.MethodItem
37 import com.android.tools.metalava.model.PackageItem
38 import com.android.tools.metalava.model.PackageList
39 import com.android.tools.metalava.model.PropertyItem
40 import com.android.tools.metalava.model.TypeParameterList
41 import com.android.tools.metalava.model.visitors.ItemVisitor
42 import com.android.tools.metalava.model.visitors.TypeVisitor
43 import java.io.File
44 import java.util.ArrayList
45 import java.util.HashMap
46 import java.util.function.Predicate
47 import kotlin.math.min
48 
49 // Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures.
50 // (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.)
51 class TextCodebase(location: File) : DefaultCodebase(location) {
52     /**
53      * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable,
54      * ! suffix means unknown, and absence of a suffix means not nullable.
55      */
56     var kotlinStyleNulls = false
57 
58     private val mPackages = HashMap<String, TextPackageItem>(300)
59     private val mAllClasses = HashMap<String, TextClassItem>(30000)
60     private val mClassToSuper = HashMap<TextClassItem, String>(30000)
61     private val mClassToInterface = HashMap<TextClassItem, ArrayList<String>>(10000)
62 
63     override var description = "Codebase"
64     override var preFiltered: Boolean = true
65 
trustedApinull66     override fun trustedApi(): Boolean = true
67 
68     /**
69      * Signature file format version, if found. Type "GradleVersion" is misleading; it's just a convenient
70      * version class.
71      */
72     var format: FileFormat = FileFormat.V1 // not specifying format: assumed to be doclava, 1.0
73 
74     override fun getPackages(): PackageList {
75         val list = ArrayList<PackageItem>(mPackages.values)
76         list.sortWith(PackageItem.comparator)
77         return PackageList(this, list)
78     }
79 
sizenull80     override fun size(): Int {
81         return mPackages.size
82     }
83 
findClassnull84     override fun findClass(className: String): TextClassItem? {
85         return mAllClasses[className]
86     }
87 
resolveInterfacesnull88     private fun resolveInterfaces(all: List<TextClassItem>) {
89         for (cl in all) {
90             val interfaces = mClassToInterface[cl] ?: continue
91             for (interfaceName in interfaces) {
92                 getOrCreateClass(interfaceName, isInterface = true)
93                 cl.addInterface(obtainTypeFromString(interfaceName))
94             }
95         }
96     }
97 
supportsDocumentationnull98     override fun supportsDocumentation(): Boolean = false
99 
100     fun mapClassToSuper(classInfo: TextClassItem, superclass: String?) {
101         superclass?.let { mClassToSuper.put(classInfo, superclass) }
102     }
103 
mapClassToInterfacenull104     fun mapClassToInterface(classInfo: TextClassItem, iface: String) {
105         if (!mClassToInterface.containsKey(classInfo)) {
106             mClassToInterface[classInfo] = ArrayList()
107         }
108         mClassToInterface[classInfo]?.add(iface)
109     }
110 
implementsInterfacenull111     fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean {
112         return mClassToInterface[classInfo]?.contains(iface) ?: false
113     }
114 
addPackagenull115     fun addPackage(pInfo: TextPackageItem) {
116         // track the set of organized packages in the API
117         mPackages[pInfo.name()] = pInfo
118 
119         // accumulate a direct map of all the classes in the API
120         for (cl in pInfo.allClasses()) {
121             mAllClasses[cl.qualifiedName()] = cl as TextClassItem
122         }
123     }
124 
resolveSuperclassesnull125     private fun resolveSuperclasses(allClasses: List<TextClassItem>) {
126         for (cl in allClasses) {
127             // java.lang.Object has no superclass
128             if (cl.isJavaLangObject()) {
129                 continue
130             }
131             var scName: String? = mClassToSuper[cl]
132             if (scName == null) {
133                 scName = when {
134                     cl.isEnum() -> JAVA_LANG_ENUM
135                     cl.isAnnotationType() -> JAVA_LANG_ANNOTATION
136                     else -> {
137                         val existing = cl.superClassType()?.toTypeString()
138                         val s = existing ?: JAVA_LANG_OBJECT
139                         s // unnecessary variable, works around current compiler believing the expression to be nullable
140                     }
141                 }
142             }
143 
144             val superclass = getOrCreateClass(scName)
145             cl.setSuperClass(superclass, obtainTypeFromString(scName))
146         }
147     }
148 
resolveThrowsClassesnull149     private fun resolveThrowsClasses(all: List<TextClassItem>) {
150         for (cl in all) {
151             for (methodItem in cl.constructors()) {
152                 resolveThrowsClasses(methodItem)
153             }
154             for (methodItem in cl.methods()) {
155                 resolveThrowsClasses(methodItem)
156             }
157         }
158     }
159 
resolveThrowsClassesnull160     private fun resolveThrowsClasses(methodItem: MethodItem) {
161         val methodInfo = methodItem as TextMethodItem
162         val names = methodInfo.throwsTypeNames()
163         if (names.isNotEmpty()) {
164             val result = ArrayList<TextClassItem>()
165             for (exception in names) {
166                 var exceptionClass: TextClassItem? = mAllClasses[exception]
167                 if (exceptionClass == null) {
168                     // Exception not provided by this codebase. Inject a stub.
169                     exceptionClass = getOrCreateClass(exception)
170                     // Set super class to throwable?
171                     if (exception != JAVA_LANG_THROWABLE) {
172                         exceptionClass.setSuperClass(
173                             getOrCreateClass(JAVA_LANG_THROWABLE),
174                             TextTypeItem(this, JAVA_LANG_THROWABLE)
175                         )
176                     }
177                 }
178                 result.add(exceptionClass)
179             }
180             methodInfo.setThrowsList(result)
181         }
182     }
183 
resolveInnerClassesnull184     private fun resolveInnerClasses(packages: List<TextPackageItem>) {
185         for (pkg in packages) {
186             // make copy: we'll be removing non-top level classes during iteration
187             val classes = ArrayList(pkg.classList())
188             for (cls in classes) {
189                 val cl = cls as TextClassItem
190                 val name = cl.name
191                 var index = name.lastIndexOf('.')
192                 if (index != -1) {
193                     cl.name = name.substring(index + 1)
194                     val qualifiedName = cl.qualifiedName
195                     index = qualifiedName.lastIndexOf('.')
196                     assert(index != -1) { qualifiedName }
197                     val outerClassName = qualifiedName.substring(0, index)
198                     val outerClass = getOrCreateClass(outerClassName)
199                     cl.containingClass = outerClass
200                     outerClass.addInnerClass(cl)
201                 }
202             }
203         }
204 
205         for (pkg in packages) {
206             pkg.pruneClassList()
207         }
208     }
209 
registerClassnull210     fun registerClass(cls: TextClassItem) {
211         mAllClasses[cls.qualifiedName] = cls
212     }
213 
getOrCreateClassnull214     fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem {
215         val erased = TextTypeItem.eraseTypeArguments(name)
216         val cls = mAllClasses[erased]
217         if (cls != null) {
218             return cls
219         }
220         val newClass = if (isInterface) {
221             TextClassItem.createInterfaceStub(this, name)
222         } else {
223             TextClassItem.createClassStub(this, name)
224         }
225         mAllClasses[erased] = newClass
226         newClass.emit = false
227 
228         val fullName = newClass.fullName()
229         if (fullName.contains('.')) {
230             // We created a new inner class stub. We need to fully initialize it with outer classes, themselves
231             // possibly stubs
232             val outerName = erased.substring(0, erased.lastIndexOf('.'))
233             val outerClass = getOrCreateClass(outerName, false)
234             newClass.containingClass = outerClass
235             outerClass.addInnerClass(newClass)
236         } else {
237             // Add to package
238             val endIndex = erased.lastIndexOf('.')
239             val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else ""
240             val pkg = findPackage(pkgPath) ?: run {
241                 val newPkg = TextPackageItem(
242                     this,
243                     pkgPath,
244                     TextModifiers(this, DefaultModifierList.PUBLIC),
245                     SourcePositionInfo.UNKNOWN
246                 )
247                 addPackage(newPkg)
248                 newPkg.emit = false
249                 newPkg
250             }
251             newClass.setContainingPackage(pkg)
252             pkg.addClass(newClass)
253         }
254 
255         return newClass
256     }
257 
postProcessnull258     fun postProcess() {
259         val classes = mAllClasses.values.toList()
260         val packages = mPackages.values.toList()
261         resolveSuperclasses(classes)
262         resolveInterfaces(classes)
263         resolveThrowsClasses(classes)
264         resolveInnerClasses(packages)
265     }
266 
findPackagenull267     override fun findPackage(pkgName: String): TextPackageItem? {
268         return mPackages[pkgName]
269     }
270 
acceptnull271     override fun accept(visitor: ItemVisitor) {
272         getPackages().accept(visitor)
273     }
274 
acceptTypesnull275     override fun acceptTypes(visitor: TypeVisitor) {
276         getPackages().acceptTypes(visitor)
277     }
278 
compareWithnull279     override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) {
280         CodebaseComparator().compare(visitor, this, other, filter)
281     }
282 
createAnnotationnull283     override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem {
284         return TextBackedAnnotationItem(this, source, mapName)
285     }
286 
toStringnull287     override fun toString(): String {
288         return description
289     }
290 
unsupportednull291     override fun unsupported(desc: String?): Nothing {
292         error(desc ?: "Not supported for a signature-file based codebase")
293     }
294 
obtainTypeFromStringnull295     fun obtainTypeFromString(
296         type: String,
297         cl: TextClassItem,
298         methodTypeParameterList: TypeParameterList
299     ): TextTypeItem {
300         if (TextTypeItem.isLikelyTypeParameter(type)) {
301             val length = type.length
302             var nameEnd = length
303             for (i in 0 until length) {
304                 val c = type[i]
305                 if (c == '<' || c == '[' || c == '!' || c == '?') {
306                     nameEnd = i
307                     break
308                 }
309             }
310             val name = if (nameEnd == length) {
311                 type
312             } else {
313                 type.substring(0, nameEnd)
314             }
315 
316             val isMethodTypeVar = methodTypeParameterList.typeParameterNames().contains(name)
317             val isClassTypeVar = cl.typeParameterList().typeParameterNames().contains(name)
318 
319             if (isMethodTypeVar || isClassTypeVar) {
320                 // Confirm that it's a type variable
321                 // If so, create type variable WITHOUT placing it into the
322                 // cache, since we can't cache these; they can have different
323                 // inherited bounds etc
324                 return TextTypeItem(this, type)
325             }
326         }
327 
328         return obtainTypeFromString(type)
329     }
330 
331     companion object {
computeDeltanull332         fun computeDelta(
333             baseFile: File,
334             baseApi: Codebase,
335             signatureApi: Codebase,
336             includeFieldsInApiDiff: Boolean = compatibility.includeFieldsInApiDiff
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(object : ComparisonVisitor() {
344                 override fun added(new: PackageItem) {
345                     delta.addPackage(new as TextPackageItem)
346                 }
347 
348                 override fun added(new: ClassItem) {
349                     val pkg = getOrAddPackage(new.containingPackage().qualifiedName())
350                     pkg.addClass(new as TextClassItem)
351                 }
352 
353                 override fun added(new: ConstructorItem) {
354                     val cls = getOrAddClass(new.containingClass())
355                     cls.addConstructor(new as TextConstructorItem)
356                 }
357 
358                 override fun added(new: MethodItem) {
359                     val cls = getOrAddClass(new.containingClass())
360                     cls.addMethod(new as TextMethodItem)
361                 }
362 
363                 override fun added(new: FieldItem) {
364                     if (!includeFieldsInApiDiff) {
365                         return
366                     }
367                     val cls = getOrAddClass(new.containingClass())
368                     cls.addField(new as TextFieldItem)
369                 }
370 
371                 override fun added(new: PropertyItem) {
372                     val cls = getOrAddClass(new.containingClass())
373                     cls.addProperty(new as TextPropertyItem)
374                 }
375 
376                 private fun getOrAddClass(fullClass: ClassItem): TextClassItem {
377                     val cls = delta.findClass(fullClass.qualifiedName())
378                     if (cls != null) {
379                         return cls
380                     }
381                     val textClass = fullClass as TextClassItem
382                     val newClass = TextClassItem(
383                         delta,
384                         SourcePositionInfo.UNKNOWN,
385                         textClass.modifiers,
386                         textClass.isInterface(),
387                         textClass.isEnum(),
388                         textClass.isAnnotationType(),
389                         textClass.qualifiedName,
390                         textClass.qualifiedName,
391                         textClass.name,
392                         textClass.annotations
393                     )
394                     val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName())
395                     pkg.addClass(newClass)
396                     newClass.setContainingPackage(pkg)
397                     delta.registerClass(newClass)
398                     return newClass
399                 }
400 
401                 private fun getOrAddPackage(pkgName: String): TextPackageItem {
402                     val pkg = delta.findPackage(pkgName)
403                     if (pkg != null) {
404                         return pkg
405                     }
406                     val newPkg = TextPackageItem(
407                         delta,
408                         pkgName,
409                         TextModifiers(delta, DefaultModifierList.PUBLIC),
410                         SourcePositionInfo.UNKNOWN
411                     )
412                     delta.addPackage(newPkg)
413                     return newPkg
414                 }
415             }, baseApi, signatureApi, ApiType.ALL.getReferenceFilter())
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