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