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