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 { tags()32 public TagInfo[] tags() { 33 return comment().tags(); 34 } 35 inherited()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 addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)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 findOverriddenMethod(String name, String signature)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 addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)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 findRealOverriddenMethod(String name, String signature, HashSet notStrippable)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 findSuperclassImplementation(HashSet notStrippable)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 findRealOverriddenClass(String name, String signature)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 { tags()177 public TagInfo[] tags() { 178 return comment().briefTags(); 179 } 180 inherited()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 { tags()192 public TagInfo[] tags() { 193 return comment().returnTags(); 194 } 195 inherited()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 isDeprecated()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 setDeprecated(boolean deprecated)230 public void setDeprecated(boolean deprecated) { 231 mDeprecatedKnown = true; 232 mIsDeprecated = deprecated; 233 } 234 getTypeParameters()235 public ArrayList<TypeInfo> getTypeParameters() { 236 return mTypeParameters; 237 } 238 cloneForClass(ClassInfo newContainingClass)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 MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name, String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized, boolean isNative, boolean isAnnotationElement, String kind, String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations)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 init(AnnotationValueInfo defaultAnnotationElementValue)286 public void init(AnnotationValueInfo defaultAnnotationElementValue) { 287 mDefaultAnnotationElementValue = defaultAnnotationElementValue; 288 } 289 isAbstract()290 public boolean isAbstract() { 291 return mIsAbstract; 292 } 293 isSynchronized()294 public boolean isSynchronized() { 295 return mIsSynchronized; 296 } 297 isNative()298 public boolean isNative() { 299 return mIsNative; 300 } 301 flatSignature()302 public String flatSignature() { 303 return mFlatSignature; 304 } 305 inlineTags()306 public InheritedTags inlineTags() { 307 return new InlineTags(); 308 } 309 firstSentenceTags()310 public InheritedTags firstSentenceTags() { 311 return new FirstSentenceTags(); 312 } 313 returnTags()314 public InheritedTags returnTags() { 315 return new ReturnTags(); 316 } 317 returnType()318 public TypeInfo returnType() { 319 return mReturnType; 320 } 321 prettySignature()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 */ prettyParameters()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 */ getHashableName()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 inList(ClassInfo item, ThrowsTagInfo[] list)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 throwsTags()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 indexOfParam(String name, String[] list)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 paramTags()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 seeTags()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 deprecatedTags()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 parameters()496 public ArrayList<ParameterInfo> parameters() { 497 return mParameters; 498 } 499 500 matchesParams(String[] params, String[] dimensions, boolean varargs)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 */ matchesType(String signatureParam, String callerParam)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 makeHDF(Data data, String base)540 public void makeHDF(Data data, String base) { 541 data.setValue(base + ".kind", kind()); 542 data.setValue(base + ".name", name()); 543 data.setValue(base + ".href", htmlPage()); 544 data.setValue(base + ".anchor", anchor()); 545 546 if (mReturnType != null) { 547 returnType().makeHDF(data, base + ".returnType", false, typeVariables()); 548 data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); 549 } 550 551 data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); 552 data.setValue(base + ".final", isFinal() ? "final" : ""); 553 data.setValue(base + ".static", isStatic() ? "static" : ""); 554 555 TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); 556 TagInfo.makeHDF(data, base + ".descr", inlineTags()); 557 TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); 558 TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); 559 data.setValue(base + ".since", getSince()); 560 if (isDeprecated()) { 561 data.setValue(base + ".deprecatedsince", getDeprecatedSince()); 562 } 563 ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); 564 AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); 565 ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); 566 ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables()); 567 if (isProtected()) { 568 data.setValue(base + ".scope", "protected"); 569 } else if (isPublic()) { 570 data.setValue(base + ".scope", "public"); 571 } 572 TagInfo.makeHDF(data, base + ".returns", returnTags()); 573 574 if (mTypeParameters != null) { 575 TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); 576 } 577 578 setFederatedReferences(data, base); 579 } 580 typeVariables()581 public HashSet<String> typeVariables() { 582 HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); 583 ClassInfo cl = containingClass(); 584 while (cl != null) { 585 ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments(); 586 if (types != null) { 587 TypeInfo.typeVariables(types, result); 588 } 589 cl = cl.containingClass(); 590 } 591 return result; 592 } 593 594 @Override isExecutable()595 public boolean isExecutable() { 596 return true; 597 } 598 thrownExceptions()599 public ArrayList<ClassInfo> thrownExceptions() { 600 return mThrownExceptions; 601 } 602 typeArgumentsName(HashSet<String> typeVars)603 public String typeArgumentsName(HashSet<String> typeVars) { 604 if (mTypeParameters == null || mTypeParameters.isEmpty()) { 605 return ""; 606 } else { 607 return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); 608 } 609 } 610 isAnnotationElement()611 public boolean isAnnotationElement() { 612 return mIsAnnotationElement; 613 } 614 defaultAnnotationElementValue()615 public AnnotationValueInfo defaultAnnotationElementValue() { 616 return mDefaultAnnotationElementValue; 617 } 618 setVarargs(boolean set)619 public void setVarargs(boolean set) { 620 mIsVarargs = set; 621 } 622 isVarArgs()623 public boolean isVarArgs() { 624 return mIsVarargs; 625 } 626 627 @Override toString()628 public String toString() { 629 return this.name(); 630 } 631 setReason(String reason)632 public void setReason(String reason) { 633 mReasonOpened = reason; 634 } 635 getReason()636 public String getReason() { 637 return mReasonOpened; 638 } 639 addException(String exec)640 public void addException(String exec) { 641 ClassInfo exceptionClass = new ClassInfo(exec); 642 643 mThrownExceptions.add(exceptionClass); 644 } 645 addParameter(ParameterInfo p)646 public void addParameter(ParameterInfo p) { 647 // Name information 648 if (mParameters == null) { 649 mParameters = new ArrayList<ParameterInfo>(); 650 } 651 652 mParameters.add(p); 653 } 654 655 private String mFlatSignature; 656 private MethodInfo mOverriddenMethod; 657 private TypeInfo mReturnType; 658 private boolean mIsAnnotationElement; 659 private boolean mIsAbstract; 660 private boolean mIsSynchronized; 661 private boolean mIsNative; 662 private boolean mIsVarargs; 663 private boolean mDeprecatedKnown; 664 private boolean mIsDeprecated; 665 private ArrayList<ParameterInfo> mParameters; 666 private ArrayList<ClassInfo> mThrownExceptions; 667 private String[] mParamStrings; 668 private ThrowsTagInfo[] mThrowsTags; 669 private ParamTagInfo[] mParamTags; 670 private ArrayList<TypeInfo> mTypeParameters; 671 private AnnotationValueInfo mDefaultAnnotationElementValue; 672 private String mReasonOpened; 673 private ArrayList<Resolution> mResolutions; 674 675 // TODO: merge with droiddoc version (above) qualifiedName()676 public String qualifiedName() { 677 String parentQName = (containingClass() != null) 678 ? (containingClass().qualifiedName() + ".") : ""; 679 return parentQName + name(); 680 } 681 682 @Override signature()683 public String signature() { 684 if (mSignature == null) { 685 StringBuilder params = new StringBuilder("("); 686 for (ParameterInfo pInfo : mParameters) { 687 if (params.length() > 1) { 688 params.append(", "); 689 } 690 params.append(pInfo.type().fullName()); 691 } 692 693 params.append(")"); 694 mSignature = params.toString(); 695 } 696 return mSignature; 697 } 698 matches(MethodInfo other)699 public boolean matches(MethodInfo other) { 700 return prettySignature().equals(other.prettySignature()); 701 } 702 throwsException(ClassInfo exception)703 public boolean throwsException(ClassInfo exception) { 704 for (ClassInfo e : mThrownExceptions) { 705 if (e.qualifiedName().equals(exception.qualifiedName())) { 706 return true; 707 } 708 } 709 return false; 710 } 711 isConsistent(MethodInfo mInfo)712 public boolean isConsistent(MethodInfo mInfo) { 713 boolean consistent = true; 714 if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) { 715 consistent = false; 716 Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName() 717 + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType); 718 } 719 720 if (mIsAbstract != mInfo.mIsAbstract) { 721 consistent = false; 722 Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName() 723 + " has changed 'abstract' qualifier"); 724 } 725 726 if (mIsNative != mInfo.mIsNative) { 727 consistent = false; 728 Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName() 729 + " has changed 'native' qualifier"); 730 } 731 732 if (mIsFinal != mInfo.mIsFinal) { 733 // Compiler-generated methods vary in their 'final' qual between versions of 734 // the compiler, so this check needs to be quite narrow. A change in 'final' 735 // status of a method is only relevant if (a) the method is not declared 'static' 736 // and (b) the method's class is not itself 'final'. 737 if (!mIsStatic) { 738 if ((containingClass() == null) || (!containingClass().isFinal())) { 739 consistent = false; 740 Errors.error(Errors.CHANGED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName() 741 + " has changed 'final' qualifier"); 742 } 743 } 744 } 745 746 if (mIsStatic != mInfo.mIsStatic) { 747 consistent = false; 748 Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName() 749 + " has changed 'static' qualifier"); 750 } 751 752 if (!scope().equals(mInfo.scope())) { 753 consistent = false; 754 Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName() 755 + " changed scope from " + scope() + " to " + mInfo.scope()); 756 } 757 758 if (!isDeprecated() == mInfo.isDeprecated()) { 759 Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName() 760 + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated()); 761 consistent = false; 762 } 763 764 // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break " 765 // "compatibility with existing binaries." 766 /* 767 if (mIsSynchronized != mInfo.mIsSynchronized) { 768 Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName() 769 + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to " 770 + mInfo.mIsSynchronized); 771 consistent = false; 772 } 773 */ 774 775 for (ClassInfo exception : thrownExceptions()) { 776 if (!mInfo.throwsException(exception)) { 777 // exclude 'throws' changes to finalize() overrides with no arguments 778 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 779 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 780 + " no longer throws exception " + exception.qualifiedName()); 781 consistent = false; 782 } 783 } 784 } 785 786 for (ClassInfo exec : mInfo.thrownExceptions()) { 787 // exclude 'throws' changes to finalize() overrides with no arguments 788 if (!throwsException(exec)) { 789 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 790 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName() 791 + " added thrown exception " + exec.qualifiedName()); 792 consistent = false; 793 } 794 } 795 } 796 797 return consistent; 798 } 799 printResolutions()800 public void printResolutions() { 801 if (mResolutions == null || mResolutions.isEmpty()) { 802 return; 803 } 804 805 System.out.println("Resolutions for Method " + mName + mFlatSignature + ":"); 806 807 for (Resolution r : mResolutions) { 808 System.out.println(r); 809 } 810 } 811 addResolution(Resolution resolution)812 public void addResolution(Resolution resolution) { 813 if (mResolutions == null) { 814 mResolutions = new ArrayList<Resolution>(); 815 } 816 817 mResolutions.add(resolution); 818 } 819 resolveResolutions()820 public boolean resolveResolutions() { 821 ArrayList<Resolution> resolutions = mResolutions; 822 mResolutions = new ArrayList<Resolution>(); 823 824 boolean allResolved = true; 825 for (Resolution resolution : resolutions) { 826 StringBuilder qualifiedClassName = new StringBuilder(); 827 InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName, 828 resolution.getInfoBuilder()); 829 830 // if we still couldn't resolve it, save it for the next pass 831 if ("".equals(qualifiedClassName.toString())) { 832 mResolutions.add(resolution); 833 allResolved = false; 834 } else if ("thrownException".equals(resolution.getVariable())) { 835 mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString())); 836 } 837 } 838 839 return allResolved; 840 } 841 } 842