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