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