1 20 package net.sf.clirr.core.internal.checks; 21 22 import net.sf.clirr.core.ApiDifference; 23 import net.sf.clirr.core.Message; 24 import net.sf.clirr.core.Severity; 25 import net.sf.clirr.core.ScopeSelector; 26 import net.sf.clirr.core.internal.AbstractDiffReporter; 27 import net.sf.clirr.core.internal.ApiDiffDispatcher; 28 import net.sf.clirr.core.internal.ClassChangeCheck; 29 import net.sf.clirr.core.internal.CoIterator; 30 import net.sf.clirr.core.spi.JavaType; 31 import net.sf.clirr.core.spi.Method; 32 import net.sf.clirr.core.spi.Scope; 33 34 import java.util.ArrayList ; 35 import java.util.HashMap ; 36 import java.util.Iterator ; 37 import java.util.List ; 38 import java.util.Map ; 39 40 45 public class MethodSetCheck 46 extends AbstractDiffReporter 47 implements ClassChangeCheck 48 { 49 private static final Message MSG_METHOD_NOW_IN_SUPERCLASS = new Message(7000); 50 private static final Message MSG_METHOD_NOW_IN_INTERFACE = new Message(7001); 51 private static final Message MSG_METHOD_REMOVED = new Message(7002); 52 private static final Message MSG_METHOD_OVERRIDE_REMOVED = new Message(7003); 53 private static final Message MSG_METHOD_ARGCOUNT_CHANGED = new Message(7004); 54 private static final Message MSG_METHOD_PARAMTYPE_CHANGED = new Message(7005); 55 private static final Message MSG_METHOD_RETURNTYPE_CHANGED = new Message(7006); 56 private static final Message MSG_METHOD_DEPRECATED = new Message(7007); 57 private static final Message MSG_METHOD_UNDEPRECATED = new Message(7008); 58 private static final Message MSG_METHOD_LESS_ACCESSIBLE = new Message(7009); 59 private static final Message MSG_METHOD_MORE_ACCESSIBLE = new Message(7010); 60 private static final Message MSG_METHOD_ADDED = new Message(7011); 61 private static final Message MSG_METHOD_ADDED_TO_INTERFACE = new Message(7012); 62 private static final Message MSG_ABSTRACT_METHOD_ADDED = new Message(7013); 63 private static final Message MSG_METHOD_NOW_FINAL = new Message(7014); 64 private static final Message MSG_METHOD_NOW_NONFINAL = new Message(7015); 65 66 private ScopeSelector scopeSelector; 67 68 74 public MethodSetCheck(ApiDiffDispatcher dispatcher, ScopeSelector scopeSelector) 75 { 76 super(dispatcher); 77 this.scopeSelector = scopeSelector; 78 } 79 80 public final boolean check(JavaType compatBaseline, JavaType currentVersion) 81 { 82 if (compatBaseline.isInterface() ^ currentVersion.isInterface()) 86 { 87 return true; 88 } 89 90 Map bNameToMethod = buildNameToMethodMap(compatBaseline); 91 Map cNameToMethod = buildNameToMethodMap(currentVersion); 92 93 CoIterator iter = new CoIterator(null, bNameToMethod.keySet(), cNameToMethod.keySet()); 94 95 while (iter.hasNext()) 96 { 97 iter.next(); 98 99 String baselineMethodName = (String ) iter.getLeft(); 100 String currentMethodName = (String ) iter.getRight(); 101 102 if (baselineMethodName == null) 103 { 104 List currentMethods = (List ) cNameToMethod.get(currentMethodName); 106 reportMethodsAdded(currentVersion, currentMethods); 107 } 108 else if (currentMethodName == null) 109 { 110 List baselineMethods = (List ) bNameToMethod.get(baselineMethodName); 112 reportMethodsRemoved(compatBaseline, baselineMethods, currentVersion); 113 } 114 else 115 { 116 118 List baselineMethods = (List ) bNameToMethod.get(baselineMethodName); 119 List currentMethods = (List ) cNameToMethod.get(currentMethodName); 120 121 filterSoftMatchedMethods( 122 compatBaseline, baselineMethods, 123 currentVersion, currentMethods); 124 125 filterChangedMethods( 126 baselineMethodName, 127 compatBaseline, baselineMethods, 128 currentVersion, currentMethods); 129 130 133 if (!baselineMethods.isEmpty()) 134 { 135 reportMethodsRemoved(compatBaseline, baselineMethods, currentVersion); 136 } 137 138 if (!currentMethods.isEmpty()) 139 { 140 reportMethodsAdded(currentVersion, currentMethods); 141 } 142 } 143 } 144 145 return true; 146 } 147 148 167 private void filterSoftMatchedMethods( 168 JavaType compatBaseline, 169 List baselineMethods, 170 JavaType currentVersion, 171 List currentMethods) 172 { 173 for (Iterator bIter = baselineMethods.iterator(); bIter.hasNext();) 174 { 175 Method bMethod = (Method) bIter.next(); 176 177 for (Iterator cIter = currentMethods.iterator(); cIter.hasNext();) 178 { 179 Method cMethod = (Method) cIter.next(); 180 181 if (isSoftMatch(bMethod, cMethod)) 182 { 183 check(compatBaseline, bMethod, cMethod); 184 bIter.remove(); 185 cIter.remove(); 186 break; 187 } 188 } 189 } 190 } 191 192 208 private boolean isSoftMatch(Method oldMethod, Method newMethod) 209 { 210 String oldName = oldMethod.getName(); 211 String newName = newMethod.getName(); 212 213 if (!oldName.equals(newName)) 214 { 215 return false; 216 } 217 218 StringBuffer buf = new StringBuffer (); 219 appendHumanReadableArgTypeList(oldMethod, buf); 220 String oldArgs = buf.toString(); 221 222 buf.setLength(0); 223 appendHumanReadableArgTypeList(newMethod, buf); 224 String newArgs = buf.toString(); 225 226 return (oldArgs.equals(newArgs)); 227 } 228 229 236 private void filterChangedMethods( 237 String methodName, 238 JavaType compatBaseline, 239 List baselineMethods, 240 JavaType currentVersion, 241 List currentMethods) 242 { 243 251 while (!baselineMethods.isEmpty() && !currentMethods.isEmpty()) 252 { 253 int[][] similarityTable = buildSimilarityTable(baselineMethods, currentMethods); 254 255 int min = Integer.MAX_VALUE; 256 int iMin = baselineMethods.size(); 257 int jMin = currentMethods.size(); 258 for (int i = 0; i < baselineMethods.size(); i++) 259 { 260 for (int j = 0; j < currentMethods.size(); j++) 261 { 262 final int tableEntry = similarityTable[i][j]; 263 if (tableEntry < min) 264 { 265 min = tableEntry; 266 iMin = i; 267 jMin = j; 268 } 269 } 270 } 271 Method iMethod = (Method) baselineMethods.remove(iMin); 272 Method jMethod = (Method) currentMethods.remove(jMin); 273 check(compatBaseline, iMethod, jMethod); 274 } 275 } 276 277 private int[][] buildSimilarityTable(List baselineMethods, List currentMethods) 278 { 279 int[][] similarityTable = new int[baselineMethods.size()][currentMethods.size()]; 280 for (int i = 0; i < baselineMethods.size(); i++) 281 { 282 for (int j = 0; j < currentMethods.size(); j++) 283 { 284 final Method iMethod = (Method) baselineMethods.get(i); 285 final Method jMethod = (Method) currentMethods.get(j); 286 similarityTable[i][j] = distance(iMethod, jMethod); 287 } 288 } 289 return similarityTable; 290 } 291 292 private int distance(Method m1, Method m2) 293 { 294 final JavaType[] m1Args = m1.getArgumentTypes(); 295 final JavaType[] m2Args = m2.getArgumentTypes(); 296 297 if (m1Args.length != m2Args.length) 298 { 299 return 1000 * Math.abs(m1Args.length - m2Args.length); 300 } 301 302 int retVal = 0; 303 for (int i = 0; i < m1Args.length; i++) 304 { 305 if (!m1Args[i].toString().equals(m2Args[i].toString())) 306 { 307 retVal += 1; 308 } 309 } 310 return retVal; 311 } 312 313 319 private String findSuperClassWithSignature(String methodSignature, JavaType clazz) 320 { 321 final JavaType[] superClasses = clazz.getSuperClasses(); 322 for (int i = 0; i < superClasses.length; i++) 323 { 324 JavaType superClass = superClasses[i]; 325 final Method[] superMethods = superClass.getMethods(); 326 for (int j = 0; j < superMethods.length; j++) 327 { 328 Method superMethod = superMethods[j]; 329 final String superMethodSignature = getMethodId(superClass, superMethod); 330 if (methodSignature.equals(superMethodSignature)) 331 { 332 return superClass.getName(); 333 } 334 } 335 336 } 337 return null; 338 } 339 340 346 private String findSuperInterfaceWithSignature(String methodSignature, JavaType clazz) 347 { 348 final JavaType[] superClasses = clazz.getAllInterfaces(); 349 for (int i = 0; i < superClasses.length; i++) 350 { 351 JavaType superClass = superClasses[i]; 352 final Method[] superMethods = superClass.getMethods(); 353 for (int j = 0; j < superMethods.length; j++) 354 { 355 Method superMethod = superMethods[j]; 356 final String superMethodSignature = getMethodId(superClass, superMethod); 357 if (methodSignature.equals(superMethodSignature)) 358 { 359 return superClass.getName(); 360 } 361 } 362 363 } 364 return null; 365 } 366 367 370 private void reportMethodsRemoved( 371 JavaType baselineClass, 372 List baselineMethods, 373 JavaType currentClass) 374 { 375 for (Iterator i = baselineMethods.iterator(); i.hasNext();) 376 { 377 Method method = (Method) i.next(); 378 reportMethodRemoved(baselineClass, method, currentClass); 379 } 380 } 381 382 388 private void reportMethodRemoved( 389 JavaType oldClass, 390 Method oldMethod, 391 JavaType currentClass) 392 { 393 if (!scopeSelector.isSelected(oldMethod)) 394 { 395 return; 396 } 397 398 String signature = getMethodId(oldClass, oldMethod); 399 400 String oldBaseClassForMethod = findSuperClassWithSignature(signature, oldClass); 401 String oldInterfaceForMethod = findSuperInterfaceWithSignature(signature, oldClass); 402 403 String newBaseClassForMethod = findSuperClassWithSignature(signature, currentClass); 404 String newInterfaceForMethod = findSuperInterfaceWithSignature(signature, currentClass); 405 406 boolean oldInheritedMethod = (oldBaseClassForMethod != null) || (oldInterfaceForMethod != null); 407 boolean newInheritedMethod = (newBaseClassForMethod != null) || (newInterfaceForMethod != null); 408 409 if (oldInheritedMethod && newInheritedMethod) 410 { 411 fireDiff(MSG_METHOD_OVERRIDE_REMOVED, 416 Severity.INFO, 417 oldClass, oldMethod, null); 418 } 419 else if (oldInheritedMethod) 420 { 421 fireDiff(MSG_METHOD_REMOVED, 426 getSeverity(oldClass, oldMethod, Severity.ERROR), 427 oldClass, oldMethod, null); 428 } 429 else if (newBaseClassForMethod != null) 430 { 431 fireDiff(MSG_METHOD_NOW_IN_SUPERCLASS, 437 Severity.INFO, oldClass, oldMethod, 438 new String [] {newBaseClassForMethod}); 439 } 440 else if (newInterfaceForMethod != null) 441 { 442 fireDiff(MSG_METHOD_NOW_IN_INTERFACE, 448 Severity.INFO, oldClass, oldMethod, 449 new String [] {newInterfaceForMethod}); 450 } 451 else 452 { 453 fireDiff(MSG_METHOD_REMOVED, 457 getSeverity(oldClass, oldMethod, Severity.ERROR), 458 oldClass, oldMethod, null); 459 } 460 } 461 462 465 private void reportMethodsAdded( 466 JavaType currentClass, 467 List currentMethods) 468 { 469 for (Iterator i = currentMethods.iterator(); i.hasNext();) 470 { 471 Method method = (Method) i.next(); 472 reportMethodAdded(currentClass, method); 473 } 474 } 475 476 479 private void reportMethodAdded(JavaType newClass, Method newMethod) 480 { 481 if (!scopeSelector.isSelected(newMethod)) 482 { 483 return; 484 } 485 486 if (newClass.isInterface()) 487 { 488 fireDiff(MSG_METHOD_ADDED_TO_INTERFACE, 494 getSeverity(newClass, newMethod, Severity.ERROR), 495 newClass, newMethod, null); 496 } 497 else if (newMethod.isAbstract()) 498 { 499 fireDiff(MSG_ABSTRACT_METHOD_ADDED, 508 Severity.ERROR, newClass, newMethod, null); 509 } 510 else 511 { 512 fireDiff(MSG_METHOD_ADDED, 523 Severity.INFO, newClass, newMethod, null); 524 } 525 } 526 527 530 private Map buildNameToMethodMap(JavaType clazz) 531 { 532 Method[] methods = clazz.getMethods(); 533 Map retVal = new HashMap (); 534 for (int i = 0; i < methods.length; i++) 535 { 536 Method method = methods[i]; 537 538 final String name = method.getName(); 539 List set = (List ) retVal.get(name); 540 if (set == null) 541 { 542 set = new ArrayList (); 543 retVal.put(name, set); 544 } 545 set.add(method); 546 } 547 return retVal; 548 } 549 550 private void check(JavaType compatBaseline, Method baselineMethod, Method currentMethod) 551 { 552 if (!scopeSelector.isSelected(baselineMethod) && !scopeSelector.isSelected(currentMethod)) 553 { 554 return; 555 } 556 557 checkParameterTypes(compatBaseline, baselineMethod, currentMethod); 558 checkReturnType(compatBaseline, baselineMethod, currentMethod); 559 checkDeclaredExceptions(compatBaseline, baselineMethod, currentMethod); 560 checkDeprecated(compatBaseline, baselineMethod, currentMethod); 561 checkVisibility(compatBaseline, baselineMethod, currentMethod); 562 checkFinal(compatBaseline, baselineMethod, currentMethod); 563 } 564 565 private void checkParameterTypes(JavaType compatBaseline, Method baselineMethod, Method currentMethod) 566 { 567 JavaType[] bArgs = baselineMethod.getArgumentTypes(); 568 JavaType[] cArgs = currentMethod.getArgumentTypes(); 569 570 if (bArgs.length != cArgs.length) 571 { 572 fireDiff(MSG_METHOD_ARGCOUNT_CHANGED, 573 getSeverity(compatBaseline, baselineMethod, Severity.ERROR), 574 compatBaseline, baselineMethod, null); 575 return; 576 } 577 578 for (int i = 0; i < bArgs.length; i++) 580 { 581 JavaType bArg = bArgs[i]; 582 JavaType cArg = cArgs[i]; 583 584 if (bArg.getName().equals(cArg.getName())) 585 { 586 continue; 587 } 588 589 String [] args = 591 { 592 "" + (i + 1), 593 cArg.toString() 594 }; 595 fireDiff(MSG_METHOD_PARAMTYPE_CHANGED, 596 getSeverity(compatBaseline, baselineMethod, Severity.ERROR), 597 compatBaseline, baselineMethod, args); 598 } 599 } 600 601 private void checkReturnType(JavaType compatBaseline, Method baselineMethod, Method currentMethod) 602 { 603 JavaType bReturnType = baselineMethod.getReturnType(); 604 JavaType cReturnType = currentMethod.getReturnType(); 605 606 if (!bReturnType.toString().equals(cReturnType.toString())) 610 { 611 fireDiff(MSG_METHOD_RETURNTYPE_CHANGED, 612 getSeverity(compatBaseline, baselineMethod, Severity.ERROR), 613 compatBaseline, baselineMethod, 614 new String [] {cReturnType.toString()}); 615 } 616 } 617 618 private void checkDeclaredExceptions( 619 JavaType compatBaseline, 620 Method baselineMethod, Method currentMethod) 621 { 622 } 624 625 private void checkDeprecated( 626 JavaType compatBaseline, 627 Method baselineMethod, Method currentMethod) 628 { 629 boolean bIsDeprecated = baselineMethod.isDeprecated(); 630 boolean cIsDeprecated = currentMethod.isDeprecated(); 631 632 if (bIsDeprecated && !cIsDeprecated) 633 { 634 fireDiff(MSG_METHOD_UNDEPRECATED, 635 Severity.INFO, compatBaseline, baselineMethod, null); 636 } 637 else if (!bIsDeprecated && cIsDeprecated) 638 { 639 fireDiff(MSG_METHOD_DEPRECATED, 640 Severity.INFO, compatBaseline, baselineMethod, null); 641 } 642 } 643 644 648 private void checkVisibility(JavaType compatBaseline, Method baselineMethod, Method currentMethod) 649 { 650 Scope bScope = baselineMethod.getEffectiveScope(); 651 Scope cScope = currentMethod.getEffectiveScope(); 652 653 if (cScope.isLessVisibleThan(bScope)) 654 { 655 String [] args = {bScope.getDesc(), cScope.getDesc()}; 656 fireDiff(MSG_METHOD_LESS_ACCESSIBLE, 657 getSeverity(compatBaseline, baselineMethod, Severity.ERROR), 658 compatBaseline, baselineMethod, args); 659 } 660 else if (cScope.isMoreVisibleThan(bScope)) 661 { 662 String [] args = {bScope.getDesc(), cScope.getDesc()}; 663 fireDiff(MSG_METHOD_MORE_ACCESSIBLE, 664 Severity.INFO, compatBaseline, baselineMethod, args); 665 } 666 } 667 668 private void checkFinal( 669 JavaType compatBaseline, 670 Method baselineMethod, Method currentMethod) 671 { 672 boolean bIsFinal = baselineMethod.isFinal(); 673 boolean cIsFinal = currentMethod.isFinal(); 674 675 if (bIsFinal && !cIsFinal) 676 { 677 fireDiff(MSG_METHOD_NOW_NONFINAL, 678 Severity.INFO, compatBaseline, baselineMethod, null); 679 } 680 else if (!bIsFinal && cIsFinal) 681 { 682 fireDiff(MSG_METHOD_NOW_FINAL, 683 Severity.ERROR, compatBaseline, baselineMethod, null); 684 } 685 } 686 687 694 private String getMethodId(JavaType clazz, Method method) 695 { 696 StringBuffer buf = new StringBuffer (); 697 698 final String scopeDecl = method.getDeclaredScope().getDecl(); 699 if (scopeDecl.length() > 0) 700 { 701 buf.append(scopeDecl); 702 buf.append(" "); 703 } 704 705 String name = method.getName(); 706 if ("<init>".equals(name)) 707 { 708 final String className = clazz.getName(); 709 int idx = className.lastIndexOf('.'); 710 name = className.substring(idx + 1); 711 } 712 else 713 { 714 buf.append(method.getReturnType()); 715 buf.append(' '); 716 } 717 buf.append(name); 718 buf.append('('); 719 appendHumanReadableArgTypeList(method, buf); 720 buf.append(')'); 721 return buf.toString(); 722 } 723 724 private void appendHumanReadableArgTypeList(Method method, StringBuffer buf) 725 { 726 JavaType[] argTypes = method.getArgumentTypes(); 727 String argSeparator = ""; 728 for (int i = 0; i < argTypes.length; i++) 729 { 730 buf.append(argSeparator); 731 buf.append(argTypes[i].getName()); 732 argSeparator = ", "; 733 } 734 } 735 736 private void fireDiff(Message msg, Severity severity, JavaType clazz, Method method, String [] args) 737 { 738 final String className = clazz.getName(); 739 final ApiDifference diff = 740 new ApiDifference( 741 msg, severity, className, getMethodId(clazz, method), null, args); 742 getApiDiffDispatcher().fireDiff(diff); 743 } 744 745 } 746 | Popular Tags |