OLD | NEW |
(Empty) | |
| 1 /* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved |
| 2 * |
| 3 * This program is distributed under the terms of |
| 4 * the GNU General Public License Version 2. See the LICENSE file |
| 5 * at the top of the source tree. |
| 6 */ |
| 7 package org.pantsbuild.jmake; |
| 8 |
| 9 import java.lang.reflect.Modifier; |
| 10 import java.util.List; |
| 11 import java.util.Set; |
| 12 |
| 13 /** |
| 14 * This class implements checking of source compatibility of classes and support
ing operations |
| 15 * |
| 16 * @author Misha Dmitriev |
| 17 * 12 March 2004 |
| 18 */ |
| 19 public class CompatibilityChecker { |
| 20 |
| 21 private PCDManager pcdm; |
| 22 private RefClassFinder rf; |
| 23 ClassInfo oldClassInfo = null; |
| 24 ClassInfo newClassInfo = null; |
| 25 private boolean versionsCompatible; |
| 26 private boolean publicConstantChanged; |
| 27 |
| 28 public CompatibilityChecker(PCDManager pcdm, boolean failOnDependentJar, boo
lean noWarnOnDependentJar) { |
| 29 this.pcdm = pcdm; |
| 30 publicConstantChanged = false; |
| 31 rf = new RefClassFinder(pcdm, failOnDependentJar, noWarnOnDependentJar); |
| 32 } |
| 33 |
| 34 /** |
| 35 * Compares the two class versions for the given PCDEntry. Returns true if a
ll changes are source |
| 36 * compatible, and false otherwise. |
| 37 */ |
| 38 public boolean compareClassVersions(PCDEntry entry) { |
| 39 // I once had the following optimization here with the comment "No sense
to make any further checks if |
| 40 // everything is recompiled anyway", but now I believe it's wrong. For e
ach class that was found changed |
| 41 // we need to know whether the new version is compatible with the old or
not, since this may determine |
| 42 // whether the new version of this class is promoted into the pdb or not
(see PCDManager.updateClassInfoInPCD()). |
| 43 // So, all changed classes should be checked just to correctly determine
version compatibility. |
| 44 // if (publicConstantChanged) return false; |
| 45 |
| 46 oldClassInfo = pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, entry); |
| 47 newClassInfo = pcdm.getClassInfoForPCDEntry(ClassInfo.VER_NEW, entry); |
| 48 |
| 49 rf.initialize(oldClassInfo.name, entry.javaFileFullPath.endsWith(".jar")
); |
| 50 versionsCompatible = true; |
| 51 |
| 52 checkAccessFlags(); |
| 53 checkSuperclasses(); |
| 54 checkImplementedInterfaces(); |
| 55 checkFields(); |
| 56 checkMethodsAndConstructors(); |
| 57 |
| 58 return versionsCompatible; |
| 59 } |
| 60 |
| 61 /** Find all dependent classes for a deleted class. */ |
| 62 public void checkDeletedClass(PCDEntry entry) { |
| 63 oldClassInfo = entry.oldClassInfo; |
| 64 rf.initialize(oldClassInfo.name, entry.javaFileFullPath.endsWith(".jar")
); |
| 65 rf.findReferencingClassesForDeletedClass(oldClassInfo); |
| 66 // It may happen that the only reference to deleted class X is via "X.cl
ass" construct |
| 67 String packageToLookIn = |
| 68 oldClassInfo.isPublic() ? null : oldClassInfo.packageName; |
| 69 rf.findClassesDeclaringField(("class$" + oldClassInfo.name).intern(), "j
ava/lang/Class", true, packageToLookIn); |
| 70 checkForFinalFields(); |
| 71 } |
| 72 |
| 73 /** Returns the names of classes affected by source incompatible changes to
the new version of the checked class. */ |
| 74 public String[] getAffectedClasses() { |
| 75 return rf.getAffectedClassNames(); |
| 76 } |
| 77 |
| 78 /** All of the following methods return true if no source incompatible chang
es found, and false otherwise */ |
| 79 private void checkAccessFlags() { |
| 80 char oldClassFlags = oldClassInfo.accessFlags; |
| 81 char newClassFlags = newClassInfo.accessFlags; |
| 82 if (oldClassFlags == newClassFlags) { |
| 83 return; |
| 84 } |
| 85 |
| 86 if (!Modifier.isFinal(oldClassFlags) && Modifier.isFinal(newClassFlags))
{ |
| 87 versionsCompatible = false; |
| 88 rf.findDirectSubclasses(oldClassInfo); |
| 89 } |
| 90 |
| 91 if (!Modifier.isAbstract(oldClassFlags) && Modifier.isAbstract(newClassF
lags)) { |
| 92 versionsCompatible = false; |
| 93 rf.findReferencingClasses0(oldClassInfo); |
| 94 } |
| 95 |
| 96 // Now to accessibility modifiers checking... |
| 97 if (Modifier.isPublic(newClassFlags)) { |
| 98 return; |
| 99 } |
| 100 |
| 101 if (Modifier.isProtected(newClassFlags)) { |
| 102 if (Modifier.isPublic(oldClassFlags)) { |
| 103 versionsCompatible = false; |
| 104 rf.findDiffPackageAndNotSubReferencingClasses1(oldClassInfo); |
| 105 } |
| 106 } else if (Modifier.isPrivate(newClassFlags)) { |
| 107 if (!Modifier.isPrivate(oldClassFlags)) { |
| 108 versionsCompatible = false; |
| 109 } else { |
| 110 return; // private -> private, nothing more to check |
| 111 } |
| 112 if (Modifier.isPublic(oldClassFlags)) { |
| 113 rf.findReferencingClasses1(oldClassInfo); |
| 114 } else if (Modifier.isProtected(oldClassFlags)) { |
| 115 rf.findThisPackageOrSubReferencingClasses1(oldClassInfo); |
| 116 } else { |
| 117 rf.findThisPackageReferencingClasses1(oldClassInfo); |
| 118 } |
| 119 } else { // newClassFlags has default access, since public has already
been excluded |
| 120 if (Modifier.isPublic(oldClassFlags)) { |
| 121 versionsCompatible = false; |
| 122 rf.findDiffPackageReferencingClasses1(oldClassInfo); |
| 123 } else if (Modifier.isProtected(oldClassFlags)) { |
| 124 versionsCompatible = false; |
| 125 rf.findDiffPackageAndSubReferencingClasses1(oldClassInfo); |
| 126 } |
| 127 } |
| 128 } |
| 129 |
| 130 private void checkSuperclasses() { |
| 131 List<String> oldSuperNames = oldClassInfo.getAllSuperclassNames(); |
| 132 List<String> newSuperNames = newClassInfo.getAllSuperclassNames(); |
| 133 |
| 134 int oldNamesSizeMinusOne = oldSuperNames.size() - 1; |
| 135 for (int i = 0; i <= oldNamesSizeMinusOne; i++) { |
| 136 String oldSuperName = oldSuperNames.get(i); |
| 137 if (!newSuperNames.contains(oldSuperName)) { |
| 138 versionsCompatible = false; |
| 139 ClassInfo missingSuperClass = |
| 140 pcdm.getClassInfoForName(ClassInfo.VER_OLD, oldSuperName
); |
| 141 if (missingSuperClass == null) { // This class is not in project |
| 142 missingSuperClass = |
| 143 ClassPath.getClassInfoForName(oldSuperName, pcdm); |
| 144 if (missingSuperClass == null) { |
| 145 missingSuperClass = new ClassInfo(oldSuperName, pcdm); |
| 146 } |
| 147 } |
| 148 rf.findReferencingClasses2(missingSuperClass, oldClassInfo); |
| 149 } |
| 150 } |
| 151 |
| 152 // Now check if the class is an exception, and its kind has changed from
unchecked to checked |
| 153 if (oldClassInfo.isInterface() || oldSuperNames.size() == 0) { |
| 154 return; |
| 155 } |
| 156 if (!(oldSuperNames.contains("java/lang/RuntimeException") || oldSuperNa
mes.contains("java/lang/Error"))) { |
| 157 return; |
| 158 } |
| 159 if (!(newSuperNames.contains("java/lang/RuntimeException") || newSuperNa
mes.contains("java/lang/Error"))) { |
| 160 if (!newSuperNames.contains("java/lang/Throwable")) { |
| 161 return; |
| 162 } |
| 163 // Ok, exception kind has changed from unchecked to checked. |
| 164 versionsCompatible = false; |
| 165 rf.findReferencingClasses0(oldClassInfo); |
| 166 rf.findRefsToMethodsThrowingException(oldClassInfo); |
| 167 } |
| 168 } |
| 169 |
| 170 private void checkImplementedInterfaces() { |
| 171 Set<String> oldIntfNames = oldClassInfo.getAllImplementedIntfNames(); |
| 172 Set<String> newIntfNames = newClassInfo.getAllImplementedIntfNames(); |
| 173 |
| 174 for (String oldIntfName : oldIntfNames) { |
| 175 if (!newIntfNames.contains(oldIntfName)) { |
| 176 versionsCompatible = false; |
| 177 ClassInfo missingSuperInterface = |
| 178 pcdm.getClassInfoForName(ClassInfo.VER_OLD, oldIntfName)
; |
| 179 if (missingSuperInterface == null) { // This class is not in pro
ject |
| 180 missingSuperInterface = |
| 181 ClassPath.getClassInfoForName(oldIntfName, pcdm); |
| 182 if (missingSuperInterface == null) { |
| 183 missingSuperInterface = new ClassInfo(oldIntfName, pcdm)
; |
| 184 } |
| 185 } |
| 186 rf.findReferencingClasses2(missingSuperInterface, oldClassInfo); |
| 187 } |
| 188 } |
| 189 |
| 190 // Check if the class is abstract, and an interface has been added to it
s list of implemented interfaces |
| 191 if (newClassInfo.isAbstract()) { |
| 192 for (String newIntfName : newIntfNames) { |
| 193 if (!oldIntfNames.contains(newIntfName)) { |
| 194 versionsCompatible = false; |
| 195 rf.findConcreteSubclasses(oldClassInfo); |
| 196 break; |
| 197 } |
| 198 } |
| 199 } |
| 200 } |
| 201 |
| 202 private void checkFields() { |
| 203 String oFNames[] = oldClassInfo.fieldNames; |
| 204 String oFSignatures[] = oldClassInfo.fieldSignatures; |
| 205 char oFFlags[] = oldClassInfo.fieldAccessFlags; |
| 206 String nFNames[] = newClassInfo.fieldNames; |
| 207 String nFSignatures[] = newClassInfo.fieldSignatures; |
| 208 char nFFlags[] = newClassInfo.fieldAccessFlags; |
| 209 int oFLen = oFNames != null ? oFNames.length : 0; |
| 210 int nFLen = nFNames != null ? nFNames.length : 0; |
| 211 |
| 212 int oFMod, nFMod; |
| 213 String oFName, oFSig, nFName; |
| 214 int i, j, k, endIdx; |
| 215 int nonMatchingNewFields = nFLen; |
| 216 |
| 217 for (i = 0; i < oFLen; i++) { |
| 218 oFMod = oFFlags[i]; |
| 219 if (Modifier.isPrivate(oFMod)) { |
| 220 continue; // Changes to private fields don't affect compatibili
ty |
| 221 } |
| 222 oFName = oFNames[i]; |
| 223 oFSig = oFSignatures[i]; |
| 224 boolean found = false; |
| 225 |
| 226 // Look for the same field in the new version considering name and t
ype |
| 227 endIdx = nFLen - 1; |
| 228 k = i < nFLen ? i : endIdx; |
| 229 for (j = 0; j < nFLen; j++) { |
| 230 if (oFName.equals(nFNames[k]) && |
| 231 oFSig.equals(nFSignatures[k])) { |
| 232 found = true; |
| 233 break; |
| 234 } |
| 235 if (k < endIdx) { |
| 236 k++; |
| 237 } else { |
| 238 k = 0; |
| 239 } |
| 240 } |
| 241 |
| 242 if (found) { |
| 243 nonMatchingNewFields--; |
| 244 nFMod = nFFlags[k]; |
| 245 checkFieldModifiers(oFMod, nFMod, i, k); |
| 246 if (publicConstantChanged) { |
| 247 return; |
| 248 } |
| 249 } else { // Matching field not found |
| 250 if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod) && |
| 251 oldClassInfo.primitiveConstantInitValues != null && |
| 252 oldClassInfo.primitiveConstantInitValues[i] != null) { |
| 253 // Compile-time constant deleted |
| 254 versionsCompatible = false; |
| 255 rf.findAllProjectClasses(oldClassInfo, i); |
| 256 if (Modifier.isPublic(oFMod)) { |
| 257 publicConstantChanged = true; |
| 258 return; |
| 259 } |
| 260 } else { |
| 261 versionsCompatible = false; |
| 262 rf.findReferencingClassesForField(oldClassInfo, i); |
| 263 } |
| 264 } |
| 265 } |
| 266 |
| 267 if (nonMatchingNewFields > 0) { // There are some fields declared in the
new version which don't exist in the old one |
| 268 // Look for fields hiding same-named fields in superclasses |
| 269 for (i = 0; i < nFLen; i++) { |
| 270 nFName = nFNames[i]; |
| 271 |
| 272 boolean found = false; |
| 273 for (j = 0; j < oFLen; j++) { |
| 274 if (nFName.equals(oFNames[j])) { |
| 275 found = true; |
| 276 break; |
| 277 } |
| 278 } |
| 279 if (found) { |
| 280 continue; // nFName is not an added field |
| 281 } |
| 282 String superName = oldClassInfo.superName; |
| 283 ClassInfo superInfo; |
| 284 while (superName != null) { |
| 285 superInfo = |
| 286 pcdm.getClassInfoForName(ClassInfo.VER_OLD, superNam
e); |
| 287 if (superInfo == null) { |
| 288 break; |
| 289 } |
| 290 String[] superOFNames = superInfo.fieldNames; |
| 291 int superOFNamesLen = superOFNames != null ? superOFNames.le
ngth |
| 292 : 0; |
| 293 for (j = 0; j < superOFNamesLen; j++) { |
| 294 if (nFName == superOFNames[j]) { |
| 295 versionsCompatible = false; |
| 296 rf.findReferencingClassesForField(superInfo, j); |
| 297 } |
| 298 } |
| 299 superName = superInfo.superName; |
| 300 } |
| 301 } |
| 302 } |
| 303 } |
| 304 |
| 305 /** It is already known that old field is not private */ |
| 306 private void checkFieldModifiers(int oFMod, int nFMod, int oldFieldIdx, int
newFieldIdx) { |
| 307 if (oFMod == nFMod) { |
| 308 if (Modifier.isFinal(oFMod) && |
| 309 (!ClassInfo.constFieldInitValuesEqual(oldClassInfo, oldField
Idx, newClassInfo, newFieldIdx))) { |
| 310 versionsCompatible = false; |
| 311 rf.findAllProjectClasses(oldClassInfo, oldFieldIdx); |
| 312 if (Modifier.isPublic(oFMod)) { |
| 313 publicConstantChanged = true; // Means we will have to reco
mpile ALL project classes |
| 314 } |
| 315 return; |
| 316 } |
| 317 } |
| 318 |
| 319 // These tests are ordered such that if a previous test succeeds, there
is no need to do further tests, since that |
| 320 // former test will cause more classes to be checked than any of the fur
ther tests. That is why it is possible to |
| 321 // check properties that are in fact independent (e.g. accessibility vs.
static/non-static) together. But this |
| 322 // optimization only works since all kinds of tests result in the same k
ind of find..ReferencingClassesForField() |
| 323 // outcome. For methods this is not true, and so there we have to check
independent properties separately. |
| 324 if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod) && // oFMod is k
nown to be non-private |
| 325 (!Modifier.isFinal(nFMod) || !ClassInfo.constFieldInitValuesEqua
l(oldClassInfo, oldFieldIdx, newClassInfo, newFieldIdx))) { |
| 326 versionsCompatible = false; |
| 327 rf.findAllProjectClasses(oldClassInfo, oldFieldIdx); |
| 328 if (Modifier.isPublic(oFMod)) { |
| 329 publicConstantChanged = true; |
| 330 } |
| 331 } else if (Modifier.isPrivate(nFMod) || // oFMod is known to be non-priv
ate |
| 332 (!Modifier.isFinal(oFMod) && Modifier.isFinal(nFMod)) || |
| 333 (Modifier.isStatic(oFMod) != Modifier.isStatic(nFMod)) || |
| 334 (Modifier.isVolatile(oFMod) != Modifier.isVolatile(nFMod))) { |
| 335 versionsCompatible = false; |
| 336 rf.findReferencingClassesForField(oldClassInfo, oldFieldIdx); |
| 337 } else if (Modifier.isPublic(oFMod) && Modifier.isProtected(nFMod)) { |
| 338 versionsCompatible = false; |
| 339 rf.findDiffPackageReferencingClassesForField(oldClassInfo, oldFieldI
dx); |
| 340 } else if ((Modifier.isPublic(oFMod) || Modifier.isProtected(oFMod)) && |
| 341 (!(Modifier.isPublic(nFMod) || Modifier.isProtected(nFMod) || Mo
difier.isPrivate(nFMod)))) { |
| 342 versionsCompatible = false; |
| 343 if (Modifier.isPublic(oFMod)) { |
| 344 rf.findDiffPackageReferencingClassesForField(oldClassInfo, oldFi
eldIdx); |
| 345 } else { |
| 346 rf.findDiffPackageAndSubReferencingClassesForField(oldClassInfo,
oldFieldIdx); |
| 347 } |
| 348 } |
| 349 } |
| 350 |
| 351 private void checkForFinalFields() { |
| 352 char oFFlags[] = oldClassInfo.fieldAccessFlags; |
| 353 int oFLen = oldClassInfo.fieldNames != null ? oldClassInfo.fieldNames.le
ngth |
| 354 : 0; |
| 355 int oFMod; |
| 356 |
| 357 for (int i = 0; i < oFLen; i++) { |
| 358 oFMod = oFFlags[i]; |
| 359 if (Modifier.isPrivate(oFMod)) { |
| 360 continue; // Changes to private fields don't affect compatibili
ty |
| 361 } |
| 362 if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod)) { |
| 363 rf.findAllProjectClasses(oldClassInfo, i); |
| 364 if (Modifier.isPublic(oFMod)) { |
| 365 publicConstantChanged = true; |
| 366 return; |
| 367 } |
| 368 } |
| 369 } |
| 370 } |
| 371 |
| 372 private void checkMethodsAndConstructors() { |
| 373 String oMNames[] = oldClassInfo.methodNames; |
| 374 String oMSignatures[] = oldClassInfo.methodSignatures; |
| 375 char oMFlags[] = oldClassInfo.methodAccessFlags; |
| 376 String nMNames[] = newClassInfo.methodNames; |
| 377 String nMSignatures[] = newClassInfo.methodSignatures; |
| 378 char nMFlags[] = newClassInfo.methodAccessFlags; |
| 379 int oMLen = oMNames != null ? oMNames.length : 0; |
| 380 int nMLen = nMNames != null ? nMNames.length : 0; |
| 381 |
| 382 int oMMod, nMMod; |
| 383 String oMName, oMSig, nMName, nMSig; |
| 384 int i, j, k, endIdx; |
| 385 int nonMatchingNewMethods = nMLen; |
| 386 |
| 387 for (i = 0; i < oMLen; i++) { |
| 388 oMMod = oMFlags[i]; |
| 389 if (Modifier.isPrivate(oMMod)) { |
| 390 continue; // Changes to private methods don't affect compatibil
ity |
| 391 } |
| 392 oMName = oMNames[i]; |
| 393 oMSig = oMSignatures[i]; |
| 394 boolean found = false; |
| 395 |
| 396 // Look for the same method in the new version considering name and
signature |
| 397 endIdx = nMLen - 1; |
| 398 k = i < nMLen ? i : endIdx; |
| 399 for (j = 0; j < nMLen; j++) { |
| 400 if (oMName == nMNames[k] && oMSig == nMSignatures[k]) { |
| 401 found = true; |
| 402 break; |
| 403 } |
| 404 if (k < endIdx) { |
| 405 k++; |
| 406 } else { |
| 407 k = 0; |
| 408 } |
| 409 } |
| 410 |
| 411 if (found) { |
| 412 nonMatchingNewMethods--; |
| 413 nMMod = nMFlags[k]; |
| 414 if (oMMod != nMMod) { |
| 415 checkMethodModifiers(oMMod, nMMod, i); |
| 416 } |
| 417 |
| 418 // Check if the new method throws more exceptions than the old o
ne |
| 419 if (newClassInfo.checkedExceptions != null && newClassInfo.check
edExceptions[k] != null) { |
| 420 if (oldClassInfo.checkedExceptions == null) { |
| 421 versionsCompatible = false; |
| 422 rf.findReferencingClassesForMethod(oldClassInfo, i); |
| 423 } else if (oldClassInfo.checkedExceptions[i] == null) { |
| 424 versionsCompatible = false; |
| 425 rf.findReferencingClassesForMethod(oldClassInfo, i); |
| 426 } else { |
| 427 String oldExceptions[] = |
| 428 oldClassInfo.checkedExceptions[i]; |
| 429 String newExceptions[] = |
| 430 newClassInfo.checkedExceptions[k]; |
| 431 for (int ei = 0; ei < newExceptions.length; ei++) { |
| 432 String newEx = newExceptions[ei]; |
| 433 found = false; |
| 434 for (int ej = 0; ej < oldExceptions.length; ej++) { |
| 435 if (newEx.equals(oldExceptions[ej])) { |
| 436 found = true; |
| 437 break; |
| 438 } |
| 439 } |
| 440 if (!found) { |
| 441 versionsCompatible = false; |
| 442 rf.findReferencingClassesForMethod(oldClassInfo,
i); |
| 443 break; |
| 444 } |
| 445 } |
| 446 } |
| 447 } |
| 448 } else { // Matching method not found |
| 449 versionsCompatible = false; |
| 450 rf.findReferencingClassesForMethod(oldClassInfo, i); |
| 451 // Deleting a concrete method from an abstract class is a specia
l case |
| 452 if (oldClassInfo.isAbstract() && !Modifier.isAbstract(oMMod)) { |
| 453 rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClas
sInfo, oldClassInfo, i); |
| 454 } |
| 455 } |
| 456 } |
| 457 |
| 458 if (nonMatchingNewMethods > 0) { // There are some methods/constructors
declared in the new version which don't exist in the old one |
| 459 if (!oldClassInfo.isInterface()) { |
| 460 for (i = 0; i < nMLen; i++) { |
| 461 nMMod = nMFlags[i]; |
| 462 if (Modifier.isPrivate(nMMod)) { |
| 463 continue; |
| 464 } |
| 465 String newMName = nMNames[i]; |
| 466 final String newMSig = nMSignatures[i]; |
| 467 final boolean isStatic = Modifier.isStatic(nMMod); |
| 468 |
| 469 boolean found = false; |
| 470 for (j = 0; j < oMLen; j++) { |
| 471 if (newMName.equals(oMNames[j]) && |
| 472 newMSig.equals(oMSignatures[j])) { |
| 473 found = true; |
| 474 break; |
| 475 } |
| 476 } |
| 477 if (found) { |
| 478 continue; // nMName is not an added method |
| 479 } |
| 480 // Check if the new method is a static one that hides an inh
erited static method |
| 481 // Check if the new method overloads an existing (declared o
r inherited) method. Overloading test is rough - |
| 482 // we just check if the number of parameters is the same. No
te that if a new constructor has been added, it |
| 483 // can be treated in the same way, except that we shouldn't
look up "same name methods" for it in superclasses. |
| 484 oldClassInfo.findExistingSameNameMethods(newMName, |
| 485 !newMName.equals("<init>"), false, |
| 486 new ClassInfo.MethodHandler() { |
| 487 |
| 488 void handleMethod(ClassInfo classInfo, int methodIdx) { |
| 489 String otherMSig = |
| 490 classInfo.methodSignatures[methodIdx]; |
| 491 if ((newMSig.equals(otherMSig) && isStatic && |
| 492 classInfo != oldClassInfo) || |
| 493 (newMSig != otherMSig && |
| 494 Utils.sameParamNumber(newMSig, otherMSig)))
{ |
| 495 versionsCompatible = false; |
| 496 rf.findReferencingClassesForMethod(classInfo, me
thodIdx); |
| 497 } |
| 498 } |
| 499 }); |
| 500 |
| 501 if (Modifier.isAbstract(nMMod)) { |
| 502 // An abstract method added to the class. Find any concr
ete subclasses that don't override |
| 503 // or inherit a concrete implementation of this method. |
| 504 versionsCompatible = false; |
| 505 rf.findConcreteSubclassesNotOverridingAbstractMethod(old
ClassInfo, newClassInfo, i); |
| 506 } |
| 507 // Check if there is a method with the same name in some sub
class, such that it now overrides |
| 508 // or overloads the added method. |
| 509 if (subclassesDeclareSameNameMethod(oldClassInfo, newMName))
{ |
| 510 versionsCompatible = false; |
| 511 } |
| 512 } |
| 513 } else { // We are checking an interface. |
| 514 for (i = 0; i < nMLen; i++) { |
| 515 String newMName = nMNames[i]; |
| 516 final String newMSig = nMSignatures[i]; |
| 517 |
| 518 boolean found = false; |
| 519 for (j = 0; j < oMLen; j++) { |
| 520 if (newMName == oMNames[j] && newMSig == oMSignatures[j]
) { |
| 521 found = true; |
| 522 break; |
| 523 } |
| 524 } |
| 525 |
| 526 if (!found) { |
| 527 versionsCompatible = false; |
| 528 |
| 529 // Check if the new method overloads an existing (declar
ed or inherited) method. Overloading test is rough - |
| 530 // we just check if the number of parameters is the same
. |
| 531 oldClassInfo.findExistingSameNameMethods(newMName, true,
true, new ClassInfo.MethodHandler() { |
| 532 |
| 533 void handleMethod(ClassInfo classInfo, int methodIdx
) { |
| 534 String otherMSig = |
| 535 classInfo.methodSignatures[methodIdx]; |
| 536 if (newMSig != otherMSig && |
| 537 Utils.sameParamNumber(newMSig, otherMSig
)) { |
| 538 rf.findReferencingClassesForMethod(classInfo
, methodIdx); |
| 539 } |
| 540 } |
| 541 }); |
| 542 |
| 543 rf.findDirectlyAndOtherwiseImplementingConcreteClasses(o
ldClassInfo); |
| 544 rf.findAbstractSubtypesWithSameNameMethod(oldClassInfo,
newMName, newMSig); |
| 545 break; |
| 546 } |
| 547 } |
| 548 } |
| 549 } |
| 550 } |
| 551 |
| 552 private void checkMethodModifiers(int oMMod, int nMMod, int oldMethodIdx) { |
| 553 if (Modifier.isPrivate(nMMod)) { |
| 554 versionsCompatible = false; |
| 555 rf.findReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
| 556 } else if (Modifier.isPublic(oMMod) && Modifier.isProtected(nMMod)) { |
| 557 versionsCompatible = false; |
| 558 rf.findDiffPackageReferencingClassesForMethod(oldClassInfo, oldMetho
dIdx); |
| 559 } else if ((Modifier.isPublic(oMMod) || Modifier.isProtected(oMMod)) && |
| 560 (!(Modifier.isPublic(nMMod) || Modifier.isProtected(nMMod) || Mo
difier.isPrivate(nMMod)))) { |
| 561 versionsCompatible = false; |
| 562 if (Modifier.isPublic(oMMod)) { |
| 563 rf.findDiffPackageReferencingClassesForMethod(oldClassInfo, oldM
ethodIdx); |
| 564 } else { |
| 565 rf.findDiffPackageAndSubReferencingClassesForMethod(oldClassInfo
, oldMethodIdx); |
| 566 } |
| 567 } else if ((Modifier.isPrivate(oMMod) && !Modifier.isPrivate(nMMod)) || |
| 568 (Modifier.isProtected(oMMod) && Modifier.isPublic(nMMod)) || |
| 569 (!(Modifier.isPublic(oMMod) || Modifier.isProtected(oMMod) || Mo
difier.isPrivate(oMMod)) && |
| 570 (Modifier.isPublic(nMMod) || Modifier.isProtected(nMMod)))) { |
| 571 versionsCompatible = false; |
| 572 rf.findSubclassesReimplementingMethod(oldClassInfo, oldMethodIdx); |
| 573 } |
| 574 |
| 575 if ((!Modifier.isAbstract(oMMod) && Modifier.isAbstract(nMMod)) || |
| 576 (Modifier.isStatic(oMMod) != Modifier.isStatic(nMMod))) { |
| 577 versionsCompatible = false; |
| 578 rf.findReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
| 579 if (!Modifier.isAbstract(oMMod) && Modifier.isAbstract(nMMod)) { |
| 580 rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInf
o, newClassInfo, oldMethodIdx); |
| 581 } |
| 582 } |
| 583 if (!Modifier.isFinal(oMMod) && Modifier.isFinal(nMMod)) { |
| 584 versionsCompatible = false; |
| 585 rf.findSubclassesReimplementingMethod(oldClassInfo, oldMethodIdx); |
| 586 } |
| 587 } |
| 588 |
| 589 /** |
| 590 * Returns true if any subclass(es), direct or indirect, declare a method wi
th name methodName. |
| 591 * For each such occurence, referencing classes are looked up and added to t
he list of affected classes. |
| 592 */ |
| 593 private boolean subclassesDeclareSameNameMethod(ClassInfo oldClassInfo, Stri
ng methodName) { |
| 594 boolean res = false; |
| 595 ClassInfo[] directSubclasses = oldClassInfo.getDirectSubclasses(); |
| 596 for (int i = 0; i < directSubclasses.length; i++) { |
| 597 ClassInfo subclass = directSubclasses[i]; |
| 598 int methNo = subclass.declaresSameNameMethod(methodName); |
| 599 if (methNo >= 0) { |
| 600 rf.addToAffectedClassNames(subclass.name); |
| 601 rf.findReferencingClassesForMethod(subclass, methNo); |
| 602 res = true; |
| 603 } |
| 604 if (subclassesDeclareSameNameMethod(subclass, methodName)) { |
| 605 res = true; |
| 606 } |
| 607 } |
| 608 return res; |
| 609 } |
| 610 } |
OLD | NEW |