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