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 /** 241 * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the 242 * typeArgumentMapping to the parameters and return types. 243 */ 244 public MethodInfo cloneForClass(ClassInfo newContainingClass, 245 Map<String, TypeInfo> typeArgumentMapping) { 246 TypeInfo returnType = mReturnType.getTypeWithArguments(typeArgumentMapping); 247 ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>(); 248 for (ParameterInfo pi : mParameters) { 249 parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping)); 250 } 251 MethodInfo result = 252 new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(), 253 newContainingClass, realContainingClass(), isPublic(), isProtected(), 254 isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract, 255 mIsSynchronized, mIsNative, mIsAnnotationElement, kind(), mFlatSignature, 256 mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(), 257 annotations()); 258 result.init(mDefaultAnnotationElementValue); 259 return result; 260 } 261 262 public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name, 263 String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, 264 boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, 265 boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized, 266 boolean isNative, boolean isAnnotationElement, String kind, String flatSignature, 267 MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters, 268 ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position, 269 ArrayList<AnnotationInstanceInfo> annotations) { 270 // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match 271 // the Java5-emitted base API description. 272 super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic, 273 isProtected, isPackagePrivate, isPrivate, 274 ((name.equals("values") && containingClass.isEnum()) ? true : isFinal), 275 isStatic, isSynthetic, kind, position, annotations); 276 277 // The underlying MethodDoc for an interface's declared methods winds up being marked 278 // non-abstract. Correct that here by looking at the immediate-parent class, and marking 279 // this method abstract if it is an unimplemented interface method. 280 if (containingClass.isInterface()) { 281 isAbstract = true; 282 } 283 284 mReasonOpened = "0:0"; 285 mIsAnnotationElement = isAnnotationElement; 286 mTypeParameters = typeParameters; 287 mIsAbstract = isAbstract; 288 mIsSynchronized = isSynchronized; 289 mIsNative = isNative; 290 mFlatSignature = flatSignature; 291 mOverriddenMethod = overriddenMethod; 292 mReturnType = returnType; 293 mParameters = parameters; 294 mThrownExceptions = thrownExceptions; 295 } 296 297 public void init(AnnotationValueInfo defaultAnnotationElementValue) { 298 mDefaultAnnotationElementValue = defaultAnnotationElementValue; 299 } 300 301 public boolean isAbstract() { 302 return mIsAbstract; 303 } 304 305 public boolean isSynchronized() { 306 return mIsSynchronized; 307 } 308 309 public boolean isNative() { 310 return mIsNative; 311 } 312 313 public String flatSignature() { 314 return mFlatSignature; 315 } 316 317 public InheritedTags inlineTags() { 318 return new InlineTags(); 319 } 320 321 public InheritedTags firstSentenceTags() { 322 return new FirstSentenceTags(); 323 } 324 325 public InheritedTags returnTags() { 326 return new ReturnTags(); 327 } 328 329 public TypeInfo returnType() { 330 return mReturnType; 331 } 332 333 public String prettySignature() { 334 return name() + prettyParameters(); 335 } 336 337 /** 338 * Returns a printable version of the parameters of this method's signature. 339 */ 340 public String prettyParameters() { 341 StringBuilder params = new StringBuilder("("); 342 for (ParameterInfo pInfo : mParameters) { 343 if (params.length() > 1) { 344 params.append(","); 345 } 346 params.append(pInfo.type().simpleTypeName()); 347 } 348 349 params.append(")"); 350 return params.toString(); 351 } 352 353 /** 354 * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}. 355 */ 356 public String getHashableName() { 357 StringBuilder result = new StringBuilder(); 358 result.append(name()); 359 360 if (mParameters == null) { 361 return result.toString(); 362 } 363 364 int i = 0; 365 for (ParameterInfo param : mParameters) { 366 result.append(":"); 367 if (i == (mParameters.size()-1) && isVarArgs()) { 368 // TODO: note that this does not attempt to handle hypothetical 369 // vararg methods whose last parameter is a list of arrays, e.g. 370 // "Object[]...". 371 result.append(param.type().fullNameNoDimension(typeVariables())).append("..."); 372 } else { 373 result.append(param.type().fullName(typeVariables())); 374 } 375 i++; 376 } 377 return result.toString(); 378 } 379 380 private boolean inList(ClassInfo item, ThrowsTagInfo[] list) { 381 int len = list.length; 382 String qn = item.qualifiedName(); 383 for (int i = 0; i < len; i++) { 384 ClassInfo ex = list[i].exception(); 385 if (ex != null && ex.qualifiedName().equals(qn)) { 386 return true; 387 } 388 } 389 return false; 390 } 391 392 public ThrowsTagInfo[] throwsTags() { 393 if (mThrowsTags == null) { 394 ThrowsTagInfo[] documented = comment().throwsTags(); 395 ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); 396 397 int len = documented.length; 398 for (int i = 0; i < len; i++) { 399 rv.add(documented[i]); 400 } 401 402 for (ClassInfo cl : mThrownExceptions) { 403 if (documented == null || !inList(cl, documented)) { 404 rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "", 405 containingClass(), position())); 406 } 407 } 408 mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]); 409 } 410 return mThrowsTags; 411 } 412 413 private static int indexOfParam(String name, String[] list) { 414 final int N = list.length; 415 for (int i = 0; i < N; i++) { 416 if (name.equals(list[i])) { 417 return i; 418 } 419 } 420 return -1; 421 } 422 423 public ParamTagInfo[] paramTags() { 424 if (mParamTags == null) { 425 final int N = mParameters.size(); 426 427 String[] names = new String[N]; 428 String[] comments = new String[N]; 429 SourcePositionInfo[] positions = new SourcePositionInfo[N]; 430 431 // get the right names so we can handle our names being different from 432 // our parent's names. 433 int i = 0; 434 for (ParameterInfo param : mParameters) { 435 names[i] = param.name(); 436 comments[i] = ""; 437 positions[i] = param.position(); 438 i++; 439 } 440 441 // gather our comments, and complain about misnamed @param tags 442 for (ParamTagInfo tag : comment().paramTags()) { 443 int index = indexOfParam(tag.parameterName(), names); 444 if (index >= 0) { 445 comments[index] = tag.parameterComment(); 446 positions[index] = tag.position(); 447 } else { 448 Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), 449 "@param tag with name that doesn't match the parameter list: '" + tag.parameterName() 450 + "'"); 451 } 452 } 453 454 // get our parent's tags to fill in the blanks 455 MethodInfo overridden = this.findOverriddenMethod(name(), signature()); 456 if (overridden != null) { 457 ParamTagInfo[] maternal = overridden.paramTags(); 458 for (i = 0; i < N; i++) { 459 if (comments[i].equals("")) { 460 comments[i] = maternal[i].parameterComment(); 461 positions[i] = maternal[i].position(); 462 } 463 } 464 } 465 466 // construct the results, and cache them for next time 467 mParamTags = new ParamTagInfo[N]; 468 for (i = 0; i < N; i++) { 469 mParamTags[i] = 470 new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], parent(), 471 positions[i]); 472 473 // while we're here, if we find any parameters that are still undocumented at this 474 // point, complain. (this warning is off by default, because it's really, really 475 // common; but, it's good to be able to enforce it) 476 if (comments[i].equals("")) { 477 Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], "Undocumented parameter '" 478 + names[i] + "' on method '" + name() + "'"); 479 } 480 } 481 } 482 return mParamTags; 483 } 484 485 public SeeTagInfo[] seeTags() { 486 SeeTagInfo[] result = comment().seeTags(); 487 if (result == null) { 488 if (mOverriddenMethod != null) { 489 result = mOverriddenMethod.seeTags(); 490 } 491 } 492 return result; 493 } 494 495 public TagInfo[] deprecatedTags() { 496 TagInfo[] result = comment().deprecatedTags(); 497 if (result.length == 0) { 498 if (comment().undeprecateTags().length == 0) { 499 if (mOverriddenMethod != null) { 500 result = mOverriddenMethod.deprecatedTags(); 501 } 502 } 503 } 504 return result; 505 } 506 507 public ArrayList<ParameterInfo> parameters() { 508 return mParameters; 509 } 510 511 512 public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) { 513 if (mParamStrings == null) { 514 if (mParameters.size() != params.length) { 515 return false; 516 } 517 int i = 0; 518 for (ParameterInfo mine : mParameters) { 519 if (!mine.matchesDimension(dimensions[i], varargs)) { 520 return false; 521 } 522 TypeInfo myType = mine.type(); 523 String qualifiedName = myType.qualifiedTypeName(); 524 String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName(); 525 String s = params[i]; 526 int slen = s.length(); 527 int qnlen = qualifiedName.length(); 528 529 // Check for a matching generic name or best known type 530 if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) { 531 return false; 532 } 533 i++; 534 } 535 } 536 return true; 537 } 538 539 /** 540 * Checks to see if a parameter from a method signature is 541 * compatible with a parameter given in a {@code @link} tag. 542 */ 543 private boolean matchesType(String signatureParam, String callerParam) { 544 int signatureLength = signatureParam.length(); 545 int callerLength = callerParam.length(); 546 return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength 547 && signatureParam.charAt(signatureLength - callerLength - 1) == '.' 548 && signatureParam.endsWith(callerParam)))); 549 } 550 551 public void makeHDF(Data data, String base) { 552 makeHDF(data, base, Collections.<String, TypeInfo>emptyMap()); 553 } 554 555 public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) { 556 data.setValue(base + ".kind", kind()); 557 data.setValue(base + ".name", name()); 558 data.setValue(base + ".href", htmlPage()); 559 data.setValue(base + ".anchor", anchor()); 560 561 if (mReturnType != null) { 562 returnType().getTypeWithArguments(typeMapping).makeHDF( 563 data, base + ".returnType", false, typeVariables()); 564 data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); 565 } 566 567 data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); 568 data.setValue(base + ".final", isFinal() ? "final" : ""); 569 data.setValue(base + ".static", isStatic() ? "static" : ""); 570 571 TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); 572 TagInfo.makeHDF(data, base + ".descr", inlineTags()); 573 TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); 574 TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); 575 data.setValue(base + ".since", getSince()); 576 if (isDeprecated()) { 577 data.setValue(base + ".deprecatedsince", getDeprecatedSince()); 578 } 579 ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); 580 AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); 581 ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); 582 ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray( 583 new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables(), typeMapping); 584 if (isProtected()) { 585 data.setValue(base + ".scope", "protected"); 586 } else if (isPublic()) { 587 data.setValue(base + ".scope", "public"); 588 } 589 TagInfo.makeHDF(data, base + ".returns", returnTags()); 590 591 if (mTypeParameters != null) { 592 TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); 593 } 594 595 AnnotationInstanceInfo.makeLinkListHDF( 596 data, 597 base + ".showAnnotations", 598 showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()])); 599 600 setFederatedReferences(data, base); 601 } 602 603 public HashSet<String> typeVariables() { 604 HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); 605 ClassInfo cl = containingClass(); 606 while (cl != null) { 607 ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments(); 608 if (types != null) { 609 TypeInfo.typeVariables(types, result); 610 } 611 cl = cl.containingClass(); 612 } 613 return result; 614 } 615 616 @Override 617 public boolean isExecutable() { 618 return true; 619 } 620 621 public ArrayList<ClassInfo> thrownExceptions() { 622 return mThrownExceptions; 623 } 624 625 public String typeArgumentsName(HashSet<String> typeVars) { 626 if (mTypeParameters == null || mTypeParameters.isEmpty()) { 627 return ""; 628 } else { 629 return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); 630 } 631 } 632 633 public boolean isAnnotationElement() { 634 return mIsAnnotationElement; 635 } 636 637 public AnnotationValueInfo defaultAnnotationElementValue() { 638 return mDefaultAnnotationElementValue; 639 } 640 641 public void setVarargs(boolean set) { 642 mIsVarargs = set; 643 } 644 645 public boolean isVarArgs() { 646 return mIsVarargs; 647 } 648 649 public boolean isEffectivelyFinal() { 650 if (mIsFinal) { 651 return true; 652 } 653 ClassInfo containingClass = containingClass(); 654 if (containingClass != null && containingClass.isEffectivelyFinal()) { 655 return true; 656 } 657 return false; 658 } 659 660 @Override 661 public String toString() { 662 return this.name(); 663 } 664 665 public void setReason(String reason) { 666 mReasonOpened = reason; 667 } 668 669 public String getReason() { 670 return mReasonOpened; 671 } 672 673 public void addException(String exec) { 674 ClassInfo exceptionClass = new ClassInfo(exec); 675 676 mThrownExceptions.add(exceptionClass); 677 } 678 679 public void addParameter(ParameterInfo p) { 680 // Name information 681 if (mParameters == null) { 682 mParameters = new ArrayList<ParameterInfo>(); 683 } 684 685 mParameters.add(p); 686 } 687 688 private String mFlatSignature; 689 private MethodInfo mOverriddenMethod; 690 private TypeInfo mReturnType; 691 private boolean mIsAnnotationElement; 692 private boolean mIsAbstract; 693 private boolean mIsSynchronized; 694 private boolean mIsNative; 695 private boolean mIsVarargs; 696 private boolean mDeprecatedKnown; 697 private boolean mIsDeprecated; 698 private ArrayList<ParameterInfo> mParameters; 699 private ArrayList<ClassInfo> mThrownExceptions; 700 private String[] mParamStrings; 701 private ThrowsTagInfo[] mThrowsTags; 702 private ParamTagInfo[] mParamTags; 703 private ArrayList<TypeInfo> mTypeParameters; 704 private AnnotationValueInfo mDefaultAnnotationElementValue; 705 private String mReasonOpened; 706 private ArrayList<Resolution> mResolutions; 707 708 // TODO: merge with droiddoc version (above) 709 public String qualifiedName() { 710 String parentQName = (containingClass() != null) 711 ? (containingClass().qualifiedName() + ".") : ""; 712 return parentQName + name(); 713 } 714 715 @Override 716 public String signature() { 717 if (mSignature == null) { 718 StringBuilder params = new StringBuilder("("); 719 for (ParameterInfo pInfo : mParameters) { 720 if (params.length() > 1) { 721 params.append(", "); 722 } 723 params.append(pInfo.type().fullName()); 724 } 725 726 params.append(")"); 727 mSignature = params.toString(); 728 } 729 return mSignature; 730 } 731 732 public boolean matches(MethodInfo other) { 733 return prettySignature().equals(other.prettySignature()); 734 } 735 736 public boolean throwsException(ClassInfo exception) { 737 for (ClassInfo e : mThrownExceptions) { 738 if (e.qualifiedName().equals(exception.qualifiedName())) { 739 return true; 740 } 741 } 742 return false; 743 } 744 745 public boolean isConsistent(MethodInfo mInfo) { 746 boolean consistent = true; 747 if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) { 748 if (!mReturnType.isPrimitive() && !mInfo.mReturnType.isPrimitive()) { 749 // Check to see if our class extends the old class. 750 ApiInfo infoApi = mInfo.containingClass().containingPackage().containingApi(); 751 ClassInfo infoReturnClass = infoApi.findClass(mInfo.mReturnType.qualifiedTypeName()); 752 // Find the classes. 753 consistent = infoReturnClass != null && 754 infoReturnClass.isAssignableTo(mReturnType.qualifiedTypeName()); 755 } else { 756 consistent = false; 757 } 758 759 if (!consistent) { 760 Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName() 761 + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType); 762 } 763 } 764 765 if (mIsAbstract != mInfo.mIsAbstract) { 766 consistent = false; 767 Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName() 768 + " has changed 'abstract' qualifier"); 769 } 770 771 if (mIsNative != mInfo.mIsNative) { 772 consistent = false; 773 Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName() 774 + " has changed 'native' qualifier"); 775 } 776 777 if (!mIsStatic) { 778 // Compiler-generated methods vary in their 'final' qualifier between versions of 779 // the compiler, so this check needs to be quite narrow. A change in 'final' 780 // status of a method is only relevant if (a) the method is not declared 'static' 781 // and (b) the method is not already inferred to be 'final' by virtue of its class. 782 if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) { 783 consistent = false; 784 Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName() 785 + " has added 'final' qualifier"); 786 } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) { 787 consistent = false; 788 Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName() 789 + " has removed 'final' qualifier"); 790 } 791 } 792 793 if (mIsStatic != mInfo.mIsStatic) { 794 consistent = false; 795 Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName() 796 + " has changed 'static' qualifier"); 797 } 798 799 if (!scope().equals(mInfo.scope())) { 800 consistent = false; 801 Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName() 802 + " changed scope from " + scope() + " to " + mInfo.scope()); 803 } 804 805 if (!isDeprecated() == mInfo.isDeprecated()) { 806 Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName() 807 + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated()); 808 consistent = false; 809 } 810 811 // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break " 812 // "compatibility with existing binaries." 813 /* 814 if (mIsSynchronized != mInfo.mIsSynchronized) { 815 Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName() 816 + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to " 817 + mInfo.mIsSynchronized); 818 consistent = false; 819 } 820 */ 821 822 for (ClassInfo exception : thrownExceptions()) { 823 if (!mInfo.throwsException(exception)) { 824 // exclude 'throws' changes to finalize() overrides with no arguments 825 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 826 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 827 + " no longer throws exception " + exception.qualifiedName()); 828 consistent = false; 829 } 830 } 831 } 832 833 for (ClassInfo exec : mInfo.thrownExceptions()) { 834 // exclude 'throws' changes to finalize() overrides with no arguments 835 if (!throwsException(exec)) { 836 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 837 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 838 + " added thrown exception " + exec.qualifiedName()); 839 consistent = false; 840 } 841 } 842 } 843 844 return consistent; 845 } 846 847 public void printResolutions() { 848 if (mResolutions == null || mResolutions.isEmpty()) { 849 return; 850 } 851 852 System.out.println("Resolutions for Method " + mName + mFlatSignature + ":"); 853 854 for (Resolution r : mResolutions) { 855 System.out.println(r); 856 } 857 } 858 859 public void addResolution(Resolution resolution) { 860 if (mResolutions == null) { 861 mResolutions = new ArrayList<Resolution>(); 862 } 863 864 mResolutions.add(resolution); 865 } 866 867 public boolean resolveResolutions() { 868 ArrayList<Resolution> resolutions = mResolutions; 869 mResolutions = new ArrayList<Resolution>(); 870 871 boolean allResolved = true; 872 for (Resolution resolution : resolutions) { 873 StringBuilder qualifiedClassName = new StringBuilder(); 874 InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName, 875 resolution.getInfoBuilder()); 876 877 // if we still couldn't resolve it, save it for the next pass 878 if ("".equals(qualifiedClassName.toString())) { 879 mResolutions.add(resolution); 880 allResolved = false; 881 } else if ("thrownException".equals(resolution.getVariable())) { 882 mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString())); 883 } 884 } 885 886 return allResolved; 887 } 888 } 889