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     setFederatedReferences(data, base);
    580   }
    581 
    582   public HashSet<String> typeVariables() {
    583     HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
    584     ClassInfo cl = containingClass();
    585     while (cl != null) {
    586         ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments();
    587       if (types != null) {
    588         TypeInfo.typeVariables(types, result);
    589       }
    590       cl = cl.containingClass();
    591     }
    592     return result;
    593   }
    594 
    595   @Override
    596   public boolean isExecutable() {
    597     return true;
    598   }
    599 
    600   public ArrayList<ClassInfo> thrownExceptions() {
    601     return mThrownExceptions;
    602   }
    603 
    604   public String typeArgumentsName(HashSet<String> typeVars) {
    605     if (mTypeParameters == null || mTypeParameters.isEmpty()) {
    606       return "";
    607     } else {
    608       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
    609     }
    610   }
    611 
    612   public boolean isAnnotationElement() {
    613     return mIsAnnotationElement;
    614   }
    615 
    616   public AnnotationValueInfo defaultAnnotationElementValue() {
    617     return mDefaultAnnotationElementValue;
    618   }
    619 
    620   public void setVarargs(boolean set) {
    621     mIsVarargs = set;
    622   }
    623 
    624   public boolean isVarArgs() {
    625     return mIsVarargs;
    626   }
    627 
    628   public boolean isEffectivelyFinal() {
    629       if (mIsFinal) {
    630           return true;
    631       }
    632       ClassInfo containingClass = containingClass();
    633       if (containingClass != null && containingClass.isEffectivelyFinal()) {
    634           return true;
    635       }
    636       return false;
    637   }
    638 
    639   @Override
    640   public String toString() {
    641     return this.name();
    642   }
    643 
    644   public void setReason(String reason) {
    645     mReasonOpened = reason;
    646   }
    647 
    648   public String getReason() {
    649     return mReasonOpened;
    650   }
    651 
    652   public void addException(String exec) {
    653     ClassInfo exceptionClass = new ClassInfo(exec);
    654 
    655     mThrownExceptions.add(exceptionClass);
    656   }
    657 
    658   public void addParameter(ParameterInfo p) {
    659     // Name information
    660     if (mParameters == null) {
    661         mParameters = new ArrayList<ParameterInfo>();
    662     }
    663 
    664     mParameters.add(p);
    665   }
    666 
    667   private String mFlatSignature;
    668   private MethodInfo mOverriddenMethod;
    669   private TypeInfo mReturnType;
    670   private boolean mIsAnnotationElement;
    671   private boolean mIsAbstract;
    672   private boolean mIsSynchronized;
    673   private boolean mIsNative;
    674   private boolean mIsVarargs;
    675   private boolean mDeprecatedKnown;
    676   private boolean mIsDeprecated;
    677   private ArrayList<ParameterInfo> mParameters;
    678   private ArrayList<ClassInfo> mThrownExceptions;
    679   private String[] mParamStrings;
    680   private ThrowsTagInfo[] mThrowsTags;
    681   private ParamTagInfo[] mParamTags;
    682   private ArrayList<TypeInfo> mTypeParameters;
    683   private AnnotationValueInfo mDefaultAnnotationElementValue;
    684   private String mReasonOpened;
    685   private ArrayList<Resolution> mResolutions;
    686 
    687   // TODO: merge with droiddoc version (above)
    688   public String qualifiedName() {
    689     String parentQName = (containingClass() != null)
    690         ? (containingClass().qualifiedName() + ".") : "";
    691     return parentQName + name();
    692   }
    693 
    694   @Override
    695   public String signature() {
    696     if (mSignature == null) {
    697       StringBuilder params = new StringBuilder("(");
    698       for (ParameterInfo pInfo : mParameters) {
    699         if (params.length() > 1) {
    700           params.append(", ");
    701         }
    702         params.append(pInfo.type().fullName());
    703       }
    704 
    705       params.append(")");
    706       mSignature = params.toString();
    707     }
    708     return mSignature;
    709   }
    710 
    711   public boolean matches(MethodInfo other) {
    712     return prettySignature().equals(other.prettySignature());
    713   }
    714 
    715   public boolean throwsException(ClassInfo exception) {
    716     for (ClassInfo e : mThrownExceptions) {
    717       if (e.qualifiedName().equals(exception.qualifiedName())) {
    718         return true;
    719       }
    720     }
    721     return false;
    722   }
    723 
    724   public boolean isConsistent(MethodInfo mInfo) {
    725     boolean consistent = true;
    726     if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) {
    727       if (!mReturnType.isPrimitive() && !mInfo.mReturnType.isPrimitive()) {
    728         // Check to see if our class extends the old class.
    729         ApiInfo infoApi = mInfo.containingClass().containingPackage().containingApi();
    730         ClassInfo infoReturnClass = infoApi.findClass(mInfo.mReturnType.qualifiedTypeName());
    731         // Find the classes.
    732         consistent = infoReturnClass != null &&
    733                      infoReturnClass.isAssignableTo(mReturnType.qualifiedTypeName());
    734       } else {
    735         consistent = false;
    736       }
    737 
    738       if (!consistent) {
    739         Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName()
    740             + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType);
    741       }
    742     }
    743 
    744     if (mIsAbstract != mInfo.mIsAbstract) {
    745       consistent = false;
    746       Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName()
    747           + " has changed 'abstract' qualifier");
    748     }
    749 
    750     if (mIsNative != mInfo.mIsNative) {
    751       consistent = false;
    752       Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName()
    753           + " has changed 'native' qualifier");
    754     }
    755 
    756     if (!mIsStatic) {
    757       // Compiler-generated methods vary in their 'final' qualifier between versions of
    758       // the compiler, so this check needs to be quite narrow. A change in 'final'
    759       // status of a method is only relevant if (a) the method is not declared 'static'
    760       // and (b) the method is not already inferred to be 'final' by virtue of its class.
    761       if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) {
    762         consistent = false;
    763         Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
    764             + " has added 'final' qualifier");
    765       } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) {
    766         consistent = false;
    767         Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
    768             + " has removed 'final' qualifier");
    769       }
    770     }
    771 
    772     if (mIsStatic != mInfo.mIsStatic) {
    773       consistent = false;
    774       Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName()
    775           + " has changed 'static' qualifier");
    776     }
    777 
    778     if (!scope().equals(mInfo.scope())) {
    779       consistent = false;
    780       Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName()
    781           + " changed scope from " + scope() + " to " + mInfo.scope());
    782     }
    783 
    784     if (!isDeprecated() == mInfo.isDeprecated()) {
    785       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName()
    786           + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated());
    787       consistent = false;
    788     }
    789 
    790     // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
    791     // "compatibility with existing binaries."
    792     /*
    793     if (mIsSynchronized != mInfo.mIsSynchronized) {
    794       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
    795           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
    796           + mInfo.mIsSynchronized);
    797       consistent = false;
    798     }
    799     */
    800 
    801     for (ClassInfo exception : thrownExceptions()) {
    802       if (!mInfo.throwsException(exception)) {
    803         // exclude 'throws' changes to finalize() overrides with no arguments
    804         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
    805           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
    806               + " no longer throws exception " + exception.qualifiedName());
    807           consistent = false;
    808         }
    809       }
    810     }
    811 
    812     for (ClassInfo exec : mInfo.thrownExceptions()) {
    813       // exclude 'throws' changes to finalize() overrides with no arguments
    814       if (!throwsException(exec)) {
    815         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
    816           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
    817               + " added thrown exception " + exec.qualifiedName());
    818           consistent = false;
    819         }
    820       }
    821     }
    822 
    823     return consistent;
    824   }
    825 
    826   public void printResolutions() {
    827       if (mResolutions == null || mResolutions.isEmpty()) {
    828           return;
    829       }
    830 
    831       System.out.println("Resolutions for Method " + mName + mFlatSignature + ":");
    832 
    833       for (Resolution r : mResolutions) {
    834           System.out.println(r);
    835       }
    836   }
    837 
    838   public void addResolution(Resolution resolution) {
    839       if (mResolutions == null) {
    840           mResolutions = new ArrayList<Resolution>();
    841       }
    842 
    843       mResolutions.add(resolution);
    844   }
    845 
    846   public boolean resolveResolutions() {
    847       ArrayList<Resolution> resolutions = mResolutions;
    848       mResolutions = new ArrayList<Resolution>();
    849 
    850       boolean allResolved = true;
    851       for (Resolution resolution : resolutions) {
    852           StringBuilder qualifiedClassName = new StringBuilder();
    853           InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
    854                   resolution.getInfoBuilder());
    855 
    856           // if we still couldn't resolve it, save it for the next pass
    857           if ("".equals(qualifiedClassName.toString())) {
    858               mResolutions.add(resolution);
    859               allResolved = false;
    860           } else if ("thrownException".equals(resolution.getVariable())) {
    861               mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString()));
    862           }
    863       }
    864 
    865       return allResolved;
    866   }
    867 }
    868