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 
     22 import java.util.*;
     23 
     24 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable {
     25   public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
     26     public int compare(MethodInfo a, MethodInfo b) {
     27         return a.name().compareTo(b.name());
     28     }
     29   };
     30 
     31   private class InlineTags implements InheritedTags {
     32     public TagInfo[] tags() {
     33       return comment().tags();
     34     }
     35 
     36     public InheritedTags inherited() {
     37       MethodInfo m = findOverriddenMethod(name(), signature());
     38       if (m != null) {
     39         return m.inlineTags();
     40       } else {
     41         return null;
     42       }
     43     }
     44   }
     45 
     46   private static void addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) {
     47     for (ClassInfo i : ifaces) {
     48       queue.add(i);
     49     }
     50     for (ClassInfo i : ifaces) {
     51       addInterfaces(i.interfaces(), queue);
     52     }
     53   }
     54 
     55   // first looks for a superclass, and then does a breadth first search to
     56   // find the least far away match
     57   public MethodInfo findOverriddenMethod(String name, String signature) {
     58     if (mReturnType == null) {
     59       // ctor
     60       return null;
     61     }
     62     if (mOverriddenMethod != null) {
     63       return mOverriddenMethod;
     64     }
     65 
     66     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
     67     addInterfaces(containingClass().interfaces(), queue);
     68     for (ClassInfo iface : queue) {
     69       for (MethodInfo me : iface.methods()) {
     70         if (me.name().equals(name) && me.signature().equals(signature)
     71             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) {
     72           return me;
     73         }
     74       }
     75     }
     76     return null;
     77   }
     78 
     79   private static void addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) {
     80     for (ClassInfo i : ifaces) {
     81       queue.add(i);
     82       if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) {
     83         queue.add(i.superclass());
     84       }
     85     }
     86     for (ClassInfo i : ifaces) {
     87       addInterfaces(i.realInterfaces(), queue);
     88     }
     89   }
     90 
     91   public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) {
     92     if (mReturnType == null) {
     93       // ctor
     94       return null;
     95     }
     96     if (mOverriddenMethod != null) {
     97       return mOverriddenMethod;
     98     }
     99 
    100     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
    101     if (containingClass().realSuperclass() != null
    102         && containingClass().realSuperclass().isAbstract()) {
    103       queue.add(containingClass());
    104     }
    105     addInterfaces(containingClass().realInterfaces(), queue);
    106     for (ClassInfo iface : queue) {
    107       for (MethodInfo me : iface.methods()) {
    108         if (me.name().equals(name) && me.signature().equals(signature)
    109             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0
    110             && notStrippable.contains(me.containingClass())) {
    111           return me;
    112         }
    113       }
    114     }
    115     return null;
    116   }
    117 
    118   public MethodInfo findSuperclassImplementation(HashSet notStrippable) {
    119     if (mReturnType == null) {
    120       // ctor
    121       return null;
    122     }
    123     if (mOverriddenMethod != null) {
    124       // Even if we're told outright that this was the overridden method, we want to
    125       // be conservative and ignore mismatches of parameter types -- they arise from
    126       // extending generic specializations, and we want to consider the derived-class
    127       // method to be a non-override.
    128       if (this.signature().equals(mOverriddenMethod.signature())) {
    129         return mOverriddenMethod;
    130       }
    131     }
    132 
    133     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
    134     if (containingClass().realSuperclass() != null
    135         && containingClass().realSuperclass().isAbstract()) {
    136       queue.add(containingClass());
    137     }
    138     addInterfaces(containingClass().realInterfaces(), queue);
    139     for (ClassInfo iface : queue) {
    140       for (MethodInfo me : iface.methods()) {
    141         if (me.name().equals(this.name()) && me.signature().equals(this.signature())
    142             && notStrippable.contains(me.containingClass())) {
    143           return me;
    144         }
    145       }
    146     }
    147     return null;
    148   }
    149 
    150   public ClassInfo findRealOverriddenClass(String name, String signature) {
    151     if (mReturnType == null) {
    152       // ctor
    153       return null;
    154     }
    155     if (mOverriddenMethod != null) {
    156       return mOverriddenMethod.mRealContainingClass;
    157     }
    158 
    159     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
    160     if (containingClass().realSuperclass() != null
    161         && containingClass().realSuperclass().isAbstract()) {
    162       queue.add(containingClass());
    163     }
    164     addInterfaces(containingClass().realInterfaces(), queue);
    165     for (ClassInfo iface : queue) {
    166       for (MethodInfo me : iface.methods()) {
    167         if (me.name().equals(name) && me.signature().equals(signature)
    168             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) {
    169           return iface;
    170         }
    171       }
    172     }
    173     return null;
    174   }
    175 
    176   private class FirstSentenceTags implements InheritedTags {
    177     public TagInfo[] tags() {
    178       return comment().briefTags();
    179     }
    180 
    181     public InheritedTags inherited() {
    182       MethodInfo m = findOverriddenMethod(name(), signature());
    183       if (m != null) {
    184         return m.firstSentenceTags();
    185       } else {
    186         return null;
    187       }
    188     }
    189   }
    190 
    191   private class ReturnTags implements InheritedTags {
    192     public TagInfo[] tags() {
    193       return comment().returnTags();
    194     }
    195 
    196     public InheritedTags inherited() {
    197       MethodInfo m = findOverriddenMethod(name(), signature());
    198       if (m != null) {
    199         return m.returnTags();
    200       } else {
    201         return null;
    202       }
    203     }
    204   }
    205 
    206   public boolean isDeprecated() {
    207     boolean deprecated = false;
    208     if (!mDeprecatedKnown) {
    209       boolean commentDeprecated = comment().isDeprecated();
    210       boolean annotationDeprecated = false;
    211       for (AnnotationInstanceInfo annotation : annotations()) {
    212         if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
    213           annotationDeprecated = true;
    214           break;
    215         }
    216       }
    217 
    218       if (commentDeprecated != annotationDeprecated) {
    219         Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Method "
    220             + mContainingClass.qualifiedName() + "." + name()
    221             + ": @Deprecated annotation and @deprecated doc tag do not match");
    222       }
    223 
    224       mIsDeprecated = commentDeprecated | annotationDeprecated;
    225       mDeprecatedKnown = true;
    226     }
    227     return mIsDeprecated;
    228   }
    229 
    230   public void setDeprecated(boolean deprecated) {
    231     mDeprecatedKnown = true;
    232     mIsDeprecated = deprecated;
    233   }
    234 
    235   public ArrayList<TypeInfo> getTypeParameters() {
    236     return mTypeParameters;
    237   }
    238 
    239   public MethodInfo cloneForClass(ClassInfo newContainingClass) {
    240     MethodInfo result =
    241         new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(),
    242             newContainingClass, realContainingClass(), isPublic(), isProtected(),
    243             isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract,
    244             mIsSynchronized, mIsNative, mIsAnnotationElement, kind(), mFlatSignature,
    245             mOverriddenMethod, mReturnType, mParameters, mThrownExceptions, position(),
    246             annotations());
    247     result.init(mDefaultAnnotationElementValue);
    248     return result;
    249   }
    250 
    251   public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name,
    252       String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic,
    253       boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal,
    254       boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized,
    255       boolean isNative, boolean isAnnotationElement, String kind, String flatSignature,
    256       MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters,
    257       ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position,
    258       ArrayList<AnnotationInstanceInfo> annotations) {
    259     // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
    260     // the Java5-emitted base API description.
    261     super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic,
    262         isProtected, isPackagePrivate, isPrivate,
    263         ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
    264         isStatic, isSynthetic, kind, position, annotations);
    265 
    266     // The underlying MethodDoc for an interface's declared methods winds up being marked
    267     // non-abstract. Correct that here by looking at the immediate-parent class, and marking
    268     // this method abstract if it is an unimplemented interface method.
    269     if (containingClass.isInterface()) {
    270       isAbstract = true;
    271     }
    272 
    273     mReasonOpened = "0:0";
    274     mIsAnnotationElement = isAnnotationElement;
    275     mTypeParameters = typeParameters;
    276     mIsAbstract = isAbstract;
    277     mIsSynchronized = isSynchronized;
    278     mIsNative = isNative;
    279     mFlatSignature = flatSignature;
    280     mOverriddenMethod = overriddenMethod;
    281     mReturnType = returnType;
    282     mParameters = parameters;
    283     mThrownExceptions = thrownExceptions;
    284   }
    285 
    286   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
    287     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
    288   }
    289 
    290   public boolean isAbstract() {
    291     return mIsAbstract;
    292   }
    293 
    294   public boolean isSynchronized() {
    295     return mIsSynchronized;
    296   }
    297 
    298   public boolean isNative() {
    299     return mIsNative;
    300   }
    301 
    302   public String flatSignature() {
    303     return mFlatSignature;
    304   }
    305 
    306   public InheritedTags inlineTags() {
    307     return new InlineTags();
    308   }
    309 
    310   public InheritedTags firstSentenceTags() {
    311     return new FirstSentenceTags();
    312   }
    313 
    314   public InheritedTags returnTags() {
    315     return new ReturnTags();
    316   }
    317 
    318   public TypeInfo returnType() {
    319     return mReturnType;
    320   }
    321 
    322   public String prettySignature() {
    323     return name() + prettyParameters();
    324   }
    325 
    326   /**
    327    * Returns a printable version of the parameters of this method's signature.
    328    */
    329   public String prettyParameters() {
    330     StringBuilder params = new StringBuilder("(");
    331     for (ParameterInfo pInfo : mParameters) {
    332       if (params.length() > 1) {
    333         params.append(",");
    334       }
    335       params.append(pInfo.type().simpleTypeName());
    336     }
    337 
    338     params.append(")");
    339     return params.toString();
    340   }
    341 
    342   /**
    343    * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}.
    344    */
    345   public String getHashableName() {
    346     StringBuilder result = new StringBuilder();
    347     result.append(name());
    348 
    349     if (mParameters == null) {
    350         return result.toString();
    351     }
    352 
    353     int i = 0;
    354     for (ParameterInfo param : mParameters) {
    355       result.append(":");
    356       if (i == (mParameters.size()-1) && isVarArgs()) {
    357         // TODO: note that this does not attempt to handle hypothetical
    358         // vararg methods whose last parameter is a list of arrays, e.g.
    359         // "Object[]...".
    360         result.append(param.type().fullNameNoDimension(typeVariables())).append("...");
    361       } else {
    362         result.append(param.type().fullName(typeVariables()));
    363       }
    364       i++;
    365     }
    366     return result.toString();
    367   }
    368 
    369   private boolean inList(ClassInfo item, ThrowsTagInfo[] list) {
    370     int len = list.length;
    371     String qn = item.qualifiedName();
    372     for (int i = 0; i < len; i++) {
    373       ClassInfo ex = list[i].exception();
    374       if (ex != null && ex.qualifiedName().equals(qn)) {
    375         return true;
    376       }
    377     }
    378     return false;
    379   }
    380 
    381   public ThrowsTagInfo[] throwsTags() {
    382     if (mThrowsTags == null) {
    383       ThrowsTagInfo[] documented = comment().throwsTags();
    384       ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
    385 
    386       int len = documented.length;
    387       for (int i = 0; i < len; i++) {
    388         rv.add(documented[i]);
    389       }
    390 
    391       for (ClassInfo cl : mThrownExceptions) {
    392         if (documented == null || !inList(cl, documented)) {
    393           rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "",
    394               containingClass(), position()));
    395         }
    396       }
    397       mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]);
    398     }
    399     return mThrowsTags;
    400   }
    401 
    402   private static int indexOfParam(String name, String[] list) {
    403     final int N = list.length;
    404     for (int i = 0; i < N; i++) {
    405       if (name.equals(list[i])) {
    406         return i;
    407       }
    408     }
    409     return -1;
    410   }
    411 
    412   public ParamTagInfo[] paramTags() {
    413     if (mParamTags == null) {
    414       final int N = mParameters.size();
    415 
    416       String[] names = new String[N];
    417       String[] comments = new String[N];
    418       SourcePositionInfo[] positions = new SourcePositionInfo[N];
    419 
    420       // get the right names so we can handle our names being different from
    421       // our parent's names.
    422       int i = 0;
    423       for (ParameterInfo param : mParameters) {
    424         names[i] = param.name();
    425         comments[i] = "";
    426         positions[i] = param.position();
    427         i++;
    428       }
    429 
    430       // gather our comments, and complain about misnamed @param tags
    431       for (ParamTagInfo tag : comment().paramTags()) {
    432         int index = indexOfParam(tag.parameterName(), names);
    433         if (index >= 0) {
    434           comments[index] = tag.parameterComment();
    435           positions[index] = tag.position();
    436         } else {
    437           Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
    438               "@param tag with name that doesn't match the parameter list: '" + tag.parameterName()
    439                   + "'");
    440         }
    441       }
    442 
    443       // get our parent's tags to fill in the blanks
    444       MethodInfo overridden = this.findOverriddenMethod(name(), signature());
    445       if (overridden != null) {
    446         ParamTagInfo[] maternal = overridden.paramTags();
    447         for (i = 0; i < N; i++) {
    448           if (comments[i].equals("")) {
    449             comments[i] = maternal[i].parameterComment();
    450             positions[i] = maternal[i].position();
    451           }
    452         }
    453       }
    454 
    455       // construct the results, and cache them for next time
    456       mParamTags = new ParamTagInfo[N];
    457       for (i = 0; i < N; i++) {
    458         mParamTags[i] =
    459             new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], parent(),
    460                 positions[i]);
    461 
    462         // while we're here, if we find any parameters that are still undocumented at this
    463         // point, complain. (this warning is off by default, because it's really, really
    464         // common; but, it's good to be able to enforce it)
    465         if (comments[i].equals("")) {
    466           Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], "Undocumented parameter '"
    467               + names[i] + "' on method '" + name() + "'");
    468         }
    469       }
    470     }
    471     return mParamTags;
    472   }
    473 
    474   public SeeTagInfo[] seeTags() {
    475     SeeTagInfo[] result = comment().seeTags();
    476     if (result == null) {
    477       if (mOverriddenMethod != null) {
    478         result = mOverriddenMethod.seeTags();
    479       }
    480     }
    481     return result;
    482   }
    483 
    484   public TagInfo[] deprecatedTags() {
    485     TagInfo[] result = comment().deprecatedTags();
    486     if (result.length == 0) {
    487       if (comment().undeprecateTags().length == 0) {
    488         if (mOverriddenMethod != null) {
    489           result = mOverriddenMethod.deprecatedTags();
    490         }
    491       }
    492     }
    493     return result;
    494   }
    495 
    496   public ArrayList<ParameterInfo> parameters() {
    497     return mParameters;
    498   }
    499 
    500 
    501   public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) {
    502     if (mParamStrings == null) {
    503       if (mParameters.size() != params.length) {
    504         return false;
    505       }
    506       int i = 0;
    507       for (ParameterInfo mine : mParameters) {
    508         if (!mine.matchesDimension(dimensions[i], varargs)) {
    509           return false;
    510         }
    511         TypeInfo myType = mine.type();
    512         String qualifiedName = myType.qualifiedTypeName();
    513         String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName();
    514         String s = params[i];
    515         int slen = s.length();
    516         int qnlen = qualifiedName.length();
    517 
    518         // Check for a matching generic name or best known type
    519         if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) {
    520           return false;
    521         }
    522         i++;
    523       }
    524     }
    525     return true;
    526   }
    527 
    528   /**
    529    * Checks to see if a parameter from a method signature is
    530    * compatible with a parameter given in a {@code @link} tag.
    531    */
    532   private boolean matchesType(String signatureParam, String callerParam) {
    533     int signatureLength = signatureParam.length();
    534     int callerLength = callerParam.length();
    535     return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength
    536         && signatureParam.charAt(signatureLength - callerLength - 1) == '.'
    537         && signatureParam.endsWith(callerParam))));
    538   }
    539 
    540   public void makeHDF(Data data, String base) {
    541     data.setValue(base + ".kind", kind());
    542     data.setValue(base + ".name", name());
    543     data.setValue(base + ".href", htmlPage());
    544     data.setValue(base + ".anchor", anchor());
    545 
    546     if (mReturnType != null) {
    547       returnType().makeHDF(data, base + ".returnType", false, typeVariables());
    548       data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
    549     }
    550 
    551     data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
    552     data.setValue(base + ".final", isFinal() ? "final" : "");
    553     data.setValue(base + ".static", isStatic() ? "static" : "");
    554 
    555     TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
    556     TagInfo.makeHDF(data, base + ".descr", inlineTags());
    557     TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
    558     TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
    559     data.setValue(base + ".since", getSince());
    560     if (isDeprecated()) {
    561       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
    562     }
    563     ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
    564     AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
    565     ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
    566     ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables());
    567     if (isProtected()) {
    568       data.setValue(base + ".scope", "protected");
    569     } else if (isPublic()) {
    570       data.setValue(base + ".scope", "public");
    571     }
    572     TagInfo.makeHDF(data, base + ".returns", returnTags());
    573 
    574     if (mTypeParameters != null) {
    575       TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
    576     }
    577 
    578     setFederatedReferences(data, base);
    579   }
    580 
    581   public HashSet<String> typeVariables() {
    582     HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
    583     ClassInfo cl = containingClass();
    584     while (cl != null) {
    585         ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments();
    586       if (types != null) {
    587         TypeInfo.typeVariables(types, result);
    588       }
    589       cl = cl.containingClass();
    590     }
    591     return result;
    592   }
    593 
    594   @Override
    595   public boolean isExecutable() {
    596     return true;
    597   }
    598 
    599   public ArrayList<ClassInfo> thrownExceptions() {
    600     return mThrownExceptions;
    601   }
    602 
    603   public String typeArgumentsName(HashSet<String> typeVars) {
    604     if (mTypeParameters == null || mTypeParameters.isEmpty()) {
    605       return "";
    606     } else {
    607       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
    608     }
    609   }
    610 
    611   public boolean isAnnotationElement() {
    612     return mIsAnnotationElement;
    613   }
    614 
    615   public AnnotationValueInfo defaultAnnotationElementValue() {
    616     return mDefaultAnnotationElementValue;
    617   }
    618 
    619   public void setVarargs(boolean set) {
    620     mIsVarargs = set;
    621   }
    622 
    623   public boolean isVarArgs() {
    624     return mIsVarargs;
    625   }
    626 
    627   public boolean isEffectivelyFinal() {
    628       if (mIsFinal) {
    629           return true;
    630       }
    631       ClassInfo containingClass = containingClass();
    632       if (containingClass != null && containingClass.isEffectivelyFinal()) {
    633           return true;
    634       }
    635       return false;
    636   }
    637 
    638   @Override
    639   public String toString() {
    640     return this.name();
    641   }
    642 
    643   public void setReason(String reason) {
    644     mReasonOpened = reason;
    645   }
    646 
    647   public String getReason() {
    648     return mReasonOpened;
    649   }
    650 
    651   public void addException(String exec) {
    652     ClassInfo exceptionClass = new ClassInfo(exec);
    653 
    654     mThrownExceptions.add(exceptionClass);
    655   }
    656 
    657   public void addParameter(ParameterInfo p) {
    658     // Name information
    659     if (mParameters == null) {
    660         mParameters = new ArrayList<ParameterInfo>();
    661     }
    662 
    663     mParameters.add(p);
    664   }
    665 
    666   private String mFlatSignature;
    667   private MethodInfo mOverriddenMethod;
    668   private TypeInfo mReturnType;
    669   private boolean mIsAnnotationElement;
    670   private boolean mIsAbstract;
    671   private boolean mIsSynchronized;
    672   private boolean mIsNative;
    673   private boolean mIsVarargs;
    674   private boolean mDeprecatedKnown;
    675   private boolean mIsDeprecated;
    676   private ArrayList<ParameterInfo> mParameters;
    677   private ArrayList<ClassInfo> mThrownExceptions;
    678   private String[] mParamStrings;
    679   private ThrowsTagInfo[] mThrowsTags;
    680   private ParamTagInfo[] mParamTags;
    681   private ArrayList<TypeInfo> mTypeParameters;
    682   private AnnotationValueInfo mDefaultAnnotationElementValue;
    683   private String mReasonOpened;
    684   private ArrayList<Resolution> mResolutions;
    685 
    686   // TODO: merge with droiddoc version (above)
    687   public String qualifiedName() {
    688     String parentQName = (containingClass() != null)
    689         ? (containingClass().qualifiedName() + ".") : "";
    690     return parentQName + name();
    691   }
    692 
    693   @Override
    694   public String signature() {
    695     if (mSignature == null) {
    696       StringBuilder params = new StringBuilder("(");
    697       for (ParameterInfo pInfo : mParameters) {
    698         if (params.length() > 1) {
    699           params.append(", ");
    700         }
    701         params.append(pInfo.type().fullName());
    702       }
    703 
    704       params.append(")");
    705       mSignature = params.toString();
    706     }
    707     return mSignature;
    708   }
    709 
    710   public boolean matches(MethodInfo other) {
    711     return prettySignature().equals(other.prettySignature());
    712   }
    713 
    714   public boolean throwsException(ClassInfo exception) {
    715     for (ClassInfo e : mThrownExceptions) {
    716       if (e.qualifiedName().equals(exception.qualifiedName())) {
    717         return true;
    718       }
    719     }
    720     return false;
    721   }
    722 
    723   public boolean isConsistent(MethodInfo mInfo) {
    724     boolean consistent = true;
    725     if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) {
    726       consistent = false;
    727       Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName()
    728           + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType);
    729     }
    730 
    731     if (mIsAbstract != mInfo.mIsAbstract) {
    732       consistent = false;
    733       Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName()
    734           + " has changed 'abstract' qualifier");
    735     }
    736 
    737     if (mIsNative != mInfo.mIsNative) {
    738       consistent = false;
    739       Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName()
    740           + " has changed 'native' qualifier");
    741     }
    742 
    743     if (!mIsStatic) {
    744       // Compiler-generated methods vary in their 'final' qualifier between versions of
    745       // the compiler, so this check needs to be quite narrow. A change in 'final'
    746       // status of a method is only relevant if (a) the method is not declared 'static'
    747       // and (b) the method is not already inferred to be 'final' by virtue of its class.
    748       if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) {
    749         consistent = false;
    750         Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
    751             + " has added 'final' qualifier");
    752       } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) {
    753         consistent = false;
    754         Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
    755             + " has removed 'final' qualifier");
    756       }
    757     }
    758 
    759     if (mIsStatic != mInfo.mIsStatic) {
    760       consistent = false;
    761       Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName()
    762           + " has changed 'static' qualifier");
    763     }
    764 
    765     if (!scope().equals(mInfo.scope())) {
    766       consistent = false;
    767       Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName()
    768           + " changed scope from " + scope() + " to " + mInfo.scope());
    769     }
    770 
    771     if (!isDeprecated() == mInfo.isDeprecated()) {
    772       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName()
    773           + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated());
    774       consistent = false;
    775     }
    776 
    777     // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
    778     // "compatibility with existing binaries."
    779     /*
    780     if (mIsSynchronized != mInfo.mIsSynchronized) {
    781       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
    782           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
    783           + mInfo.mIsSynchronized);
    784       consistent = false;
    785     }
    786     */
    787 
    788     for (ClassInfo exception : thrownExceptions()) {
    789       if (!mInfo.throwsException(exception)) {
    790         // exclude 'throws' changes to finalize() overrides with no arguments
    791         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
    792           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
    793               + " no longer throws exception " + exception.qualifiedName());
    794           consistent = false;
    795         }
    796       }
    797     }
    798 
    799     for (ClassInfo exec : mInfo.thrownExceptions()) {
    800       // exclude 'throws' changes to finalize() overrides with no arguments
    801       if (!throwsException(exec)) {
    802         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
    803           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
    804               + " added thrown exception " + exec.qualifiedName());
    805           consistent = false;
    806         }
    807       }
    808     }
    809 
    810     return consistent;
    811   }
    812 
    813   public void printResolutions() {
    814       if (mResolutions == null || mResolutions.isEmpty()) {
    815           return;
    816       }
    817 
    818       System.out.println("Resolutions for Method " + mName + mFlatSignature + ":");
    819 
    820       for (Resolution r : mResolutions) {
    821           System.out.println(r);
    822       }
    823   }
    824 
    825   public void addResolution(Resolution resolution) {
    826       if (mResolutions == null) {
    827           mResolutions = new ArrayList<Resolution>();
    828       }
    829 
    830       mResolutions.add(resolution);
    831   }
    832 
    833   public boolean resolveResolutions() {
    834       ArrayList<Resolution> resolutions = mResolutions;
    835       mResolutions = new ArrayList<Resolution>();
    836 
    837       boolean allResolved = true;
    838       for (Resolution resolution : resolutions) {
    839           StringBuilder qualifiedClassName = new StringBuilder();
    840           InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
    841                   resolution.getInfoBuilder());
    842 
    843           // if we still couldn't resolve it, save it for the next pass
    844           if ("".equals(qualifiedClassName.toString())) {
    845               mResolutions.add(resolution);
    846               allResolved = false;
    847           } else if ("thrownException".equals(resolution.getVariable())) {
    848               mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString()));
    849           }
    850       }
    851 
    852       return allResolved;
    853   }
    854 }
    855