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