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 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.Comparator; 25 import java.util.HashSet; 26 import java.util.LinkedList; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.Objects; 30 import java.util.Queue; 31 import java.util.function.Predicate; 32 33 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable { 34 public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() { 35 @Override 36 public int compare(MethodInfo a, MethodInfo b) { 37 // TODO: expand to compare signature for better sorting 38 return a.name().compareTo(b.name()); 39 } 40 }; 41 42 private class InlineTags implements InheritedTags { tags()43 public TagInfo[] tags() { 44 return comment().tags(); 45 } 46 inherited()47 public InheritedTags inherited() { 48 MethodInfo m = findOverriddenMethod(name(), signature()); 49 if (m != null) { 50 return m.inlineTags(); 51 } else { 52 return null; 53 } 54 } 55 } 56 addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)57 private static void addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) { 58 for (ClassInfo i : ifaces) { 59 queue.add(i); 60 } 61 for (ClassInfo i : ifaces) { 62 addInterfaces(i.interfaces(), queue); 63 } 64 } 65 66 // first looks for a superclass, and then does a breadth first search to 67 // find the least far away match findOverriddenMethod(String name, String signature)68 public MethodInfo findOverriddenMethod(String name, String signature) { 69 if (mReturnType == null) { 70 // ctor 71 return null; 72 } 73 if (mOverriddenMethod != null) { 74 return mOverriddenMethod; 75 } 76 77 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 78 addInterfaces(containingClass().interfaces(), queue); 79 for (ClassInfo iface : queue) { 80 for (MethodInfo me : iface.methods()) { 81 if (me.name().equals(name) && me.signature().equals(signature) 82 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) { 83 return me; 84 } 85 } 86 } 87 return null; 88 } 89 addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)90 private static void addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) { 91 for (ClassInfo i : ifaces) { 92 queue.add(i); 93 if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) { 94 queue.add(i.superclass()); 95 } 96 } 97 for (ClassInfo i : ifaces) { 98 addInterfaces(i.realInterfaces(), queue); 99 } 100 } 101 findRealOverriddenMethod(String name, String signature, HashSet notStrippable)102 public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) { 103 if (mReturnType == null) { 104 // ctor 105 return null; 106 } 107 if (mOverriddenMethod != null) { 108 return mOverriddenMethod; 109 } 110 111 ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); 112 if (containingClass().realSuperclass() != null 113 && containingClass().realSuperclass().isAbstract()) { 114 queue.add(containingClass().realSuperclass()); 115 } 116 addInterfaces(containingClass().realInterfaces(), queue); 117 for (ClassInfo iface : queue) { 118 for (MethodInfo me : iface.methods()) { 119 if (me.name().equals(name) && me.signature().equals(signature) 120 && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0 121 && notStrippable.contains(me.containingClass())) { 122 return me; 123 } 124 } 125 } 126 return null; 127 } 128 findPredicateOverriddenMethod(Predicate<MemberInfo> predicate)129 public MethodInfo findPredicateOverriddenMethod(Predicate<MemberInfo> predicate) { 130 if (mReturnType == null) { 131 // ctor 132 return null; 133 } 134 if (mOverriddenMethod != null) { 135 if (equals(mOverriddenMethod) && !mOverriddenMethod.isStatic() 136 && predicate.test(mOverriddenMethod)) { 137 return mOverriddenMethod; 138 } 139 } 140 141 for (ClassInfo clazz : containingClass().gatherAncestorClasses()) { 142 for (MethodInfo method : clazz.getExhaustiveMethods()) { 143 if (equals(method) && !method.isStatic() && predicate.test(method)) { 144 return method; 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().realSuperclass()); 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 // Check to see that the JavaDoc contains @deprecated AND the method is marked as @Deprecated. 220 // Otherwise, warn. 221 // Note: We only do this for "included" classes (i.e. those we have source code for); we do 222 // not have comments for classes from .class files but we do know whether a method is marked 223 // as @Deprecated. 224 if (mContainingClass.isIncluded() && !isHiddenOrRemoved() 225 && commentDeprecated != annotationDeprecated) { 226 Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Method " 227 + mContainingClass.qualifiedName() + "." + name() 228 + ": @Deprecated annotation (" + (annotationDeprecated ? "" : "not ") 229 + "present) and @deprecated doc tag (" + (commentDeprecated ? "" : "not ") 230 + "present) do not match"); 231 } 232 233 mIsDeprecated = commentDeprecated | annotationDeprecated; 234 mDeprecatedKnown = true; 235 } 236 return mIsDeprecated; 237 } 238 setDeprecated(boolean deprecated)239 public void setDeprecated(boolean deprecated) { 240 mDeprecatedKnown = true; 241 mIsDeprecated = deprecated; 242 } 243 getTypeParameters()244 public ArrayList<TypeInfo> getTypeParameters() { 245 return mTypeParameters; 246 } 247 hasTypeParameters()248 public boolean hasTypeParameters() { 249 return mTypeParameters != null && !mTypeParameters.isEmpty(); 250 } 251 252 /** 253 * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the 254 * typeArgumentMapping to the parameters and return types. 255 */ cloneForClass(ClassInfo newContainingClass, Map<String, TypeInfo> typeArgumentMapping)256 public MethodInfo cloneForClass(ClassInfo newContainingClass, 257 Map<String, TypeInfo> typeArgumentMapping) { 258 if (newContainingClass == containingClass()) { 259 return this; 260 } 261 TypeInfo returnType = (mReturnType != null) 262 ? mReturnType.getTypeWithArguments(typeArgumentMapping) 263 : null; 264 ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>(); 265 for (ParameterInfo pi : mParameters) { 266 parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping)); 267 } 268 MethodInfo result = 269 new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(), 270 newContainingClass, realContainingClass(), isPublic(), isProtected(), 271 isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract, 272 mIsSynchronized, mIsNative, mIsDefault, mIsAnnotationElement, kind(), mFlatSignature, 273 mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(), 274 annotations()); 275 result.init(mDefaultAnnotationElementValue); 276 return result; 277 } 278 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)279 public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name, 280 String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, 281 boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, 282 boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized, 283 boolean isNative, boolean isDefault, boolean isAnnotationElement, String kind, 284 String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType, 285 ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions, 286 SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations) { 287 // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match 288 // the Java5-emitted base API description. 289 super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic, 290 isProtected, isPackagePrivate, isPrivate, 291 ((name.equals("values") && containingClass.isEnum()) ? true : isFinal), 292 isStatic, isSynthetic, kind, position, annotations); 293 294 mReasonOpened = "0:0"; 295 mIsAnnotationElement = isAnnotationElement; 296 mTypeParameters = typeParameters; 297 mIsAbstract = isAbstract; 298 mIsSynchronized = isSynchronized; 299 mIsNative = isNative; 300 mIsDefault = isDefault; 301 mFlatSignature = flatSignature; 302 mOverriddenMethod = overriddenMethod; 303 mReturnType = returnType; 304 mParameters = parameters; 305 mThrownExceptions = thrownExceptions; 306 } 307 init(AnnotationValueInfo defaultAnnotationElementValue)308 public void init(AnnotationValueInfo defaultAnnotationElementValue) { 309 mDefaultAnnotationElementValue = defaultAnnotationElementValue; 310 } 311 isAbstract()312 public boolean isAbstract() { 313 return mIsAbstract; 314 } 315 isSynchronized()316 public boolean isSynchronized() { 317 return mIsSynchronized; 318 } 319 isNative()320 public boolean isNative() { 321 return mIsNative; 322 } 323 isDefault()324 public boolean isDefault() { 325 return mIsDefault; 326 } 327 flatSignature()328 public String flatSignature() { 329 return mFlatSignature; 330 } 331 inlineTags()332 public InheritedTags inlineTags() { 333 return new InlineTags(); 334 } 335 blockTags()336 public TagInfo[] blockTags() { 337 return comment().blockTags(); 338 } 339 firstSentenceTags()340 public InheritedTags firstSentenceTags() { 341 return new FirstSentenceTags(); 342 } 343 returnTags()344 public InheritedTags returnTags() { 345 return new ReturnTags(); 346 } 347 returnType()348 public TypeInfo returnType() { 349 return mReturnType; 350 } 351 prettySignature()352 public String prettySignature() { 353 return name() + prettyParameters(); 354 } 355 prettyQualifiedSignature()356 public String prettyQualifiedSignature() { 357 return qualifiedName() + prettyParameters(); 358 } 359 360 /** 361 * Returns a printable version of the parameters of this method's signature. 362 */ prettyParameters()363 public String prettyParameters() { 364 StringBuilder params = new StringBuilder("("); 365 for (ParameterInfo pInfo : mParameters) { 366 if (params.length() > 1) { 367 params.append(","); 368 } 369 params.append(pInfo.type().simpleTypeName()); 370 } 371 372 params.append(")"); 373 return params.toString(); 374 } 375 376 /** 377 * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}. 378 */ getHashableName()379 public String getHashableName() { 380 StringBuilder result = new StringBuilder(); 381 result.append(name()); 382 383 if (mParameters == null) { 384 return result.toString(); 385 } 386 387 int i = 0; 388 for (ParameterInfo param : mParameters) { 389 result.append(":"); 390 if (i == (mParameters.size()-1) && isVarArgs()) { 391 // TODO: note that this does not attempt to handle hypothetical 392 // vararg methods whose last parameter is a list of arrays, e.g. 393 // "Object[]...". 394 result.append(param.type().fullNameNoDimension(typeVariables())).append("..."); 395 } else { 396 result.append(param.type().fullName(typeVariables())); 397 } 398 i++; 399 } 400 return result.toString(); 401 } 402 inList(ClassInfo item, ThrowsTagInfo[] list)403 private boolean inList(ClassInfo item, ThrowsTagInfo[] list) { 404 int len = list.length; 405 String qn = item.qualifiedName(); 406 for (int i = 0; i < len; i++) { 407 ClassInfo ex = list[i].exception(); 408 if (ex != null && ex.qualifiedName().equals(qn)) { 409 return true; 410 } 411 } 412 return false; 413 } 414 throwsTags()415 public ThrowsTagInfo[] throwsTags() { 416 if (mThrowsTags == null) { 417 ThrowsTagInfo[] documented = comment().throwsTags(); 418 ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); 419 420 int len = documented.length; 421 for (int i = 0; i < len; i++) { 422 rv.add(documented[i]); 423 } 424 425 for (ClassInfo cl : mThrownExceptions) { 426 if (documented == null || !inList(cl, documented)) { 427 rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "", 428 containingClass(), position())); 429 } 430 } 431 432 mThrowsTags = rv.toArray(ThrowsTagInfo.getArray(rv.size())); 433 } 434 return mThrowsTags; 435 } 436 indexOfParam(String name, ParamTagInfo[] list)437 private static int indexOfParam(String name, ParamTagInfo[] list) { 438 final int N = list.length; 439 for (int i = 0; i < N; i++) { 440 if (name.equals(list[i].parameterName())) { 441 return i; 442 } 443 } 444 return -1; 445 } 446 447 /* Checks whether the name documented with the provided @param tag 448 * actually matches one of the method parameters. */ isParamTagInMethod(ParamTagInfo tag)449 private boolean isParamTagInMethod(ParamTagInfo tag) { 450 for (ParameterInfo paramInfo : mParameters) { 451 if (paramInfo.name().equals(tag.parameterName())) { 452 return true; 453 } 454 } 455 return false; 456 } 457 paramTags()458 public ParamTagInfo[] paramTags() { 459 if (mParamTags == null) { 460 final int N = mParameters.size(); 461 final String DEFAULT_COMMENT = "<!-- no parameter comment -->"; 462 463 if (N == 0) { 464 // Early out for empty case. 465 mParamTags = ParamTagInfo.EMPTY_ARRAY; 466 return ParamTagInfo.EMPTY_ARRAY; 467 } 468 // Where we put each result 469 mParamTags = ParamTagInfo.getArray(N); 470 471 // collect all the @param tag info 472 ParamTagInfo[] paramTags = comment().paramTags(); 473 474 // Complain about misnamed @param tags 475 for (ParamTagInfo tag : paramTags) { 476 if (!isParamTagInMethod(tag) && !tag.isTypeParameter()){ 477 Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), 478 "@param tag with name that doesn't match the parameter list: '" 479 + tag.parameterName() + "'"); 480 } 481 } 482 483 // Loop the parameters known from the method signature... 484 // Start by getting the known parameter name and data type. Then, if 485 // there's an @param tag that matches the current parameter name, get the 486 // javadoc comments. But if there's no @param comments here, then 487 // check if it's available from the parent class. 488 int i = 0; 489 for (ParameterInfo param : mParameters) { 490 String name = param.name(); 491 String type = param.type().simpleTypeName(); 492 String comment = DEFAULT_COMMENT; 493 SourcePositionInfo position = param.position(); 494 495 // Find the matching param from the @param tags in order to get 496 // the parameter comments 497 int index = indexOfParam(name, paramTags); 498 if (index >= 0) { 499 comment = paramTags[index].parameterComment(); 500 position = paramTags[index].position(); 501 } 502 503 // get our parent's tags to fill in the blanks 504 MethodInfo overridden = this.findOverriddenMethod(name(), signature()); 505 if (overridden != null) { 506 ParamTagInfo[] maternal = overridden.paramTags(); 507 if (comment.equals(DEFAULT_COMMENT)) { 508 comment = maternal[i].parameterComment(); 509 position = maternal[i].position(); 510 } 511 } 512 513 // Collect all docs requested by annotations 514 TagInfo[] auxTags = Doclava.auxSource.paramAuxTags(this, param, comment); 515 516 // Okay, now add the collected parameter information to the method data 517 mParamTags[i] = 518 new ParamTagInfo("@param", type, name + " " + comment, parent(), 519 position, auxTags); 520 521 // while we're here, if we find any parameters that are still 522 // undocumented at this point, complain. This warning is off by 523 // default, because it's really, really common; 524 // but, it's good to be able to enforce it. 525 if (comment.equals(DEFAULT_COMMENT)) { 526 Errors.error(Errors.UNDOCUMENTED_PARAMETER, position, 527 "Undocumented parameter '" + name + "' on method '" 528 + name() + "'"); 529 } else { 530 Doclava.linter.lintParameter(this, param, position, mParamTags[i]); 531 } 532 i++; 533 } 534 } 535 return mParamTags; 536 } 537 seeTags()538 public SeeTagInfo[] seeTags() { 539 SeeTagInfo[] result = comment().seeTags(); 540 if (result == null) { 541 if (mOverriddenMethod != null) { 542 result = mOverriddenMethod.seeTags(); 543 } 544 } 545 return result; 546 } 547 deprecatedTags()548 public TagInfo[] deprecatedTags() { 549 TagInfo[] result = comment().deprecatedTags(); 550 if (result.length == 0) { 551 if (comment().undeprecateTags().length == 0) { 552 if (mOverriddenMethod != null) { 553 result = mOverriddenMethod.deprecatedTags(); 554 } 555 } 556 } 557 return result; 558 } 559 parameters()560 public ArrayList<ParameterInfo> parameters() { 561 return mParameters; 562 } 563 564 matchesParams(String[] params, String[] dimensions, boolean varargs)565 public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) { 566 if (mParamStrings == null) { 567 if (mParameters.size() != params.length) { 568 return false; 569 } 570 int i = 0; 571 for (ParameterInfo mine : mParameters) { 572 // If the method we're matching against is a varargs method (varargs == true), then 573 // only its last parameter is varargs. 574 if (!mine.matchesDimension(dimensions[i], (i == params.length - 1) ? varargs : false)) { 575 return false; 576 } 577 TypeInfo myType = mine.type(); 578 String qualifiedName = myType.qualifiedTypeName(); 579 String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName(); 580 String s = params[i]; 581 582 // Check for a matching generic name or best known type 583 if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) { 584 return false; 585 } 586 i++; 587 } 588 } 589 return true; 590 } 591 592 /** 593 * Checks to see if a parameter from a method signature is 594 * compatible with a parameter given in a {@code @link} tag. 595 */ matchesType(String signatureParam, String callerParam)596 private boolean matchesType(String signatureParam, String callerParam) { 597 int signatureLength = signatureParam.length(); 598 int callerLength = callerParam.length(); 599 return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength 600 && signatureParam.charAt(signatureLength - callerLength - 1) == '.' 601 && signatureParam.endsWith(callerParam)))); 602 } 603 makeHDF(Data data, String base)604 public void makeHDF(Data data, String base) { 605 makeHDF(data, base, Collections.<String, TypeInfo>emptyMap()); 606 } 607 makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping)608 public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) { 609 data.setValue(base + ".kind", kind()); 610 data.setValue(base + ".name", name()); 611 data.setValue(base + ".href", htmlPage()); 612 data.setValue(base + ".anchor", anchor()); 613 614 if (mReturnType != null) { 615 returnType().getTypeWithArguments(typeMapping).makeHDF( 616 data, base + ".returnType", false, typeVariables()); 617 data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); 618 } 619 620 data.setValue(base + ".default", mIsDefault ? "default" : ""); 621 data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); 622 data.setValue(base + ".final", isFinal() ? "final" : ""); 623 data.setValue(base + ".static", isStatic() ? "static" : ""); 624 625 TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); 626 TagInfo.makeHDF(data, base + ".descr", inlineTags()); 627 TagInfo.makeHDF(data, base + ".descrAux", Doclava.auxSource.methodAuxTags(this)); 628 TagInfo.makeHDF(data, base + ".blockTags", blockTags()); 629 TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); 630 TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); 631 data.setValue(base + ".since", getSince()); 632 data.setValue(base + ".sdkextsince", getSdkExtSince()); 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