• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2017 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
18 
19 import com.android.tools.metalava.model.AnnotationItem
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.Codebase
22 import com.android.tools.metalava.model.ConstructorItem
23 import com.android.tools.metalava.model.FieldItem
24 import com.android.tools.metalava.model.Item
25 import com.android.tools.metalava.model.MethodItem
26 import com.android.tools.metalava.model.PackageItem
27 import com.android.tools.metalava.model.ParameterItem
28 import com.android.tools.metalava.model.PropertyItem
29 import com.android.tools.metalava.model.visitors.ApiVisitor
30 import com.android.tools.metalava.model.visitors.VisibleItemVisitor
31 import com.intellij.util.containers.Stack
32 import java.util.Comparator
33 import java.util.function.Predicate
34 
35 /**
36  * Visitor which visits all items in two matching codebases and
37  * matches up the items and invokes [compare] on each pair, or
38  * [added] or [removed] when items are not matched
39  */
40 open class ComparisonVisitor(
41     /**
42      * Whether constructors should be visited as part of a [#visitMethod] call
43      * instead of just a [#visitConstructor] call. Helps simplify visitors that
44      * don't care to distinguish between the two cases. Defaults to true.
45      */
46     val visitConstructorsAsMethods: Boolean = true,
47     /**
48      * Normally if a new item is found, the visitor will
49      * only visit the top level newly added item, not all
50      * of its children. This flags enables you to request
51      * all individual items to also be visited.
52      */
53     val visitAddedItemsRecursively: Boolean = false
54 ) {
55     open fun compare(old: Item, new: Item) {}
56     open fun added(new: Item) {}
57     open fun removed(old: Item, from: Item?) {}
58 
59     open fun compare(old: PackageItem, new: PackageItem) {}
60     open fun compare(old: ClassItem, new: ClassItem) {}
61     open fun compare(old: ConstructorItem, new: ConstructorItem) {}
62     open fun compare(old: MethodItem, new: MethodItem) {}
63     open fun compare(old: FieldItem, new: FieldItem) {}
64     open fun compare(old: PropertyItem, new: PropertyItem) {}
65     open fun compare(old: ParameterItem, new: ParameterItem) {}
66 
67     open fun added(new: PackageItem) {}
68     open fun added(new: ClassItem) {}
69     open fun added(new: ConstructorItem) {}
70     open fun added(new: MethodItem) {}
71     open fun added(new: FieldItem) {}
72     open fun added(new: PropertyItem) {}
73     open fun added(new: ParameterItem) {}
74 
75     open fun removed(old: PackageItem, from: Item?) {}
76     open fun removed(old: ClassItem, from: Item?) {}
77     open fun removed(old: ConstructorItem, from: ClassItem?) {}
78     open fun removed(old: MethodItem, from: ClassItem?) {}
79     open fun removed(old: FieldItem, from: ClassItem?) {}
80     open fun removed(old: PropertyItem, from: ClassItem?) {}
81     open fun removed(old: ParameterItem, from: MethodItem?) {}
82 }
83 
84 class CodebaseComparator {
85     /**
86      * Visits this codebase and compares it with another codebase, informing the visitors about
87      * the correlations and differences that it finds
88      */
comparenull89     fun compare(visitor: ComparisonVisitor, old: Codebase, new: Codebase, filter: Predicate<Item>? = null) {
90         // Algorithm: build up two trees (by nesting level); then visit the
91         // two trees
92         val oldTree = createTree(old, filter)
93         val newTree = createTree(new, filter)
94 
95         /* Debugging:
96         println("Old:\n${ItemTree.prettyPrint(oldTree)}")
97         println("New:\n${ItemTree.prettyPrint(newTree)}")
98         */
99 
100         compare(visitor, oldTree, newTree, null, null)
101     }
102 
comparenull103     private fun compare(
104         visitor: ComparisonVisitor,
105         oldList: List<ItemTree>,
106         newList: List<ItemTree>,
107         newParent: Item?,
108         oldParent: Item?
109     ) {
110         // Debugging tip: You can print out a tree like this: ItemTree.prettyPrint(list)
111         var index1 = 0
112         var index2 = 0
113         val length1 = oldList.size
114         val length2 = newList.size
115 
116         while (true) {
117             if (index1 < length1) {
118                 if (index2 < length2) {
119                     // Compare the items
120                     val oldTree = oldList[index1]
121                     val newTree = newList[index2]
122                     val old = oldTree.item()
123                     val new = newTree.item()
124 
125                     val compare = compare(old, new)
126                     when {
127                         compare > 0 -> {
128                             index2++
129                             visitAdded(new, oldParent, visitor, newTree)
130                         }
131                         compare < 0 -> {
132                             index1++
133                             visitRemoved(visitor, old, newParent)
134                         }
135                         else -> {
136                             visitCompare(visitor, old, new)
137 
138                             // Compare the children (recurse)
139                             compare(visitor, oldTree.children, newTree.children, newTree.item(), oldTree.item())
140 
141                             index1++
142                             index2++
143                         }
144                     }
145                 } else {
146                     // All the remaining items in oldList have been deleted
147                     while (index1 < length1) {
148                         visitRemoved(visitor, oldList[index1++].item(), newParent)
149                     }
150                 }
151             } else if (index2 < length2) {
152                 // All the remaining items in newList have been added
153                 while (index2 < length2) {
154                     val newTree = newList[index2++]
155                     val new = newTree.item()
156 
157                     visitAdded(new, oldParent, visitor, newTree)
158                 }
159             } else {
160                 break
161             }
162         }
163     }
164 
visitAddednull165     private fun visitAdded(
166         new: Item,
167         oldParent: Item?,
168         visitor: ComparisonVisitor,
169         newTree: ItemTree
170     ) {
171         // If it's a method, we may not have added a new method,
172         // we may simply have inherited it previously and overriding
173         // it now (or in the case of signature files, identical overrides
174         // are not explicitly listed and therefore not added to the model)
175         val inherited =
176             if (new is MethodItem && oldParent is ClassItem) {
177                 oldParent.findMethod(
178                     template = new,
179                     includeSuperClasses = true,
180                     includeInterfaces = true
181                 )?.duplicate(oldParent)
182             } else {
183                 null
184             }
185 
186         if (inherited != null) {
187             visitCompare(visitor, inherited, new)
188             // Compare the children (recurse)
189             if (inherited.parameters().isNotEmpty()) {
190                 val parameters = inherited.parameters().map { ItemTree(it) }.toList()
191                 compare(visitor, parameters, newTree.children, newTree.item(), inherited)
192             }
193         } else {
194             visitAdded(visitor, new)
195         }
196     }
197 
visitAddednull198     private fun visitAdded(visitor: ComparisonVisitor, new: Item) {
199         if (visitor.visitAddedItemsRecursively) {
200             new.accept(object : VisibleItemVisitor() {
201                 override fun visitItem(item: Item) {
202                     doVisitAdded(visitor, item)
203                 }
204             })
205         } else {
206             doVisitAdded(visitor, new)
207         }
208     }
209 
210     @Suppress("USELESS_CAST") // Overloaded visitor methods: be explicit about which one is being invoked
doVisitAddednull211     private fun doVisitAdded(visitor: ComparisonVisitor, item: Item) {
212         visitor.added(item)
213 
214         when (item) {
215             is PackageItem -> visitor.added(item)
216             is ClassItem -> visitor.added(item)
217             is MethodItem -> {
218                 if (visitor.visitConstructorsAsMethods) {
219                     visitor.added(item)
220                 } else {
221                     if (item is ConstructorItem) {
222                         visitor.added(item as ConstructorItem)
223                     } else {
224                         visitor.added(item as MethodItem)
225                     }
226                 }
227             }
228             is FieldItem -> visitor.added(item)
229             is ParameterItem -> visitor.added(item)
230             is PropertyItem -> visitor.added(item)
231         }
232     }
233 
234     @Suppress("USELESS_CAST") // Overloaded visitor methods: be explicit about which one is being invoked
visitRemovednull235     private fun visitRemoved(visitor: ComparisonVisitor, item: Item, from: Item?) {
236         visitor.removed(item, from)
237 
238         when (item) {
239             is PackageItem -> visitor.removed(item, from)
240             is ClassItem -> visitor.removed(item, from)
241             is MethodItem -> {
242                 if (visitor.visitConstructorsAsMethods) {
243                     visitor.removed(item, from as ClassItem?)
244                 } else {
245                     if (item is ConstructorItem) {
246                         visitor.removed(item as ConstructorItem, from as ClassItem?)
247                     } else {
248                         visitor.removed(item as MethodItem, from as ClassItem?)
249                     }
250                 }
251             }
252             is FieldItem -> visitor.removed(item, from as ClassItem?)
253             is ParameterItem -> visitor.removed(item, from as MethodItem?)
254             is PropertyItem -> visitor.removed(item, from as ClassItem?)
255         }
256     }
257 
258     @Suppress("USELESS_CAST") // Overloaded visitor methods: be explicit about which one is being invoked
visitComparenull259     private fun visitCompare(visitor: ComparisonVisitor, old: Item, new: Item) {
260         visitor.compare(old, new)
261 
262         when (old) {
263             is PackageItem -> visitor.compare(old, new as PackageItem)
264             is ClassItem -> visitor.compare(old, new as ClassItem)
265             is MethodItem -> {
266                 if (visitor.visitConstructorsAsMethods) {
267                     visitor.compare(old, new as MethodItem)
268                 } else {
269                     if (old is ConstructorItem) {
270                         visitor.compare(old as ConstructorItem, new as MethodItem)
271                     } else {
272                         visitor.compare(old as MethodItem, new as MethodItem)
273                     }
274                 }
275             }
276             is FieldItem -> visitor.compare(old, new as FieldItem)
277             is ParameterItem -> visitor.compare(old, new as ParameterItem)
278             is PropertyItem -> visitor.compare(old, new as PropertyItem)
279         }
280     }
281 
comparenull282     private fun compare(item1: Item, item2: Item): Int = comparator.compare(item1, item2)
283 
284     companion object {
285         /** Sorting rank for types */
286         private fun typeRank(item: Item): Int {
287             return when (item) {
288                 is PackageItem -> 0
289                 is MethodItem -> if (item.isConstructor()) 1 else 2
290                 is FieldItem -> 3
291                 is ClassItem -> 4
292                 is ParameterItem -> 5
293                 is AnnotationItem -> 6
294                 is PropertyItem -> 7
295                 else -> 8
296             }
297         }
298 
299         val comparator: Comparator<Item> = Comparator { item1, item2 ->
300             val typeSort = typeRank(item1) - typeRank(item2)
301             when {
302                 typeSort != 0 -> typeSort
303                 item1 == item2 -> 0
304                 else -> when (item1) {
305                     is PackageItem -> {
306                         item1.qualifiedName().compareTo((item2 as PackageItem).qualifiedName())
307                     }
308                     is ClassItem -> {
309                         item1.qualifiedName().compareTo((item2 as ClassItem).qualifiedName())
310                     }
311                     is MethodItem -> {
312                         var delta = item1.name().compareTo((item2 as MethodItem).name())
313                         if (delta == 0) {
314                             val parameters1 = item1.parameters()
315                             val parameters2 = item2.parameters()
316                             val parameterCount1 = parameters1.size
317                             val parameterCount2 = parameters2.size
318                             delta = parameterCount1 - parameterCount2
319                             if (delta == 0) {
320                                 for (i in 0 until parameterCount1) {
321                                     val parameter1 = parameters1[i]
322                                     val parameter2 = parameters2[i]
323                                     val type1 = parameter1.type().toTypeString(context = parameter1)
324                                     val type2 = parameter2.type().toTypeString(context = parameter2)
325                                     delta = type1.compareTo(type2)
326                                     if (delta != 0) {
327                                         // Try a little harder:
328                                         //  (1) treat varargs and arrays the same, and
329                                         //  (2) drop java.lang. prefixes from comparisons in wildcard
330                                         //      signatures since older signature files may have removed
331                                         //      those
332                                         val simpleType1 = parameter1.type().toCanonicalType(parameter1)
333                                         val simpleType2 = parameter2.type().toCanonicalType(parameter2)
334                                         delta = simpleType1.compareTo(simpleType2)
335                                         if (delta != 0) {
336                                             // Special case: Kotlin coroutines
337                                             if (simpleType1.startsWith("kotlin.coroutines.") && simpleType2.startsWith("kotlin.coroutines.")) {
338                                                 val t1 = simpleType1.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
339                                                 val t2 = simpleType2.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
340                                                 delta = t1.compareTo(t2)
341                                                 if (delta != 0) {
342                                                     break
343                                                 }
344                                             } else {
345                                                 break
346                                             }
347                                         }
348                                     }
349                                 }
350                             }
351                         }
352                         delta
353                     }
354                     is FieldItem -> {
355                         item1.name().compareTo((item2 as FieldItem).name())
356                     }
357                     is ParameterItem -> {
358                         item1.parameterIndex.compareTo((item2 as ParameterItem).parameterIndex)
359                     }
360                     is AnnotationItem -> {
361                         (item1.qualifiedName() ?: "").compareTo((item2 as AnnotationItem).qualifiedName() ?: "")
362                     }
363                     is PropertyItem -> {
364                         item1.name().compareTo((item2 as PropertyItem).name())
365                     }
366                     else -> {
367                         error("Unexpected item type ${item1.javaClass}")
368                     }
369                 }
370             }
371         }
372 
373         val treeComparator: Comparator<ItemTree> = Comparator { item1, item2 ->
374             comparator.compare(item1.item, item2.item())
375         }
376     }
377 
ensureSortednull378     private fun ensureSorted(items: MutableList<ItemTree>) {
379         items.sortWith(treeComparator)
380         for (item in items) {
381             ensureSorted(item)
382         }
383     }
384 
ensureSortednull385     private fun ensureSorted(item: ItemTree) {
386         item.children.sortWith(treeComparator)
387         for (child in item.children) {
388             ensureSorted(child)
389         }
390     }
391 
createTreenull392     private fun createTree(codebase: Codebase, filter: Predicate<Item>? = null): List<ItemTree> {
393         val stack = Stack<ItemTree>()
394         val root = ItemTree(null)
395         stack.push(root)
396 
397         val acceptAll = codebase.preFiltered || filter == null
398         val predicate = if (acceptAll) Predicate { true } else filter!!
399         codebase.accept(object : ApiVisitor(
400             nestInnerClasses = true,
401             inlineInheritedFields = true,
402             filterEmit = predicate,
403             filterReference = predicate
404         ) {
405             override fun visitItem(item: Item) {
406                 val node = ItemTree(item)
407                 val parent = stack.peek()
408                 parent.children += node
409 
410                 stack.push(node)
411             }
412 
413             override fun include(cls: ClassItem): Boolean = if (acceptAll) true else super.include(cls)
414 
415             override fun afterVisitItem(item: Item) {
416                 stack.pop()
417             }
418         })
419 
420         ensureSorted(root.children)
421         return root.children
422     }
423 
424     data class ItemTree(val item: Item?) : Comparable<ItemTree> {
425         val children: MutableList<ItemTree> = mutableListOf()
itemnull426         fun item(): Item = item!! // Only the root note can be null, and this method should never be called on it
427 
428         override fun compareTo(other: ItemTree): Int {
429             return comparator.compare(item(), other.item())
430         }
431 
toStringnull432         override fun toString(): String {
433             return item.toString()
434         }
435 
436         @Suppress("unused") // Left for debugging
prettyPrintnull437         fun prettyPrint(): String {
438             val sb = StringBuilder(1000)
439             prettyPrint(sb, 0)
440             return sb.toString()
441         }
442 
prettyPrintnull443         private fun prettyPrint(sb: StringBuilder, depth: Int) {
444             for (i in 0 until depth) {
445                 sb.append("    ")
446             }
447             sb.append(toString())
448             sb.append('\n')
449             for (child in children) {
450                 child.prettyPrint(sb, depth + 1)
451             }
452         }
453 
454         companion object {
455             @Suppress("unused") // Left for debugging
prettyPrintnull456             fun prettyPrint(list: List<ItemTree>): String {
457                 val sb = StringBuilder(1000)
458                 for (child in list) {
459                     child.prettyPrint(sb, 0)
460                 }
461                 return sb.toString()
462             }
463         }
464     }
465 }