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.ArrayList; 24 import java.util.Collections; 25 import java.util.Comparator; 26 import java.util.HashSet; 27 import java.util.LinkedList; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Objects; 31 import java.util.Queue; 32 import java.util.function.Predicate; 33 34 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable { 35 public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() { 36 @Override 37 public int compare(MethodInfo a, MethodInfo b) { 38 // TODO: expand to compare signature for better sorting 39 return a.name().compareTo(b.name()); 40 } 41 }; 42 43 private class InlineTags implements InheritedTags { tags()44 public TagInfo[] tags() { 45 return comment().tags(); 46 } 47 inherited()48 public InheritedTags inherited() { 49 MethodInfo m = findOverriddenMethod(name(), signature()); 50 if (m != null) { 51 return m.inlineTags(); 52 } else { 53 return null; 54 } 55 } 56 } 57 addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)58 private static void addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) { 59 for (ClassInfo i : ifaces) { 60 queue.add(i); 61 } 62 for (ClassInfo i : ifaces) { 63 addInterfaces(i.interfaces(), queue); 64 } 65 } 66 67 // first looks for a superclass, and then does a breadth first search to 68 // find the least far away match findOverriddenMethod(String name, String signature)69 public MethodInfo findOverriddenMethod(String name, String signature) { 70 if (mReturnType == null) { 71 // ctor 72 return null; 73 } 74 if (mOverriddenMethod != null) { 75 return mOverriddenMethod; 76 } 77 78 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 79 addInterfaces(containingClass().interfaces(), queue); 80 for (ClassInfo iface : queue) { 81 for (MethodInfo me : iface.methods()) { 82 if (me.name().equals(name) && me.signature().equals(signature) 83 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) { 84 return me; 85 } 86 } 87 } 88 return null; 89 } 90 addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)91 private static void addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) { 92 for (ClassInfo i : ifaces) { 93 queue.add(i); 94 if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) { 95 queue.add(i.superclass()); 96 } 97 } 98 for (ClassInfo i : ifaces) { 99 addInterfaces(i.realInterfaces(), queue); 100 } 101 } 102 findRealOverriddenMethod(String name, String signature, HashSet notStrippable)103 public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) { 104 if (mReturnType == null) { 105 // ctor 106 return null; 107 } 108 if (mOverriddenMethod != null) { 109 return mOverriddenMethod; 110 } 111 112 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 113 if (containingClass().realSuperclass() != null 114 && containingClass().realSuperclass().isAbstract()) { 115 queue.add(containingClass().realSuperclass()); 116 } 117 addInterfaces(containingClass().realInterfaces(), queue); 118 for (ClassInfo iface : queue) { 119 for (MethodInfo me : iface.methods()) { 120 if (me.name().equals(name) && me.signature().equals(signature) 121 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0 122 && notStrippable.contains(me.containingClass())) { 123 return me; 124 } 125 } 126 } 127 return null; 128 } 129 findPredicateOverriddenMethod(Predicate<MemberInfo> predicate)130 public MethodInfo findPredicateOverriddenMethod(Predicate<MemberInfo> predicate) { 131 if (mReturnType == null) { 132 // ctor 133 return null; 134 } 135 if (mOverriddenMethod != null) { 136 if (equals(mOverriddenMethod) && !mOverriddenMethod.isStatic() 137 && predicate.test(mOverriddenMethod)) { 138 return mOverriddenMethod; 139 } 140 } 141 142 for (ClassInfo clazz : containingClass().gatherAncestorClasses()) { 143 for (MethodInfo method : clazz.getExhaustiveMethods()) { 144 if (equals(method) && !method.isStatic() && predicate.test(method)) { 145 return method; 146 } 147 } 148 } 149 return null; 150 } 151 findRealOverriddenClass(String name, String signature)152 public ClassInfo findRealOverriddenClass(String name, String signature) { 153 if (mReturnType == null) { 154 // ctor 155 return null; 156 } 157 if (mOverriddenMethod != null) { 158 return mOverriddenMethod.mRealContainingClass; 159 } 160 161 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 162 if (containingClass().realSuperclass() != null 163 && containingClass().realSuperclass().isAbstract()) { 164 queue.add(containingClass().realSuperclass()); 165 } 166 addInterfaces(containingClass().realInterfaces(), queue); 167 for (ClassInfo iface : queue) { 168 for (MethodInfo me : iface.methods()) { 169 if (me.name().equals(name) && me.signature().equals(signature) 170 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) { 171 return iface; 172 } 173 } 174 } 175 return null; 176 } 177 178 private class FirstSentenceTags implements InheritedTags { tags()179 public TagInfo[] tags() { 180 return comment().briefTags(); 181 } 182 inherited()183 public InheritedTags inherited() { 184 MethodInfo m = findOverriddenMethod(name(), signature()); 185 if (m != null) { 186 return m.firstSentenceTags(); 187 } else { 188 return null; 189 } 190 } 191 } 192 193 private class ReturnTags implements InheritedTags { tags()194 public TagInfo[] tags() { 195 return comment().returnTags(); 196 } 197 inherited()198 public InheritedTags inherited() { 199 MethodInfo m = findOverriddenMethod(name(), signature()); 200 if (m != null) { 201 return m.returnTags(); 202 } else { 203 return null; 204 } 205 } 206 } 207 isDeprecated()208 public boolean isDeprecated() { 209 boolean deprecated = false; 210 if (!mDeprecatedKnown) { 211 boolean commentDeprecated = comment().isDeprecated(); 212 boolean annotationDeprecated = false; 213 for (AnnotationInstanceInfo annotation : annotations()) { 214 if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { 215 annotationDeprecated = true; 216 break; 217 } 218 } 219 220 // Check to see that the JavaDoc contains @deprecated AND the method is marked as @Deprecated. 221 // Otherwise, warn. 222 // Note: We only do this for "included" classes (i.e. those we have source code for); we do 223 // not have comments for classes from .class files but we do know whether a method is marked 224 // as @Deprecated. 225 if (mContainingClass.isIncluded() && !isHiddenOrRemoved() 226 && commentDeprecated != annotationDeprecated) { 227 Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Method " 228 + mContainingClass.qualifiedName() + "." + name() 229 + ": @Deprecated annotation (" + (annotationDeprecated ? "" : "not ") 230 + "present) and @deprecated doc tag (" + (commentDeprecated ? "" : "not ") 231 + "present) do not match"); 232 } 233 234 mIsDeprecated = commentDeprecated | annotationDeprecated; 235 mDeprecatedKnown = true; 236 } 237 return mIsDeprecated; 238 } 239 setDeprecated(boolean deprecated)240 public void setDeprecated(boolean deprecated) { 241 mDeprecatedKnown = true; 242 mIsDeprecated = deprecated; 243 } 244 getTypeParameters()245 public ArrayList<TypeInfo> getTypeParameters() { 246 return mTypeParameters; 247 } 248 hasTypeParameters()249 public boolean hasTypeParameters() { 250 return mTypeParameters != null && !mTypeParameters.isEmpty(); 251 } 252 253 /** 254 * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the 255 * typeArgumentMapping to the parameters and return types. 256 */ cloneForClass(ClassInfo newContainingClass, Map<String, TypeInfo> typeArgumentMapping)257 public MethodInfo cloneForClass(ClassInfo newContainingClass, 258 Map<String, TypeInfo> typeArgumentMapping) { 259 if (newContainingClass == containingClass()) { 260 return this; 261 } 262 TypeInfo returnType = (mReturnType != null) 263 ? mReturnType.getTypeWithArguments(typeArgumentMapping) 264 : null; 265 ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>(); 266 for (ParameterInfo pi : mParameters) { 267 parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping)); 268 } 269 MethodInfo result = 270 new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(), 271 newContainingClass, realContainingClass(), isPublic(), isProtected(), 272 isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract, 273 mIsSynchronized, mIsNative, mIsDefault, mIsAnnotationElement, kind(), mFlatSignature, 274 mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(), 275 annotations()); 276 result.init(mDefaultAnnotationElementValue); 277 return result; 278 } 279 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 isDefault, boolean isAnnotationElement, String kind, String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations)280 public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name, 281 String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, 282 boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, 283 boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized, 284 boolean isNative, boolean isDefault, boolean isAnnotationElement, String kind, 285 String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType, 286 ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions, 287 SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations) { 288 // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match 289 // the Java5-emitted base API description. 290 super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic, 291 isProtected, isPackagePrivate, isPrivate, 292 ((name.equals("values") && containingClass.isEnum()) ? true : isFinal), 293 isStatic, isSynthetic, kind, position, annotations); 294 295 mReasonOpened = "0:0"; 296 mIsAnnotationElement = isAnnotationElement; 297 mTypeParameters = typeParameters; 298 mIsAbstract = isAbstract; 299 mIsSynchronized = isSynchronized; 300 mIsNative = isNative; 301 mIsDefault = isDefault; 302 mFlatSignature = flatSignature; 303 mOverriddenMethod = overriddenMethod; 304 mReturnType = returnType; 305 mParameters = parameters; 306 mThrownExceptions = thrownExceptions; 307 } 308 init(AnnotationValueInfo defaultAnnotationElementValue)309 public void init(AnnotationValueInfo defaultAnnotationElementValue) { 310 mDefaultAnnotationElementValue = defaultAnnotationElementValue; 311 } 312 isAbstract()313 public boolean isAbstract() { 314 return mIsAbstract; 315 } 316 isSynchronized()317 public boolean isSynchronized() { 318 return mIsSynchronized; 319 } 320 isNative()321 public boolean isNative() { 322 return mIsNative; 323 } 324 isDefault()325 public boolean isDefault() { 326 return mIsDefault; 327 } 328 flatSignature()329 public String flatSignature() { 330 return mFlatSignature; 331 } 332 inlineTags()333 public InheritedTags inlineTags() { 334 return new InlineTags(); 335 } 336 blockTags()337 public TagInfo[] blockTags() { 338 return comment().blockTags(); 339 } 340 firstSentenceTags()341 public InheritedTags firstSentenceTags() { 342 return new FirstSentenceTags(); 343 } 344 returnTags()345 public InheritedTags returnTags() { 346 return new ReturnTags(); 347 } 348 returnType()349 public TypeInfo returnType() { 350 return mReturnType; 351 } 352 prettySignature()353 public String prettySignature() { 354 return name() + prettyParameters(); 355 } 356 prettyQualifiedSignature()357 public String prettyQualifiedSignature() { 358 return qualifiedName() + prettyParameters(); 359 } 360 361 /** 362 * Returns a printable version of the parameters of this method's signature. 363 */ prettyParameters()364 public String prettyParameters() { 365 StringBuilder params = new StringBuilder("("); 366 for (ParameterInfo pInfo : mParameters) { 367 if (params.length() > 1) { 368 params.append(","); 369 } 370 params.append(pInfo.type().simpleTypeName()); 371 } 372 373 params.append(")"); 374 return params.toString(); 375 } 376 377 /** 378 * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}. 379 */ getHashableName()380 public String getHashableName() { 381 StringBuilder result = new StringBuilder(); 382 result.append(name()); 383 384 if (mParameters == null) { 385 return result.toString(); 386 } 387 388 int i = 0; 389 for (ParameterInfo param : mParameters) { 390 result.append(":"); 391 if (i == (mParameters.size()-1) && isVarArgs()) { 392 // TODO: note that this does not attempt to handle hypothetical 393 // vararg methods whose last parameter is a list of arrays, e.g. 394 // "Object[]...". 395 result.append(param.type().fullNameNoDimension(typeVariables())).append("..."); 396 } else { 397 result.append(param.type().fullName(typeVariables())); 398 } 399 i++; 400 } 401 return result.toString(); 402 } 403 inList(ClassInfo item, ThrowsTagInfo[] list)404 private boolean inList(ClassInfo item, ThrowsTagInfo[] list) { 405 int len = list.length; 406 String qn = item.qualifiedName(); 407 for (int i = 0; i < len; i++) { 408 ClassInfo ex = list[i].exception(); 409 if (ex != null && ex.qualifiedName().equals(qn)) { 410 return true; 411 } 412 } 413 return false; 414 } 415 throwsTags()416 public ThrowsTagInfo[] throwsTags() { 417 if (mThrowsTags == null) { 418 ThrowsTagInfo[] documented = comment().throwsTags(); 419 ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); 420 421 int len = documented.length; 422 for (int i = 0; i < len; i++) { 423 rv.add(documented[i]); 424 } 425 426 for (ClassInfo cl : mThrownExceptions) { 427 if (documented == null || !inList(cl, documented)) { 428 rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "", 429 containingClass(), position())); 430 } 431 } 432 433 mThrowsTags = rv.toArray(ThrowsTagInfo.getArray(rv.size())); 434 } 435 return mThrowsTags; 436 } 437 indexOfParam(String name, ParamTagInfo[] list)438 private static int indexOfParam(String name, ParamTagInfo[] list) { 439 final int N = list.length; 440 for (int i = 0; i < N; i++) { 441 if (name.equals(list[i].parameterName())) { 442 return i; 443 } 444 } 445 return -1; 446 } 447 448 /* Checks whether the name documented with the provided @param tag 449 * actually matches one of the method parameters. */ isParamTagInMethod(ParamTagInfo tag)450 private boolean isParamTagInMethod(ParamTagInfo tag) { 451 for (ParameterInfo paramInfo : mParameters) { 452 if (paramInfo.name().equals(tag.parameterName())) { 453 return true; 454 } 455 } 456 return false; 457 } 458 paramTags()459 public ParamTagInfo[] paramTags() { 460 if (mParamTags == null) { 461 final int N = mParameters.size(); 462 final String DEFAULT_COMMENT = "<!-- no parameter comment -->"; 463 464 if (N == 0) { 465 // Early out for empty case. 466 mParamTags = ParamTagInfo.EMPTY_ARRAY; 467 return ParamTagInfo.EMPTY_ARRAY; 468 } 469 // Where we put each result 470 mParamTags = ParamTagInfo.getArray(N); 471 472 // collect all the @param tag info 473 ParamTagInfo[] paramTags = comment().paramTags(); 474 475 // Complain about misnamed @param tags 476 for (ParamTagInfo tag : paramTags) { 477 if (!isParamTagInMethod(tag) && !tag.isTypeParameter()){ 478 Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), 479 "@param tag with name that doesn't match the parameter list: '" 480 + tag.parameterName() + "'"); 481 } 482 } 483 484 // Loop the parameters known from the method signature... 485 // Start by getting the known parameter name and data type. Then, if 486 // there's an @param tag that matches the current parameter name, get the 487 // javadoc comments. But if there's no @param comments here, then 488 // check if it's available from the parent class. 489 int i = 0; 490 for (ParameterInfo param : mParameters) { 491 String name = param.name(); 492 String type = param.type().simpleTypeName(); 493 String comment = DEFAULT_COMMENT; 494 SourcePositionInfo position = param.position(); 495 496 // Find the matching param from the @param tags in order to get 497 // the parameter comments 498 int index = indexOfParam(name, paramTags); 499 if (index >= 0) { 500 comment = paramTags[index].parameterComment(); 501 position = paramTags[index].position(); 502 } 503 504 // get our parent's tags to fill in the blanks 505 MethodInfo overridden = this.findOverriddenMethod(name(), signature()); 506 if (overridden != null) { 507 ParamTagInfo[] maternal = overridden.paramTags(); 508 if (comment.equals(DEFAULT_COMMENT)) { 509 comment = maternal[i].parameterComment(); 510 position = maternal[i].position(); 511 } 512 } 513 514 // Collect all docs requested by annotations 515 TagInfo[] auxTags = Doclava.auxSource.paramAuxTags(this, param, comment); 516 517 // Okay, now add the collected parameter information to the method data 518 mParamTags[i] = 519 new ParamTagInfo("@param", type, name + " " + comment, parent(), 520 position, auxTags); 521 522 // while we're here, if we find any parameters that are still 523 // undocumented at this point, complain. This warning is off by 524 // default, because it's really, really common; 525 // but, it's good to be able to enforce it. 526 if (comment.equals(DEFAULT_COMMENT)) { 527 Errors.error(Errors.UNDOCUMENTED_PARAMETER, position, 528 "Undocumented parameter '" + name + "' on method '" 529 + name() + "'"); 530 } else { 531 Doclava.linter.lintParameter(this, param, position, mParamTags[i]); 532 } 533 i++; 534 } 535 } 536 return mParamTags; 537 } 538 seeTags()539 public SeeTagInfo[] seeTags() { 540 SeeTagInfo[] result = comment().seeTags(); 541 if (result == null) { 542 if (mOverriddenMethod != null) { 543 result = mOverriddenMethod.seeTags(); 544 } 545 } 546 return result; 547 } 548 deprecatedTags()549 public TagInfo[] deprecatedTags() { 550 TagInfo[] result = comment().deprecatedTags(); 551 if (result.length == 0) { 552 if (comment().undeprecateTags().length == 0) { 553 if (mOverriddenMethod != null) { 554 result = mOverriddenMethod.deprecatedTags(); 555 } 556 } 557 } 558 return result; 559 } 560 parameters()561 public ArrayList<ParameterInfo> parameters() { 562 return mParameters; 563 } 564 565 matchesParams(String[] params, String[] dimensions, boolean varargs)566 public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) { 567 if (mParamStrings == null) { 568 if (mParameters.size() != params.length) { 569 return false; 570 } 571 int i = 0; 572 for (ParameterInfo mine : mParameters) { 573 // If the method we're matching against is a varargs method (varargs == true), then 574 // only its last parameter is varargs. 575 if (!mine.matchesDimension(dimensions[i], (i == params.length - 1) ? varargs : false)) { 576 return false; 577 } 578 TypeInfo myType = mine.type(); 579 String qualifiedName = myType.qualifiedTypeName(); 580 String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName(); 581 String s = params[i]; 582 583 // Check for a matching generic name or best known type 584 if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) { 585 return false; 586 } 587 i++; 588 } 589 } 590 return true; 591 } 592 593 /** 594 * Checks to see if a parameter from a method signature is 595 * compatible with a parameter given in a {@code @link} tag. 596 */ matchesType(String signatureParam, String callerParam)597 private boolean matchesType(String signatureParam, String callerParam) { 598 int signatureLength = signatureParam.length(); 599 int callerLength = callerParam.length(); 600 return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength 601 && signatureParam.charAt(signatureLength - callerLength - 1) == '.' 602 && signatureParam.endsWith(callerParam)))); 603 } 604 makeHDF(Data data, String base)605 public void makeHDF(Data data, String base) { 606 makeHDF(data, base, Collections.<String, TypeInfo>emptyMap()); 607 } 608 makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping)609 public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) { 610 data.setValue(base + ".kind", kind()); 611 data.setValue(base + ".name", name()); 612 data.setValue(base + ".href", htmlPage()); 613 data.setValue(base + ".anchor", anchor()); 614 615 if (mReturnType != null) { 616 returnType().getTypeWithArguments(typeMapping).makeHDF( 617 data, base + ".returnType", false, typeVariables()); 618 data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); 619 } 620 621 data.setValue(base + ".default", mIsDefault ? "default" : ""); 622 data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); 623 data.setValue(base + ".final", isFinal() ? "final" : ""); 624 data.setValue(base + ".static", isStatic() ? "static" : ""); 625 626 TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); 627 TagInfo.makeHDF(data, base + ".descr", inlineTags()); 628 TagInfo.makeHDF(data, base + ".descrAux", Doclava.auxSource.methodAuxTags(this)); 629 TagInfo.makeHDF(data, base + ".blockTags", blockTags()); 630 TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); 631 TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); 632 data.setValue(base + ".since", getSince()); 633 if (isDeprecated()) { 634 data.setValue(base + ".deprecatedsince", getDeprecatedSince()); 635 } 636 ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); 637 AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); 638 ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); 639 ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray( 640 new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables(), typeMapping); 641 if (isProtected()) { 642 data.setValue(base + ".scope", "protected"); 643 } else if (isPublic()) { 644 data.setValue(base + ".scope", "public"); 645 } 646 TagInfo.makeHDF(data, base + ".returns", returnTags()); 647 TagInfo.makeHDF(data, base + ".returnsAux", Doclava.auxSource.returnAuxTags(this)); 648 649 if (mTypeParameters != null) { 650 TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); 651 } 652 653 int numAnnotationDocumentation = 0; 654 for (AnnotationInstanceInfo aii : annotations()) { 655 String annotationDocumentation = Doclava.getDocumentationStringForAnnotation( 656 aii.type().qualifiedName()); 657 if (annotationDocumentation != null) { 658 data.setValue(base + ".annotationdocumentation." + numAnnotationDocumentation + ".text", 659 annotationDocumentation); 660 numAnnotationDocumentation++; 661 } 662 } 663 664 AnnotationInstanceInfo.makeLinkListHDF( 665 data, 666 base + ".showAnnotations", 667 showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()])); 668 669 setFederatedReferences(data, base); 670 671 Doclava.linter.lintMethod(this); 672 } 673 typeVariables()674 public HashSet<String> typeVariables() { 675 HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); 676 ClassInfo cl = containingClass(); 677 while (cl != null) { 678 ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments(); 679 if (types != null) { 680 TypeInfo.typeVariables(types, result); 681 } 682 cl = cl.containingClass(); 683 } 684 return result; 685 } 686 687 @Override isExecutable()688 public boolean isExecutable() { 689 return true; 690 } 691 thrownExceptions()692 public ArrayList<ClassInfo> thrownExceptions() { 693 return mThrownExceptions; 694 } 695 typeArgumentsName(HashSet<String> typeVars)696 public String typeArgumentsName(HashSet<String> typeVars) { 697 if (!hasTypeParameters()) { 698 return ""; 699 } else { 700 return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); 701 } 702 } 703 isAnnotationElement()704 public boolean isAnnotationElement() { 705 return mIsAnnotationElement; 706 } 707 defaultAnnotationElementValue()708 public AnnotationValueInfo defaultAnnotationElementValue() { 709 return mDefaultAnnotationElementValue; 710 } 711 setVarargs(boolean set)712 public void setVarargs(boolean set) { 713 mIsVarargs = set; 714 } 715 isVarArgs()716 public boolean isVarArgs() { 717 return mIsVarargs; 718 } 719 isEffectivelyFinal()720 public boolean isEffectivelyFinal() { 721 if (mIsFinal) { 722 return true; 723 } 724 ClassInfo containingClass = containingClass(); 725 if (containingClass != null && containingClass.isEffectivelyFinal()) { 726 return true; 727 } 728 return false; 729 } 730 731 @Override toString()732 public String toString() { 733 return this.name(); 734 } 735 736 @Override equals(Object o)737 public boolean equals(Object o) { 738 if (this == o) { 739 return true; 740 } else if (o instanceof MethodInfo) { 741 final MethodInfo m = (MethodInfo) o; 742 return mName.equals(m.mName) && signature().equals(m.signature()); 743 } else { 744 return false; 745 } 746 } 747 748 @Override hashCode()749 public int hashCode() { 750 return Objects.hash(mName, signature()); 751 } 752 setReason(String reason)753 public void setReason(String reason) { 754 mReasonOpened = reason; 755 } 756 getReason()757 public String getReason() { 758 return mReasonOpened; 759 } 760 addException(String exec)761 public void addException(String exec) { 762 ClassInfo exceptionClass = new ClassInfo(exec); 763 764 mThrownExceptions.add(exceptionClass); 765 } 766 addParameter(ParameterInfo p)767 public void addParameter(ParameterInfo p) { 768 // Name information 769 if (mParameters == null) { 770 mParameters = new ArrayList<ParameterInfo>(); 771 } 772 773 mParameters.add(p); 774 } 775 776 private String mFlatSignature; 777 private MethodInfo mOverriddenMethod; 778 private TypeInfo mReturnType; 779 private boolean mIsAnnotationElement; 780 private boolean mIsAbstract; 781 private boolean mIsSynchronized; 782 private boolean mIsNative; 783 private boolean mIsVarargs; 784 private boolean mDeprecatedKnown; 785 private boolean mIsDeprecated; 786 private boolean mIsDefault; 787 private ArrayList<ParameterInfo> mParameters; 788 private ArrayList<ClassInfo> mThrownExceptions; 789 private String[] mParamStrings; 790 private ThrowsTagInfo[] mThrowsTags; 791 private ParamTagInfo[] mParamTags; 792 private ArrayList<TypeInfo> mTypeParameters; 793 private AnnotationValueInfo mDefaultAnnotationElementValue; 794 private String mReasonOpened; 795 private ArrayList<Resolution> mResolutions; 796 797 // TODO: merge with droiddoc version (above) qualifiedName()798 public String qualifiedName() { 799 String parentQName = (containingClass() != null) 800 ? (containingClass().qualifiedName() + ".") : ""; 801 // TODO: This logic doesn't work well with constructors, as name() for constructors already 802 // contains the containingClass's name, leading to things like A.B.B() being rendered as A.B.A.B() 803 return parentQName + name(); 804 } 805 806 @Override signature()807 public String signature() { 808 if (mSignature == null) { 809 StringBuilder params = new StringBuilder("("); 810 for (ParameterInfo pInfo : mParameters) { 811 if (params.length() > 1) { 812 params.append(", "); 813 } 814 params.append(pInfo.type().fullName()); 815 } 816 817 params.append(")"); 818 mSignature = params.toString(); 819 } 820 return mSignature; 821 } 822 matches(MethodInfo other)823 public boolean matches(MethodInfo other) { 824 return prettySignature().equals(other.prettySignature()); 825 } 826 throwsException(ClassInfo exception)827 public boolean throwsException(ClassInfo exception) { 828 for (ClassInfo e : mThrownExceptions) { 829 if (e.qualifiedName().equals(exception.qualifiedName())) { 830 return true; 831 } 832 } 833 return false; 834 } 835 isConsistent(MethodInfo mInfo)836 public boolean isConsistent(MethodInfo mInfo) { 837 boolean consistent = true; 838 if (!mReturnType.isTypeVariable() && !mInfo.mReturnType.isTypeVariable()) { 839 if (!mReturnType.equals(mInfo.mReturnType) || 840 mReturnType.dimension() != mInfo.mReturnType.dimension()) { 841 consistent = false; 842 } 843 } else if (!mReturnType.isTypeVariable() && mInfo.mReturnType.isTypeVariable()) { 844 List<ClassInfo> constraints = mInfo.resolveConstraints(mInfo.mReturnType); 845 for (ClassInfo constraint : constraints) { 846 if (!constraint.isAssignableTo(mReturnType.qualifiedTypeName())) { 847 consistent = false; 848 } 849 } 850 } else if (mReturnType.isTypeVariable() && !mInfo.mReturnType.isTypeVariable()) { 851 // It's never valid to go from being a parameterized type to not being one. 852 // This would drop the implicit cast breaking backwards compatibility. 853 consistent = false; 854 } else { 855 // If both return types are parameterized then the constraints must be 856 // exactly the same. 857 List<ClassInfo> currentConstraints = mInfo.resolveConstraints(mReturnType); 858 List<ClassInfo> newConstraints = mInfo.resolveConstraints(mInfo.mReturnType); 859 if (currentConstraints.size() != newConstraints.size() || 860 currentConstraints.retainAll(newConstraints)) { 861 consistent = false; 862 } 863 } 864 865 if (!consistent) { 866 Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " 867 + mInfo.prettyQualifiedSignature() + " has changed return type from " + mReturnType 868 + " to " + mInfo.mReturnType); 869 } 870 871 if (mIsAbstract != mInfo.mIsAbstract) { 872 consistent = false; 873 Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " 874 + mInfo.prettyQualifiedSignature() + " has changed 'abstract' qualifier"); 875 } 876 877 if (mIsNative != mInfo.mIsNative) { 878 consistent = false; 879 Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " 880 + mInfo.prettyQualifiedSignature() + " has changed 'native' qualifier"); 881 } 882 883 if (!mIsStatic) { 884 // Compiler-generated methods vary in their 'final' qualifier between versions of 885 // the compiler, so this check needs to be quite narrow. A change in 'final' 886 // status of a method is only relevant if (a) the method is not declared 'static' 887 // and (b) the method is not already inferred to be 'final' by virtue of its class. 888 if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) { 889 consistent = false; 890 Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " 891 + mInfo.prettyQualifiedSignature() + " has added 'final' qualifier"); 892 } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) { 893 consistent = false; 894 Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " 895 + mInfo.prettyQualifiedSignature() + " has removed 'final' qualifier"); 896 } 897 } 898 899 if (mIsStatic != mInfo.mIsStatic) { 900 consistent = false; 901 Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " 902 + mInfo.prettyQualifiedSignature() + " has changed 'static' qualifier"); 903 } 904 905 if (!scope().equals(mInfo.scope())) { 906 consistent = false; 907 Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " 908 + mInfo.prettyQualifiedSignature() + " changed scope from " + scope() 909 + " to " + mInfo.scope()); 910 } 911 912 if (!isDeprecated() == mInfo.isDeprecated()) { 913 Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " 914 + mInfo.prettyQualifiedSignature() + " has changed deprecation state " + isDeprecated() 915 + " --> " + mInfo.isDeprecated()); 916 consistent = false; 917 } 918 919 // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break " 920 // "compatibility with existing binaries." 921 /* 922 if (mIsSynchronized != mInfo.mIsSynchronized) { 923 Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName() 924 + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to " 925 + mInfo.mIsSynchronized); 926 consistent = false; 927 } 928 */ 929 930 for (ClassInfo exception : thrownExceptions()) { 931 if (!mInfo.throwsException(exception)) { 932 // exclude 'throws' changes to finalize() overrides with no arguments 933 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 934 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " 935 + mInfo.prettyQualifiedSignature() + " no longer throws exception " 936 + exception.qualifiedName()); 937 consistent = false; 938 } 939 } 940 } 941 942 for (ClassInfo exec : mInfo.thrownExceptions()) { 943 // exclude 'throws' changes to finalize() overrides with no arguments 944 if (!throwsException(exec)) { 945 if (!name().equals("finalize") || (!mParameters.isEmpty())) { 946 Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " 947 + mInfo.prettyQualifiedSignature() + " added thrown exception " 948 + exec.qualifiedName()); 949 consistent = false; 950 } 951 } 952 } 953 954 return consistent; 955 } 956 getTypeParameter(String qualifiedTypeName)957 public TypeInfo getTypeParameter(String qualifiedTypeName) { 958 if (hasTypeParameters()) { 959 for (TypeInfo parameter : mTypeParameters) { 960 if (parameter.qualifiedTypeName().equals(qualifiedTypeName)) { 961 return parameter; 962 } 963 } 964 } 965 return containingClass().getTypeParameter(qualifiedTypeName); 966 } 967 968 // Given a type parameter it returns a list of all of the classes and interfaces it must extend 969 // and implement. resolveConstraints(TypeInfo type)970 private List<ClassInfo> resolveConstraints(TypeInfo type) { 971 ApiInfo api = containingClass().containingPackage().containingApi(); 972 List<ClassInfo> classes = new ArrayList<>(); 973 Queue<TypeInfo> types = new LinkedList<>(); 974 types.add(type); 975 while (!types.isEmpty()) { 976 type = types.poll(); 977 if (!type.isTypeVariable()) { 978 ClassInfo cl = api.findClass(type.qualifiedTypeName()); 979 if (cl != null) { 980 classes.add(cl); 981 } 982 } else { 983 TypeInfo parameter = getTypeParameter(type.qualifiedTypeName()); 984 if (parameter.extendsBounds() != null) { 985 for (TypeInfo bound : parameter.extendsBounds()) { 986 types.add(bound); 987 } 988 } 989 } 990 } 991 return classes; 992 } 993 printResolutions()994 public void printResolutions() { 995 if (mResolutions == null || mResolutions.isEmpty()) { 996 return; 997 } 998 999 System.out.println("Resolutions for Method " + mName + mFlatSignature + ":"); 1000 1001 for (Resolution r : mResolutions) { 1002 System.out.println(r); 1003 } 1004 } 1005 addResolution(Resolution resolution)1006 public void addResolution(Resolution resolution) { 1007 if (mResolutions == null) { 1008 mResolutions = new ArrayList<Resolution>(); 1009 } 1010 1011 mResolutions.add(resolution); 1012 } 1013 resolveResolutions()1014 public boolean resolveResolutions() { 1015 ArrayList<Resolution> resolutions = mResolutions; 1016 mResolutions = new ArrayList<Resolution>(); 1017 1018 boolean allResolved = true; 1019 for (Resolution resolution : resolutions) { 1020 StringBuilder qualifiedClassName = new StringBuilder(); 1021 InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName, 1022 resolution.getInfoBuilder()); 1023 1024 // if we still couldn't resolve it, save it for the next pass 1025 if ("".equals(qualifiedClassName.toString())) { 1026 mResolutions.add(resolution); 1027 allResolved = false; 1028 } else if ("thrownException".equals(resolution.getVariable())) { 1029 mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString())); 1030 } 1031 } 1032 1033 return allResolved; 1034 } 1035 } 1036