1 /* 2 * Copyright (C) 2008 The Android Open Source Project 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 import org.clearsilver.HDF; 18 19 import java.util.*; 20 21 public class MethodInfo extends MemberInfo 22 { 23 public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() { 24 public int compare(MethodInfo a, MethodInfo b) { 25 return a.name().compareTo(b.name()); 26 } 27 }; 28 29 private class InlineTags implements InheritedTags 30 { tags()31 public TagInfo[] tags() 32 { 33 return comment().tags(); 34 } inherited()35 public InheritedTags inherited() 36 { 37 MethodInfo m = findOverriddenMethod(name(), signature()); 38 if (m != null) { 39 return m.inlineTags(); 40 } else { 41 return null; 42 } 43 } 44 } 45 addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)46 private static void addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue) 47 { 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 { 60 if (mReturnType == null) { 61 // ctor 62 return null; 63 } 64 if (mOverriddenMethod != null) { 65 return mOverriddenMethod; 66 } 67 68 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 69 addInterfaces(containingClass().interfaces(), queue); 70 for (ClassInfo iface: queue) { 71 for (MethodInfo me: iface.methods()) { 72 if (me.name().equals(name) 73 && me.signature().equals(signature) 74 && me.inlineTags().tags() != null 75 && me.inlineTags().tags().length > 0) { 76 return me; 77 } 78 } 79 } 80 return null; 81 } 82 addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)83 private static void addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue) 84 { 85 for (ClassInfo i: ifaces) { 86 queue.add(i); 87 if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) { 88 queue.add(i.superclass()); 89 } 90 } 91 for (ClassInfo i: ifaces) { 92 addInterfaces(i.realInterfaces(), queue); 93 } 94 } 95 findRealOverriddenMethod(String name, String signature, HashSet notStrippable)96 public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) { 97 if (mReturnType == null) { 98 // ctor 99 return null; 100 } 101 if (mOverriddenMethod != null) { 102 return mOverriddenMethod; 103 } 104 105 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 106 if (containingClass().realSuperclass() != null && 107 containingClass().realSuperclass().isAbstract()) { 108 queue.add(containingClass()); 109 } 110 addInterfaces(containingClass().realInterfaces(), queue); 111 for (ClassInfo iface: queue) { 112 for (MethodInfo me: iface.methods()) { 113 if (me.name().equals(name) 114 && me.signature().equals(signature) 115 && me.inlineTags().tags() != null 116 && me.inlineTags().tags().length > 0 117 && notStrippable.contains(me.containingClass())) { 118 return me; 119 } 120 } 121 } 122 return null; 123 } 124 findSuperclassImplementation(HashSet notStrippable)125 public MethodInfo findSuperclassImplementation(HashSet notStrippable) { 126 if (mReturnType == null) { 127 // ctor 128 return null; 129 } 130 if (mOverriddenMethod != null) { 131 // Even if we're told outright that this was the overridden method, we want to 132 // be conservative and ignore mismatches of parameter types -- they arise from 133 // extending generic specializations, and we want to consider the derived-class 134 // method to be a non-override. 135 if (this.signature().equals(mOverriddenMethod.signature())) { 136 return mOverriddenMethod; 137 } 138 } 139 140 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 141 if (containingClass().realSuperclass() != null && 142 containingClass().realSuperclass().isAbstract()) { 143 queue.add(containingClass()); 144 } 145 addInterfaces(containingClass().realInterfaces(), queue); 146 for (ClassInfo iface: queue) { 147 for (MethodInfo me: iface.methods()) { 148 if (me.name().equals(this.name()) 149 && me.signature().equals(this.signature()) 150 && notStrippable.contains(me.containingClass())) { 151 return me; 152 } 153 } 154 } 155 return null; 156 } 157 findRealOverriddenClass(String name, String signature)158 public ClassInfo findRealOverriddenClass(String name, String signature) { 159 if (mReturnType == null) { 160 // ctor 161 return null; 162 } 163 if (mOverriddenMethod != null) { 164 return mOverriddenMethod.mRealContainingClass; 165 } 166 167 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 168 if (containingClass().realSuperclass() != null && 169 containingClass().realSuperclass().isAbstract()) { 170 queue.add(containingClass()); 171 } 172 addInterfaces(containingClass().realInterfaces(), queue); 173 for (ClassInfo iface: queue) { 174 for (MethodInfo me: iface.methods()) { 175 if (me.name().equals(name) 176 && me.signature().equals(signature) 177 && me.inlineTags().tags() != null 178 && me.inlineTags().tags().length > 0) { 179 return iface; 180 } 181 } 182 } 183 return null; 184 } 185 186 private class FirstSentenceTags implements InheritedTags 187 { tags()188 public TagInfo[] tags() 189 { 190 return comment().briefTags(); 191 } inherited()192 public InheritedTags inherited() 193 { 194 MethodInfo m = findOverriddenMethod(name(), signature()); 195 if (m != null) { 196 return m.firstSentenceTags(); 197 } else { 198 return null; 199 } 200 } 201 } 202 203 private class ReturnTags implements InheritedTags { tags()204 public TagInfo[] tags() { 205 return comment().returnTags(); 206 } inherited()207 public InheritedTags inherited() { 208 MethodInfo m = findOverriddenMethod(name(), signature()); 209 if (m != null) { 210 return m.returnTags(); 211 } else { 212 return null; 213 } 214 } 215 } 216 isDeprecated()217 public boolean isDeprecated() { 218 boolean deprecated = false; 219 if (!mDeprecatedKnown) { 220 boolean commentDeprecated = (comment().deprecatedTags().length > 0); 221 boolean annotationDeprecated = false; 222 for (AnnotationInstanceInfo annotation : annotations()) { 223 if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { 224 annotationDeprecated = true; 225 break; 226 } 227 } 228 229 if (commentDeprecated != annotationDeprecated) { 230 Errors.error(Errors.DEPRECATION_MISMATCH, position(), 231 "Method " + mContainingClass.qualifiedName() + "." + name() 232 + ": @Deprecated annotation and @deprecated doc tag do not match"); 233 } 234 235 mIsDeprecated = commentDeprecated | annotationDeprecated; 236 mDeprecatedKnown = true; 237 } 238 return mIsDeprecated; 239 } 240 getTypeParameters()241 public TypeInfo[] getTypeParameters(){ 242 return mTypeParameters; 243 } 244 cloneForClass(ClassInfo newContainingClass)245 public MethodInfo cloneForClass(ClassInfo newContainingClass) { 246 MethodInfo result = new MethodInfo(getRawCommentText(), mTypeParameters, 247 name(), signature(), newContainingClass, realContainingClass(), 248 isPublic(), isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), 249 isSynthetic(), mIsAbstract, mIsSynchronized, mIsNative, mIsAnnotationElement, 250 kind(), mFlatSignature, mOverriddenMethod, 251 mReturnType, mParameters, mThrownExceptions, position(), annotations()); 252 result.init(mDefaultAnnotationElementValue); 253 return result; 254 } 255 MethodInfo(String rawCommentText, 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, ParameterInfo[] parameters, ClassInfo[] thrownExceptions, SourcePositionInfo position, AnnotationInstanceInfo[] annotations)256 public MethodInfo(String rawCommentText, TypeInfo[] typeParameters, String name, 257 String signature, ClassInfo containingClass, ClassInfo realContainingClass, 258 boolean isPublic, boolean isProtected, 259 boolean isPackagePrivate, boolean isPrivate, 260 boolean isFinal, boolean isStatic, boolean isSynthetic, 261 boolean isAbstract, boolean isSynchronized, boolean isNative, 262 boolean isAnnotationElement, String kind, 263 String flatSignature, MethodInfo overriddenMethod, 264 TypeInfo returnType, ParameterInfo[] parameters, 265 ClassInfo[] thrownExceptions, SourcePositionInfo position, 266 AnnotationInstanceInfo[] annotations) 267 { 268 // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match 269 // the Java5-emitted base API description. 270 super(rawCommentText, name, signature, containingClass, realContainingClass, 271 isPublic, isProtected, isPackagePrivate, isPrivate, 272 ((name.equals("values") && containingClass.isEnum()) ? true : isFinal), 273 isStatic, isSynthetic, kind, position, annotations); 274 275 // The underlying MethodDoc for an interface's declared methods winds up being marked 276 // non-abstract. Correct that here by looking at the immediate-parent class, and marking 277 // this method abstract if it is an unimplemented interface method. 278 if (containingClass.isInterface()) { 279 isAbstract = true; 280 } 281 282 mReasonOpened = "0:0"; 283 mIsAnnotationElement = isAnnotationElement; 284 mTypeParameters = typeParameters; 285 mIsAbstract = isAbstract; 286 mIsSynchronized = isSynchronized; 287 mIsNative = isNative; 288 mFlatSignature = flatSignature; 289 mOverriddenMethod = overriddenMethod; 290 mReturnType = returnType; 291 mParameters = parameters; 292 mThrownExceptions = thrownExceptions; 293 } 294 init(AnnotationValueInfo defaultAnnotationElementValue)295 public void init(AnnotationValueInfo defaultAnnotationElementValue) 296 { 297 mDefaultAnnotationElementValue = defaultAnnotationElementValue; 298 } 299 isAbstract()300 public boolean isAbstract() 301 { 302 return mIsAbstract; 303 } 304 isSynchronized()305 public boolean isSynchronized() 306 { 307 return mIsSynchronized; 308 } 309 isNative()310 public boolean isNative() 311 { 312 return mIsNative; 313 } 314 flatSignature()315 public String flatSignature() 316 { 317 return mFlatSignature; 318 } 319 inlineTags()320 public InheritedTags inlineTags() 321 { 322 return new InlineTags(); 323 } 324 firstSentenceTags()325 public InheritedTags firstSentenceTags() 326 { 327 return new FirstSentenceTags(); 328 } 329 returnTags()330 public InheritedTags returnTags() { 331 return new ReturnTags(); 332 } 333 returnType()334 public TypeInfo returnType() 335 { 336 return mReturnType; 337 } 338 prettySignature()339 public String prettySignature() 340 { 341 String s = "("; 342 int N = mParameters.length; 343 for (int i=0; i<N; i++) { 344 ParameterInfo p = mParameters[i]; 345 TypeInfo t = p.type(); 346 if (t.isPrimitive()) { 347 s += t.simpleTypeName(); 348 } else { 349 s += t.asClassInfo().name(); 350 } 351 if (i != N-1) { 352 s += ','; 353 } 354 } 355 s += ')'; 356 return s; 357 } 358 359 /** 360 * Returns a name consistent with the {@link 361 * com.android.apicheck.MethodInfo#getHashableName()}. 362 */ getHashableName()363 public String getHashableName() { 364 StringBuilder result = new StringBuilder(); 365 result.append(name()); 366 for (ParameterInfo pInfo : mParameters) { 367 result.append(":").append(pInfo.type().fullName()); 368 } 369 return result.toString(); 370 } 371 inList(ClassInfo item, ThrowsTagInfo[] list)372 private boolean inList(ClassInfo item, ThrowsTagInfo[] list) 373 { 374 int len = list.length; 375 String qn = item.qualifiedName(); 376 for (int i=0; i<len; i++) { 377 ClassInfo ex = list[i].exception(); 378 if (ex != null && ex.qualifiedName().equals(qn)) { 379 return true; 380 } 381 } 382 return false; 383 } 384 throwsTags()385 public ThrowsTagInfo[] throwsTags() 386 { 387 if (mThrowsTags == null) { 388 ThrowsTagInfo[] documented = comment().throwsTags(); 389 ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); 390 391 int len = documented.length; 392 for (int i=0; i<len; i++) { 393 rv.add(documented[i]); 394 } 395 396 ClassInfo[] all = mThrownExceptions; 397 len = all.length; 398 for (int i=0; i<len; i++) { 399 ClassInfo cl = all[i]; 400 if (documented == null || !inList(cl, documented)) { 401 rv.add(new ThrowsTagInfo("@throws", "@throws", 402 cl.qualifiedName(), cl, "", 403 containingClass(), position())); 404 } 405 } 406 mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]); 407 } 408 return mThrowsTags; 409 } 410 indexOfParam(String name, String[] list)411 private static int indexOfParam(String name, String[] list) 412 { 413 final int N = list.length; 414 for (int i=0; i<N; i++) { 415 if (name.equals(list[i])) { 416 return i; 417 } 418 } 419 return -1; 420 } 421 paramTags()422 public ParamTagInfo[] paramTags() 423 { 424 if (mParamTags == null) { 425 final int N = mParameters.length; 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 for (int i=0; i<N; i++) { 434 names[i] = mParameters[i].name(); 435 comments[i] = ""; 436 positions[i] = mParameters[i].position(); 437 } 438 439 // gather our comments, and complain about misnamed @param tags 440 for (ParamTagInfo tag: comment().paramTags()) { 441 int index = indexOfParam(tag.parameterName(), names); 442 if (index >= 0) { 443 comments[index] = tag.parameterComment(); 444 positions[index] = tag.position(); 445 } else { 446 Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), 447 "@param tag with name that doesn't match the parameter list: '" 448 + tag.parameterName() + "'"); 449 } 450 } 451 452 // get our parent's tags to fill in the blanks 453 MethodInfo overridden = this.findOverriddenMethod(name(), signature()); 454 if (overridden != null) { 455 ParamTagInfo[] maternal = overridden.paramTags(); 456 for (int i=0; i<N; i++) { 457 if (comments[i].equals("")) { 458 comments[i] = maternal[i].parameterComment(); 459 positions[i] = maternal[i].position(); 460 } 461 } 462 } 463 464 // construct the results, and cache them for next time 465 mParamTags = new ParamTagInfo[N]; 466 for (int i=0; i<N; i++) { 467 mParamTags[i] = new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], 468 parent(), positions[i]); 469 470 // while we're here, if we find any parameters that are still undocumented at this 471 // point, complain. (this warning is off by default, because it's really, really 472 // common; but, it's good to be able to enforce it) 473 if (comments[i].equals("")) { 474 Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], 475 "Undocumented parameter '" + names[i] + "' on method '" 476 + name() + "'"); 477 } 478 } 479 } 480 return mParamTags; 481 } 482 seeTags()483 public SeeTagInfo[] seeTags() 484 { 485 SeeTagInfo[] result = comment().seeTags(); 486 if (result == null) { 487 if (mOverriddenMethod != null) { 488 result = mOverriddenMethod.seeTags(); 489 } 490 } 491 return result; 492 } 493 deprecatedTags()494 public TagInfo[] deprecatedTags() 495 { 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 parameters()507 public ParameterInfo[] parameters() 508 { 509 return mParameters; 510 } 511 512 matchesParams(String[] params, String[] dimensions)513 public boolean matchesParams(String[] params, String[] dimensions) 514 { 515 if (mParamStrings == null) { 516 ParameterInfo[] mine = mParameters; 517 int len = mine.length; 518 if (len != params.length) { 519 return false; 520 } 521 for (int i=0; i<len; i++) { 522 TypeInfo t = mine[i].type(); 523 if (!t.dimension().equals(dimensions[i])) { 524 return false; 525 } 526 String qn = t.qualifiedTypeName(); 527 String s = params[i]; 528 int slen = s.length(); 529 int qnlen = qn.length(); 530 if (!(qn.equals(s) || 531 ((slen+1)<qnlen && qn.charAt(qnlen-slen-1)=='.' 532 && qn.endsWith(s)))) { 533 return false; 534 } 535 } 536 } 537 return true; 538 } 539 makeHDF(HDF data, String base)540 public void makeHDF(HDF data, String base) 541 { 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 ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); 562 AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); 563 ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); 564 ParameterInfo.makeHDF(data, base + ".params", parameters(), isVarArgs(), typeVariables()); 565 if (isProtected()) { 566 data.setValue(base + ".scope", "protected"); 567 } 568 else if (isPublic()) { 569 data.setValue(base + ".scope", "public"); 570 } 571 TagInfo.makeHDF(data, base + ".returns", returnTags()); 572 573 if (mTypeParameters != null) { 574 TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); 575 } 576 } 577 typeVariables()578 public HashSet<String> typeVariables() 579 { 580 HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); 581 ClassInfo cl = containingClass(); 582 while (cl != null) { 583 TypeInfo[] types = cl.asTypeInfo().typeArguments(); 584 if (types != null) { 585 TypeInfo.typeVariables(types, result); 586 } 587 cl = cl.containingClass(); 588 } 589 return result; 590 } 591 592 @Override isExecutable()593 public boolean isExecutable() 594 { 595 return true; 596 } 597 thrownExceptions()598 public ClassInfo[] thrownExceptions() 599 { 600 return mThrownExceptions; 601 } 602 typeArgumentsName(HashSet<String> typeVars)603 public String typeArgumentsName(HashSet<String> typeVars) 604 { 605 if (mTypeParameters == null || mTypeParameters.length == 0) { 606 return ""; 607 } else { 608 return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); 609 } 610 } 611 isAnnotationElement()612 public boolean isAnnotationElement() 613 { 614 return mIsAnnotationElement; 615 } 616 defaultAnnotationElementValue()617 public AnnotationValueInfo defaultAnnotationElementValue() 618 { 619 return mDefaultAnnotationElementValue; 620 } 621 setVarargs(boolean set)622 public void setVarargs(boolean set){ 623 mIsVarargs = set; 624 } isVarArgs()625 public boolean isVarArgs(){ 626 return mIsVarargs; 627 } 628 629 @Override toString()630 public String toString(){ 631 return this.name(); 632 } 633 setReason(String reason)634 public void setReason(String reason) { 635 mReasonOpened = reason; 636 } 637 getReason()638 public String getReason() { 639 return mReasonOpened; 640 } 641 642 private String mFlatSignature; 643 private MethodInfo mOverriddenMethod; 644 private TypeInfo mReturnType; 645 private boolean mIsAnnotationElement; 646 private boolean mIsAbstract; 647 private boolean mIsSynchronized; 648 private boolean mIsNative; 649 private boolean mIsVarargs; 650 private boolean mDeprecatedKnown; 651 private boolean mIsDeprecated; 652 private ParameterInfo[] mParameters; 653 private ClassInfo[] mThrownExceptions; 654 private String[] mParamStrings; 655 ThrowsTagInfo[] mThrowsTags; 656 private ParamTagInfo[] mParamTags; 657 private TypeInfo[] mTypeParameters; 658 private AnnotationValueInfo mDefaultAnnotationElementValue; 659 private String mReasonOpened; 660 } 661 662