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 }