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