1 /* 2 * Copyright (C) 2010 Google Inc. 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.google.doclava; 18 19 import com.google.clearsilver.jsilver.data.Data; 20 import com.google.doclava.apicheck.AbstractMethodInfo; 21 22 import java.util.*; 23 24 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable { 25 public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() { 26 public int compare(MethodInfo a, MethodInfo b) { 27 return a.name().compareTo(b.name()); 28 } 29 }; 30 31 private class InlineTags implements InheritedTags { 32 public TagInfo[] tags() { 33 return comment().tags(); 34 } 35 36 public InheritedTags inherited() { 37 MethodInfo m = findOverriddenMethod(name(), signature()); 38 if (m != null) { 39 return m.inlineTags(); 40 } else { 41 return null; 42 } 43 } 44 } 45 46 private static void addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) { 47 for (ClassInfo i : ifaces) { 48 queue.add(i); 49 } 50 for (ClassInfo i : ifaces) { 51 addInterfaces(i.interfaces(), queue); 52 } 53 } 54 55 // first looks for a superclass, and then does a breadth first search to 56 // find the least far away match 57 public MethodInfo findOverriddenMethod(String name, String signature) { 58 if (mReturnType == null) { 59 // ctor 60 return null; 61 } 62 if (mOverriddenMethod != null) { 63 return mOverriddenMethod; 64 } 65 66 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 67 addInterfaces(containingClass().interfaces(), queue); 68 for (ClassInfo iface : queue) { 69 for (MethodInfo me : iface.methods()) { 70 if (me.name().equals(name) && me.signature().equals(signature) 71 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) { 72 return me; 73 } 74 } 75 } 76 return null; 77 } 78 79 private static void addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) { 80 for (ClassInfo i : ifaces) { 81 queue.add(i); 82 if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) { 83 queue.add(i.superclass()); 84 } 85 } 86 for (ClassInfo i : ifaces) { 87 addInterfaces(i.realInterfaces(), queue); 88 } 89 } 90 91 public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) { 92 if (mReturnType == null) { 93 // ctor 94 return null; 95 } 96 if (mOverriddenMethod != null) { 97 return mOverriddenMethod; 98 } 99 100 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 101 if (containingClass().realSuperclass() != null 102 && containingClass().realSuperclass().isAbstract()) { 103 queue.add(containingClass()); 104 } 105 addInterfaces(containingClass().realInterfaces(), queue); 106 for (ClassInfo iface : queue) { 107 for (MethodInfo me : iface.methods()) { 108 if (me.name().equals(name) && me.signature().equals(signature) 109 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0 110 && notStrippable.contains(me.containingClass())) { 111 return me; 112 } 113 } 114 } 115 return null; 116 } 117 118 public MethodInfo findSuperclassImplementation(HashSet notStrippable) { 119 if (mReturnType == null) { 120 // ctor 121 return null; 122 } 123 if (mOverriddenMethod != null) { 124 // Even if we're told outright that this was the overridden method, we want to 125 // be conservative and ignore mismatches of parameter types -- they arise from 126 // extending generic specializations, and we want to consider the derived-class 127 // method to be a non-override. 128 if (this.signature().equals(mOverriddenMethod.signature())) { 129 return mOverriddenMethod; 130 } 131 } 132 133 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 134 if (containingClass().realSuperclass() != null 135 && containingClass().realSuperclass().isAbstract()) { 136 queue.add(containingClass()); 137 } 138 addInterfaces(containingClass().realInterfaces(), queue); 139 for (ClassInfo iface : queue) { 140 for (MethodInfo me : iface.methods()) { 141 if (me.name().equals(this.name()) && me.signature().equals(this.signature()) 142 && notStrippable.contains(me.containingClass())) { 143 return me; 144 } 145 } 146 } 147 return null; 148 } 149 150 public ClassInfo findRealOverriddenClass(String name, String signature) { 151 if (mReturnType == null) { 152 // ctor 153 return null; 154 } 155 if (mOverriddenMethod != null) { 156 return mOverriddenMethod.mRealContainingClass; 157 } 158 159 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 160 if (containingClass().realSuperclass() != null 161 && containingClass().realSuperclass().isAbstract()) { 162 queue.add(containingClass()); 163 } 164 addInterfaces(containingClass().realInterfaces(), queue); 165 for (ClassInfo iface : queue) { 166 for (MethodInfo me : iface.methods()) { 167 if (me.name().equals(name) && me.signature().equals(signature) 168 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) { 169 return iface; 170 } 171 } 172 } 173 return null; 174 } 175 176 private class FirstSentenceTags implements InheritedTags { 177 public TagInfo[] tags() { 178 return comment().briefTags(); 179 } 180 181 public InheritedTags inherited() { 182 MethodInfo m = findOverriddenMethod(name(), signature()); 183 if (m != null) { 184 return m.firstSentenceTags(); 185 } else { 186 return null; 187 } 188 } 189 } 190 191 private class ReturnTags implements InheritedTags { 192 public TagInfo[] tags() { 193 return comment().returnTags(); 194 } 195 196 public InheritedTags inherited() { 197 MethodInfo m = findOverriddenMethod(name(), signature()); 198 if (m != null) { 199 return m.returnTags(); 200 } else { 201 return null; 202 } 203 } 204 } 205 206 public boolean isDeprecated() { 207 boolean deprecated = false; 208 if (!mDeprecatedKnown) { 209 boolean commentDeprecated = comment().isDeprecated(); 210 boolean annotationDeprecated = false; 211 for (AnnotationInstanceInfo annotation : annotations()) { 212 if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { 213 annotationDeprecated = true; 214 break; 215 } 216 } 217 218 if (commentDeprecated != annotationDeprecated) { 219 Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Method " 220 + mContainingClass.qualifiedName() + "." + name() 221 + ": @Deprecated annotation and @deprecated doc tag do not match"); 222 } 223 224 mIsDeprecated = commentDeprecated | annotationDeprecated; 225 mDeprecatedKnown = true; 226 } 227 return mIsDeprecated; 228 } 229 230 public void setDeprecated(boolean deprecated) { 231 mDeprecatedKnown = true; 232 mIsDeprecated = deprecated; 233 } 234 235 public ArrayList<TypeInfo> getTypeParameters() { 236 return mTypeParameters; 237 } 238 239 public MethodInfo cloneForClass(ClassInfo newContainingClass) { 240 MethodInfo result = 241 new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(), 242 newContainingClass, realContainingClass(), isPublic(), isProtected(), 243 isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract, 244 mIsSynchronized, mIsNative, mIsAnnotationElement, kind(), mFlatSignature, 245 mOverriddenMethod, mReturnType, mParameters, mThrownExceptions, position(), 246 annotations()); 247 result.init(mDefaultAnnotationElementValue); 248 return result; 249 } 250 251 public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name, 252 String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, 253 boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, 254 boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized, 255 boolean isNative, boolean isAnnotationElement, String kind, String flatSignature, 256 MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters, 257 ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position, 258 ArrayList<AnnotationInstanceInfo> annotations) { 259 // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match 260 // the Java5-emitted base API description. 261 super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic, 262 isProtected, isPackagePrivate, isPrivate, 263 ((name.equals("values") && containingClass.isEnum()) ? true : isFinal), 264 isStatic, isSynthetic, kind, position, annotations); 265 266 // The underlying MethodDoc for an interface's declared methods winds up being marked 267 // non-abstract. Correct that here by looking at the immediate-parent class, and marking 268 // this method abstract if it is an unimplemented interface method. 269 if (containingClass.isInterface()) { 270 isAbstract = true; 271 } 272 273 mReasonOpened = "0:0"; 274 mIsAnnotationElement = isAnnotationElement; 275 mTypeParameters = typeParameters; 276 mIsAbstract = isAbstract; 277 mIsSynchronized = isSynchronized; 278 mIsNative = isNative; 279 mFlatSignature = flatSignature; 280 mOverriddenMethod = overriddenMethod; 281 mReturnType = returnType; 282 mParameters = parameters; 283 mThrownExceptions = thrownExceptions; 284 } 285 286 public void init(AnnotationValueInfo defaultAnnotationElementValue) { 287 mDefaultAnnotationElementValue = defaultAnnotationElementValue; 288 } 289 290 public boolean isAbstract() { 291 return mIsAbstract; 292 } 293 294 public boolean isSynchronized() { 295 return mIsSynchronized; 296 } 297 298 public boolean isNative() { 299 return mIsNative; 300 } 301 302 public String flatSignature() { 303 return mFlatSignature; 304 } 305 306 public InheritedTags inlineTags() { 307 return new InlineTags(); 308 } 309 310 public InheritedTags firstSentenceTags() { 311 return new FirstSentenceTags(); 312 } 313 314 public InheritedTags returnTags() { 315 return new ReturnTags(); 316 } 317 318 public TypeInfo returnType() { 319 return mReturnType; 320 } 321 322 public String prettySignature() { 323 return name() + prettyParameters(); 324 } 325 326 /** 327 * Returns a printable version of the parameters of this method's signature. 328 */ 329 public String prettyParameters() { 330 StringBuilder params = new StringBuilder("("); 331 for (ParameterInfo pInfo : mParameters) { 332 if (params.length() > 1) { 333 params.append(","); 334 } 335 params.append(pInfo.type().simpleTypeName()); 336 } 337 338 params.append(")"); 339 return params.toString(); 340 } 341 342 /** 343 * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}. 344 */ 345 public String getHashableName() { 346 StringBuilder result = new StringBuilder(); 347 result.append(name()); 348 349 if (mParameters == null) { 350 return result.toString(); 351 } 352 353 int i = 0; 354 for (ParameterInfo param : mParameters) { 355 result.append(":"); 356 if (i == (mParameters.size()-1) && isVarArgs()) { 357 // TODO: note that this does not attempt to handle hypothetical 358 // vararg methods whose last parameter is a list of arrays, e.g. 359 // "Object[]...". 360 result.append(param.type().fullNameNoDimension(typeVariables())).append("..."); 361 } else { 362 result.append(param.type().fullName(typeVariables())); 363 } 364 i++; 365 } 366 return result.toString(); 367 } 368 369 private boolean inList(ClassInfo item, ThrowsTagInfo[] list) { 370 int len = list.length; 371 String qn = item.qualifiedName(); 372 for (int i = 0; i < len; i++) { 373 ClassInfo ex = list[i].exception(); 374 if (ex != null && ex.qualifiedName().equals(qn)) { 375 return true; 376 } 377 } 378 return false; 379 } 380 381 public ThrowsTagInfo[] throwsTags() { 382 if (mThrowsTags == null) { 383 ThrowsTagInfo[] documented = comment().throwsTags(); 384 ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); 385 386 int len = documented.length; 387 for (int i = 0; i < len; i++) { 388 rv.add(documented[i]); 389 } 390 391 for (ClassInfo cl : mThrownExceptions) { 392 if (documented == null || !inList(cl, documented)) { 393 rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "", 394 containingClass(), position())); 395 } 396 } 397 mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]); 398 } 399 return mThrowsTags; 400 } 401 402 private static int indexOfParam(String name, String[] list) { 403 final int N = list.length; 404 for (int i = 0; i < N; i++) { 405 if (name.equals(list[i])) { 406 return i; 407 } 408 } 409 return -1; 410 } 411 412 public ParamTagInfo[] paramTags() { 413 if (mParamTags == null) { 414 final int N = mParameters.size(); 415 416 String[] names = new String[N]; 417 String[] comments = new String[N]; 418 SourcePositionInfo[] positions = new SourcePositionInfo[N]; 419 420 // get the right names so we can handle our names being different from 421 // our parent's names. 422 int i = 0; 423 for (ParameterInfo param : mParameters) { 424 names[i] = param.name(); 425 comments[i] = ""; 426 positions[i] = param.position(); 427 i++; 428 } 429 430 // gather our comments, and complain about misnamed @param tags 431 for (ParamTagInfo tag : comment().paramTags()) { 432 int index = indexOfParam(tag.parameterName(), names); 433 if (index >= 0) { 434 comments[index] = tag.parameterComment(); 435 positions[index] = tag.position(); 436 } else { 437 Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), 438 "@param tag with name that doesn't match the parameter list: '" + tag.parameterName() 439 + "'"); 440 } 441 } 442 443 // get our parent's tags to fill in the blanks 444 MethodInfo overridden = this.findOverriddenMethod(name(), signature()); 445 if (overridden != null) { 446 ParamTagInfo[] maternal = overridden.paramTags(); 447 for (i = 0; i < N; i++) { 448 if (comments[i].equals("")) { 449 comments[i] = maternal[i].parameterComment(); 450 positions[i] = maternal[i].position(); 451 } 452 } 453 } 454 455 // construct the results, and cache them for next time 456 mParamTags = new ParamTagInfo[N]; 457 for (i = 0; i < N; i++) { 458 mParamTags[i] = 459 new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], parent(), 460 positions[i]); 461 462 // while we're here, if we find any parameters that are still undocumented at this 463 // point, complain. (this warning is off by default, because it's really, really 464 // common; but, it's good to be able to enforce it) 465 if (comments[i].equals("")) { 466 Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], "Undocumented parameter '" 467 + names[i] + "' on method '" + name() + "'"); 468 } 469 } 470 } 471 return mParamTags; 472 } 473 474 public SeeTagInfo[] seeTags() { 475 SeeTagInfo[] result = comment().seeTags(); 476 if (result == null) { 477 if (mOverriddenMethod != null) { 478 result = mOverriddenMethod.seeTags(); 479 } 480 } 481 return result; 482 } 483 484 public TagInfo[] deprecatedTags() { 485 TagInfo[] result = comment().deprecatedTags(); 486 if (result.length == 0) { 487 if (comment().undeprecateTags().length == 0) { 488 if (mOverriddenMethod != null) { 489 result = mOverriddenMethod.deprecatedTags(); 490 } 491 } 492 } 493 return result; 494 } 495 496 public ArrayList<ParameterInfo> parameters() { 497 return mParameters; 498 } 499 500 501 public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) { 502 if (mParamStrings == null) { 503 if (mParameters.size() != params.length) { 504 return false; 505 } 506 int i = 0; 507 for (ParameterInfo mine : mParameters) { 508 if (!mine.matchesDimension(dimensions[i], varargs)) { 509 return false; 510 } 511 TypeInfo myType = mine.type(); 512 String qualifiedName = myType.qualifiedTypeName(); 513 String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName(); 514 String s = params[i]; 515 int slen = s.length(); 516 int qnlen = qualifiedName.length(); 517 518 // Check for a matching generic name or best known type 519 if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) { 520 return false; 521 } 522 i++; 523 } 524 } 525 return true; 526 } 527 528 /** 529 * Checks to see if a parameter from a method signature is 530 * compatible with a parameter given in a {@code @link} tag. 531 */ 532 private boolean matchesType(String signatureParam, String callerParam) { 533 int signatureLength = signatureParam.length(); 534 int callerLength = callerParam.length(); 535 return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength 536 && signatureParam.charAt(signatureLength - callerLength - 1) == '.' 537 && signatureParam.endsWith(callerParam)))); 538 } 539 540 public void makeHDF(Data data, String base) { 541 data.setValue(base + ".kind", kind()); 542 data.setValue(base + ".name", name()); 543 data.setValue(base + ".href", htmlPage()); 544 data.setValue(base + ".anchor", anchor()); 545 546 if (mReturnType != null) { 547 returnType().makeHDF(data, base + ".returnType", false, typeVariables()); 548 data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); 549 } 550 551 data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); 552 data.setValue(base + ".final", isFinal() ? "final" : ""); 553 data.setValue(base + ".static", isStatic() ? "static" : ""); 554 555 TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); 556 TagInfo.makeHDF(data, base + ".descr", inlineTags()); 557 TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); 558 TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); 559 data.setValue(base + ".since", getSince()); 560 if (isDeprecated()) { 561 data.setValue(base + ".deprecatedsince", getDeprecatedSince()); 562 } 563 ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); 564 AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); 565 ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); 566 ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables()); 567 if (isProtected()) { 568 data.setValue(base + ".scope", "protected"); 569 } else if (isPublic()) { 570 data.setValue(base + ".scope", "public"); 571 } 572 TagInfo.makeHDF(data, base + ".returns", returnTags()); 573 574 if (mTypeParameters != null) { 575 TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); 576 } 577 578 setFederatedReferences(data, base); 579 } 580 581 public HashSet<String> typeVariables() { 582 HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); 583 ClassInfo cl = containingClass(); 584 while (cl != null) { 585 ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments(); 586 if (types != null) { 587 TypeInfo.typeVariables(types, result); 588 } 589 cl = cl.containingClass(); 590 } 591 return result; 592 } 593 594 @Override 595 public boolean isExecutable() { 596 return true; 597 } 598 599 public ArrayList<ClassInfo> thrownExceptions() { 600 return mThrownExceptions; 601 } 602 603 public String typeArgumentsName(HashSet<String> typeVars) { 604 if (mTypeParameters == null || mTypeParameters.isEmpty()) { 605 return ""; 606 } else { 607 return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); 608 } 609 } 610 611 public boolean isAnnotationElement() { 612 return mIsAnnotationElement; 613 } 614 615 public AnnotationValueInfo defaultAnnotationElementValue() { 616 return mDefaultAnnotationElementValue; 617 } 618 619 public void setVarargs(boolean set) { 620 mIsVarargs = set; 621 } 622 623 public boolean isVarArgs() { 624 return mIsVarargs; 625 } 626 627 public boolean isEffectivelyFinal() { 628 if (mIsFinal) { 629 return true; 630 } 631 ClassInfo containingClass = containingClass(); 632 if (containingClass != null && containingClass.isEffectivelyFinal()) { 633 return true; 634 } 635 return false; 636 } 637 638 @Override 639 public String toString() { 640 return this.name(); 641 } 642 643 public void setReason(String reason) { 644 mReasonOpened = reason; 645 } 646 647 public String getReason() { 648 return mReasonOpened; 649 } 650 651 public void addException(String exec) { 652 ClassInfo exceptionClass = new ClassInfo(exec); 653 654 mThrownExceptions.add(exceptionClass); 655 } 656 657 public void addParameter(ParameterInfo p) { 658 // Name information 659 if (mParameters == null) { 660 mParameters = new ArrayList<ParameterInfo>(); 661 } 662 663 mParameters.add(p); 664 } 665 666 private String mFlatSignature; 667 private MethodInfo mOverriddenMethod; 668 private TypeInfo mReturnType; 669 private boolean mIsAnnotationElement; 670 private boolean mIsAbstract; 671 private boolean mIsSynchronized; 672 private boolean mIsNative; 673 private boolean mIsVarargs; 674 private boolean mDeprecatedKnown; 675 private boolean mIsDeprecated; 676 private ArrayList<ParameterInfo> mParameters; 677 private ArrayList<ClassInfo> mThrownExceptions; 678 private String[] mParamStrings; 679 private ThrowsTagInfo[] mThrowsTags; 680 private ParamTagInfo[] mParamTags; 681 private ArrayList<TypeInfo> mTypeParameters; 682 private AnnotationValueInfo mDefaultAnnotationElementValue; 683 private String mReasonOpened; 684 private ArrayList<Resolution> mResolutions; 685 686 // TODO: merge with droiddoc version (above) 687 public String qualifiedName() { 688 String parentQName = (containingClass() != null) 689 ? (containingClass().qualifiedName() + ".") : ""; 690 return parentQName + name(); 691 } 692 693 @Override 694 public String signature() { 695 if (mSignature == null) { 696 StringBuilder params = new StringBuilder("("); 697 for (ParameterInfo pInfo : mParameters) { 698 if (params.length() > 1) { 699 params.append(", "); 700 } 701 params.append(pInfo.type().fullName()); 702 } 703 704 params.append(")"); 705 mSignature = params.toString(); 706 } 707 return mSignature; 708 } 709 710 public boolean matches(MethodInfo other) { 711 return prettySignature().equals(other.prettySignature()); 712 } 713 714 public boolean throwsException(ClassInfo exception) { 715 for (ClassInfo e : mThrownExceptions) { 716 if (e.qualifiedName().equals(exception.qualifiedName())) { 717 return true; 718 } 719 } 720 return false; 721 } 722 723 public boolean isConsistent(MethodInfo mInfo) { 724 boolean consistent = true; 725 if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) { 726 consistent = false; 727 Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName() 728 + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType); 729 } 730 731 if (mIsAbstract != mInfo.mIsAbstract) { 732 consistent = false; 733 Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName() 734 + " has changed 'abstract' qualifier"); 735 } 736 737 if (mIsNative != mInfo.mIsNative) { 738 consistent = false; 739 Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName() 740 + " has changed 'native' qualifier"); 741 } 742 743 if (!mIsStatic) { 744 // Compiler-generated methods vary in their 'final' qualifier between versions of 745 // the compiler, so this check needs to be quite narrow. A change in 'final' 746 // status of a method is only relevant if (a) the method is not declared 'static' 747 // and (b) the method is not already inferred to be 'final' by virtue of its class. 748 if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) { 749 consistent = false; 750 Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName() 751 + " has added 'final' qualifier"); 752 } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) { 753 consistent = false; 754 Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName() 755 + " has removed 'final' qualifier"); 756 } 757 } 758 759 if (mIsStatic != mInfo.mIsStatic) { 760 consistent = false; 761 Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName() 762 + " has changed 'static' qualifier"); 763 } 764 765 if (!scope().equals(mInfo.scope())) { 766 consistent = false; 767 Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName() 768 + " changed scope from " + scope() + " to " + mInfo.scope()); 769 } 770 771 if (!isDeprecated() == mInfo.isDeprecated()) { 772 Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName() 773 + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated()); 774 consistent = false; 775 } 776 777 // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break " 778 // "compatibility with existing binaries." 779 /* 780 if (mIsSynchronized != mInfo.mIsSynchronized) { 781 Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName() 782 + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to " 783 + mInfo.mIsSynchronized); 784 consistent = false; 785 } 786 */ 787 788 for (ClassInfo exception : thrownExceptions()) { 789 if (!mInfo.throwsException(exception)) { 790 // exclude 'throws' changes to finalize() overrides with no arguments 791 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 792 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 793 + " no longer throws exception " + exception.qualifiedName()); 794 consistent = false; 795 } 796 } 797 } 798 799 for (ClassInfo exec : mInfo.thrownExceptions()) { 800 // exclude 'throws' changes to finalize() overrides with no arguments 801 if (!throwsException(exec)) { 802 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 803 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 804 + " added thrown exception " + exec.qualifiedName()); 805 consistent = false; 806 } 807 } 808 } 809 810 return consistent; 811 } 812 813 public void printResolutions() { 814 if (mResolutions == null || mResolutions.isEmpty()) { 815 return; 816 } 817 818 System.out.println("Resolutions for Method " + mName + mFlatSignature + ":"); 819 820 for (Resolution r : mResolutions) { 821 System.out.println(r); 822 } 823 } 824 825 public void addResolution(Resolution resolution) { 826 if (mResolutions == null) { 827 mResolutions = new ArrayList<Resolution>(); 828 } 829 830 mResolutions.add(resolution); 831 } 832 833 public boolean resolveResolutions() { 834 ArrayList<Resolution> resolutions = mResolutions; 835 mResolutions = new ArrayList<Resolution>(); 836 837 boolean allResolved = true; 838 for (Resolution resolution : resolutions) { 839 StringBuilder qualifiedClassName = new StringBuilder(); 840 InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName, 841 resolution.getInfoBuilder()); 842 843 // if we still couldn't resolve it, save it for the next pass 844 if ("".equals(qualifiedClassName.toString())) { 845 mResolutions.add(resolution); 846 allResolved = false; 847 } else if ("thrownException".equals(resolution.getVariable())) { 848 mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString())); 849 } 850 } 851 852 return allResolved; 853 } 854 } 855