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