Home | History | Annotate | Download | only in doclava
      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 {
     44     public TagInfo[] tags() {
     45       return comment().tags();
     46     }
     47 
     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 
     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
     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 
     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 
    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 
    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 
    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 {
    179     public TagInfo[] tags() {
    180       return comment().briefTags();
    181     }
    182 
    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 {
    194     public TagInfo[] tags() {
    195       return comment().returnTags();
    196     }
    197 
    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 
    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 
    240   public void setDeprecated(boolean deprecated) {
    241     mDeprecatedKnown = true;
    242     mIsDeprecated = deprecated;
    243   }
    244 
    245   public ArrayList<TypeInfo> getTypeParameters() {
    246     return mTypeParameters;
    247   }
    248 
    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    */
    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 
    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 
    309   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
    310     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
    311   }
    312 
    313   public boolean isAbstract() {
    314     return mIsAbstract;
    315   }
    316 
    317   public boolean isSynchronized() {
    318     return mIsSynchronized;
    319   }
    320 
    321   public boolean isNative() {
    322     return mIsNative;
    323   }
    324 
    325   public boolean isDefault() {
    326     return mIsDefault;
    327   }
    328 
    329   public String flatSignature() {
    330     return mFlatSignature;
    331   }
    332 
    333   public InheritedTags inlineTags() {
    334     return new InlineTags();
    335   }
    336 
    337   public TagInfo[] blockTags() {
    338     return comment().blockTags();
    339   }
    340 
    341   public InheritedTags firstSentenceTags() {
    342     return new FirstSentenceTags();
    343   }
    344 
    345   public InheritedTags returnTags() {
    346     return new ReturnTags();
    347   }
    348 
    349   public TypeInfo returnType() {
    350     return mReturnType;
    351   }
    352 
    353   public String prettySignature() {
    354     return name() + prettyParameters();
    355   }
    356 
    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    */
    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    */
    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 
    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 
    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 
    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. */
    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 
    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 
    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 
    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 
    561   public ArrayList<ParameterInfo> parameters() {
    562     return mParameters;
    563   }
    564 
    565 
    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    */
    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 
    605   public void makeHDF(Data data, String base) {
    606     makeHDF(data, base, Collections.<String, TypeInfo>emptyMap());
    607   }
    608 
    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 
    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
    688   public boolean isExecutable() {
    689     return true;
    690   }
    691 
    692   public ArrayList<ClassInfo> thrownExceptions() {
    693     return mThrownExceptions;
    694   }
    695 
    696   public String typeArgumentsName(HashSet<String> typeVars) {
    697     if (!hasTypeParameters()) {
    698       return "";
    699     } else {
    700       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
    701     }
    702   }
    703 
    704   public boolean isAnnotationElement() {
    705     return mIsAnnotationElement;
    706   }
    707 
    708   public AnnotationValueInfo defaultAnnotationElementValue() {
    709     return mDefaultAnnotationElementValue;
    710   }
    711 
    712   public void setVarargs(boolean set) {
    713     mIsVarargs = set;
    714   }
    715 
    716   public boolean isVarArgs() {
    717     return mIsVarargs;
    718   }
    719 
    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
    732   public String toString() {
    733     return this.name();
    734   }
    735 
    736   @Override
    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
    749   public int hashCode() {
    750     return Objects.hash(mName, signature());
    751   }
    752 
    753   public void setReason(String reason) {
    754     mReasonOpened = reason;
    755   }
    756 
    757   public String getReason() {
    758     return mReasonOpened;
    759   }
    760 
    761   public void addException(String exec) {
    762     ClassInfo exceptionClass = new ClassInfo(exec);
    763 
    764     mThrownExceptions.add(exceptionClass);
    765   }
    766 
    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)
    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
    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 
    823   public boolean matches(MethodInfo other) {
    824     return prettySignature().equals(other.prettySignature());
    825   }
    826 
    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 
    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 
    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.
    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 
    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 
   1006   public void addResolution(Resolution resolution) {
   1007       if (mResolutions == null) {
   1008           mResolutions = new ArrayList<Resolution>();
   1009       }
   1010 
   1011       mResolutions.add(resolution);
   1012   }
   1013 
   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