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 ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); 561 AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); 562 ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); 563 ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables()); 564 if (isProtected()) { 565 data.setValue(base + ".scope", "protected"); 566 } else if (isPublic()) { 567 data.setValue(base + ".scope", "public"); 568 } 569 TagInfo.makeHDF(data, base + ".returns", returnTags()); 570 571 if (mTypeParameters != null) { 572 TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); 573 } 574 575 setFederatedReferences(data, base); 576 } 577 578 public HashSet<String> typeVariables() { 579 HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); 580 ClassInfo cl = containingClass(); 581 while (cl != null) { 582 ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments(); 583 if (types != null) { 584 TypeInfo.typeVariables(types, result); 585 } 586 cl = cl.containingClass(); 587 } 588 return result; 589 } 590 591 @Override 592 public boolean isExecutable() { 593 return true; 594 } 595 596 public ArrayList<ClassInfo> thrownExceptions() { 597 return mThrownExceptions; 598 } 599 600 public String typeArgumentsName(HashSet<String> typeVars) { 601 if (mTypeParameters == null || mTypeParameters.isEmpty()) { 602 return ""; 603 } else { 604 return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); 605 } 606 } 607 608 public boolean isAnnotationElement() { 609 return mIsAnnotationElement; 610 } 611 612 public AnnotationValueInfo defaultAnnotationElementValue() { 613 return mDefaultAnnotationElementValue; 614 } 615 616 public void setVarargs(boolean set) { 617 mIsVarargs = set; 618 } 619 620 public boolean isVarArgs() { 621 return mIsVarargs; 622 } 623 624 @Override 625 public String toString() { 626 return this.name(); 627 } 628 629 public void setReason(String reason) { 630 mReasonOpened = reason; 631 } 632 633 public String getReason() { 634 return mReasonOpened; 635 } 636 637 public void addException(String exec) { 638 ClassInfo exceptionClass = new ClassInfo(exec); 639 640 mThrownExceptions.add(exceptionClass); 641 } 642 643 public void addParameter(ParameterInfo p) { 644 // Name information 645 if (mParameters == null) { 646 mParameters = new ArrayList<ParameterInfo>(); 647 } 648 649 mParameters.add(p); 650 } 651 652 private String mFlatSignature; 653 private MethodInfo mOverriddenMethod; 654 private TypeInfo mReturnType; 655 private boolean mIsAnnotationElement; 656 private boolean mIsAbstract; 657 private boolean mIsSynchronized; 658 private boolean mIsNative; 659 private boolean mIsVarargs; 660 private boolean mDeprecatedKnown; 661 private boolean mIsDeprecated; 662 private ArrayList<ParameterInfo> mParameters; 663 private ArrayList<ClassInfo> mThrownExceptions; 664 private String[] mParamStrings; 665 private ThrowsTagInfo[] mThrowsTags; 666 private ParamTagInfo[] mParamTags; 667 private ArrayList<TypeInfo> mTypeParameters; 668 private AnnotationValueInfo mDefaultAnnotationElementValue; 669 private String mReasonOpened; 670 private ArrayList<Resolution> mResolutions; 671 672 // TODO: merge with droiddoc version (above) 673 public String qualifiedName() { 674 String parentQName = (containingClass() != null) 675 ? (containingClass().qualifiedName() + ".") : ""; 676 return parentQName + name(); 677 } 678 679 @Override 680 public String signature() { 681 if (mSignature == null) { 682 StringBuilder params = new StringBuilder("("); 683 for (ParameterInfo pInfo : mParameters) { 684 if (params.length() > 1) { 685 params.append(", "); 686 } 687 params.append(pInfo.type().fullName()); 688 } 689 690 params.append(")"); 691 mSignature = params.toString(); 692 } 693 return mSignature; 694 } 695 696 public boolean matches(MethodInfo other) { 697 return prettySignature().equals(other.prettySignature()); 698 } 699 700 public boolean throwsException(ClassInfo exception) { 701 for (ClassInfo e : mThrownExceptions) { 702 if (e.qualifiedName().equals(exception.qualifiedName())) { 703 return true; 704 } 705 } 706 return false; 707 } 708 709 public boolean isConsistent(MethodInfo mInfo) { 710 boolean consistent = true; 711 if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) { 712 consistent = false; 713 Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName() 714 + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType); 715 } 716 717 if (mIsAbstract != mInfo.mIsAbstract) { 718 consistent = false; 719 Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName() 720 + " has changed 'abstract' qualifier"); 721 } 722 723 if (mIsNative != mInfo.mIsNative) { 724 consistent = false; 725 Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName() 726 + " has changed 'native' qualifier"); 727 } 728 729 if (mIsFinal != mInfo.mIsFinal) { 730 // Compiler-generated methods vary in their 'final' qual between versions of 731 // the compiler, so this check needs to be quite narrow. A change in 'final' 732 // status of a method is only relevant if (a) the method is not declared 'static' 733 // and (b) the method's class is not itself 'final'. 734 if (!mIsStatic) { 735 if ((containingClass() == null) || (!containingClass().isFinal())) { 736 consistent = false; 737 Errors.error(Errors.CHANGED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName() 738 + " has changed 'final' qualifier"); 739 } 740 } 741 } 742 743 if (mIsStatic != mInfo.mIsStatic) { 744 consistent = false; 745 Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName() 746 + " has changed 'static' qualifier"); 747 } 748 749 if (!scope().equals(mInfo.scope())) { 750 consistent = false; 751 Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName() 752 + " changed scope from " + scope() + " to " + mInfo.scope()); 753 } 754 755 if (!isDeprecated() == mInfo.isDeprecated()) { 756 Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName() 757 + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated()); 758 consistent = false; 759 } 760 761 // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break " 762 // "compatibility with existing binaries." 763 /* 764 if (mIsSynchronized != mInfo.mIsSynchronized) { 765 Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName() 766 + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to " 767 + mInfo.mIsSynchronized); 768 consistent = false; 769 } 770 */ 771 772 for (ClassInfo exception : thrownExceptions()) { 773 if (!mInfo.throwsException(exception)) { 774 // exclude 'throws' changes to finalize() overrides with no arguments 775 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 776 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 777 + " no longer throws exception " + exception.qualifiedName()); 778 consistent = false; 779 } 780 } 781 } 782 783 for (ClassInfo exec : mInfo.thrownExceptions()) { 784 // exclude 'throws' changes to finalize() overrides with no arguments 785 if (!throwsException(exec)) { 786 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 787 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 788 + " added thrown exception " + exec.qualifiedName()); 789 consistent = false; 790 } 791 } 792 } 793 794 return consistent; 795 } 796 797 public void printResolutions() { 798 if (mResolutions == null || mResolutions.isEmpty()) { 799 return; 800 } 801 802 System.out.println("Resolutions for Method " + mName + mFlatSignature + ":"); 803 804 for (Resolution r : mResolutions) { 805 System.out.println(r); 806 } 807 } 808 809 public void addResolution(Resolution resolution) { 810 if (mResolutions == null) { 811 mResolutions = new ArrayList<Resolution>(); 812 } 813 814 mResolutions.add(resolution); 815 } 816 817 public boolean resolveResolutions() { 818 ArrayList<Resolution> resolutions = mResolutions; 819 mResolutions = new ArrayList<Resolution>(); 820 821 boolean allResolved = true; 822 for (Resolution resolution : resolutions) { 823 StringBuilder qualifiedClassName = new StringBuilder(); 824 InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName, 825 resolution.getInfoBuilder()); 826 827 // if we still couldn't resolve it, save it for the next pass 828 if ("".equals(qualifiedClassName.toString())) { 829 mResolutions.add(resolution); 830 allResolved = false; 831 } else if ("thrownException".equals(resolution.getVariable())) { 832 mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString())); 833 } 834 } 835 836 return allResolved; 837 } 838 } 839