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   /**
    241    * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the
    242    * typeArgumentMapping to the parameters and return types.
    243    */
    244   public MethodInfo cloneForClass(ClassInfo newContainingClass,
    245       Map<String, TypeInfo> typeArgumentMapping) {
    246     TypeInfo returnType = mReturnType.getTypeWithArguments(typeArgumentMapping);
    247     ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
    248     for (ParameterInfo pi : mParameters) {
    249       parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping));
    250     }
    251     MethodInfo result =
    252         new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(),
    253             newContainingClass, realContainingClass(), isPublic(), isProtected(),
    254             isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract,
    255             mIsSynchronized, mIsNative, mIsAnnotationElement, kind(), mFlatSignature,
    256             mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(),
    257             annotations());
    258     result.init(mDefaultAnnotationElementValue);
    259     return result;
    260   }
    261 
    262   public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name,
    263       String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic,
    264       boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal,
    265       boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized,
    266       boolean isNative, boolean isAnnotationElement, String kind, String flatSignature,
    267       MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters,
    268       ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position,
    269       ArrayList<AnnotationInstanceInfo> annotations) {
    270     // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
    271     // the Java5-emitted base API description.
    272     super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic,
    273         isProtected, isPackagePrivate, isPrivate,
    274         ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
    275         isStatic, isSynthetic, kind, position, annotations);
    276 
    277     // The underlying MethodDoc for an interface's declared methods winds up being marked
    278     // non-abstract. Correct that here by looking at the immediate-parent class, and marking
    279     // this method abstract if it is an unimplemented interface method.
    280     if (containingClass.isInterface()) {
    281       isAbstract = true;
    282     }
    283 
    284     mReasonOpened = "0:0";
    285     mIsAnnotationElement = isAnnotationElement;
    286     mTypeParameters = typeParameters;
    287     mIsAbstract = isAbstract;
    288     mIsSynchronized = isSynchronized;
    289     mIsNative = isNative;
    290     mFlatSignature = flatSignature;
    291     mOverriddenMethod = overriddenMethod;
    292     mReturnType = returnType;
    293     mParameters = parameters;
    294     mThrownExceptions = thrownExceptions;
    295   }
    296 
    297   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
    298     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
    299   }
    300 
    301   public boolean isAbstract() {
    302     return mIsAbstract;
    303   }
    304 
    305   public boolean isSynchronized() {
    306     return mIsSynchronized;
    307   }
    308 
    309   public boolean isNative() {
    310     return mIsNative;
    311   }
    312 
    313   public String flatSignature() {
    314     return mFlatSignature;
    315   }
    316 
    317   public InheritedTags inlineTags() {
    318     return new InlineTags();
    319   }
    320 
    321   public InheritedTags firstSentenceTags() {
    322     return new FirstSentenceTags();
    323   }
    324 
    325   public InheritedTags returnTags() {
    326     return new ReturnTags();
    327   }
    328 
    329   public TypeInfo returnType() {
    330     return mReturnType;
    331   }
    332 
    333   public String prettySignature() {
    334     return name() + prettyParameters();
    335   }
    336 
    337   /**
    338    * Returns a printable version of the parameters of this method's signature.
    339    */
    340   public String prettyParameters() {
    341     StringBuilder params = new StringBuilder("(");
    342     for (ParameterInfo pInfo : mParameters) {
    343       if (params.length() > 1) {
    344         params.append(",");
    345       }
    346       params.append(pInfo.type().simpleTypeName());
    347     }
    348 
    349     params.append(")");
    350     return params.toString();
    351   }
    352 
    353   /**
    354    * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}.
    355    */
    356   public String getHashableName() {
    357     StringBuilder result = new StringBuilder();
    358     result.append(name());
    359 
    360     if (mParameters == null) {
    361         return result.toString();
    362     }
    363 
    364     int i = 0;
    365     for (ParameterInfo param : mParameters) {
    366       result.append(":");
    367       if (i == (mParameters.size()-1) && isVarArgs()) {
    368         // TODO: note that this does not attempt to handle hypothetical
    369         // vararg methods whose last parameter is a list of arrays, e.g.
    370         // "Object[]...".
    371         result.append(param.type().fullNameNoDimension(typeVariables())).append("...");
    372       } else {
    373         result.append(param.type().fullName(typeVariables()));
    374       }
    375       i++;
    376     }
    377     return result.toString();
    378   }
    379 
    380   private boolean inList(ClassInfo item, ThrowsTagInfo[] list) {
    381     int len = list.length;
    382     String qn = item.qualifiedName();
    383     for (int i = 0; i < len; i++) {
    384       ClassInfo ex = list[i].exception();
    385       if (ex != null && ex.qualifiedName().equals(qn)) {
    386         return true;
    387       }
    388     }
    389     return false;
    390   }
    391 
    392   public ThrowsTagInfo[] throwsTags() {
    393     if (mThrowsTags == null) {
    394       ThrowsTagInfo[] documented = comment().throwsTags();
    395       ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
    396 
    397       int len = documented.length;
    398       for (int i = 0; i < len; i++) {
    399         rv.add(documented[i]);
    400       }
    401 
    402       for (ClassInfo cl : mThrownExceptions) {
    403         if (documented == null || !inList(cl, documented)) {
    404           rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "",
    405               containingClass(), position()));
    406         }
    407       }
    408       mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]);
    409     }
    410     return mThrowsTags;
    411   }
    412 
    413   private static int indexOfParam(String name, String[] list) {
    414     final int N = list.length;
    415     for (int i = 0; i < N; i++) {
    416       if (name.equals(list[i])) {
    417         return i;
    418       }
    419     }
    420     return -1;
    421   }
    422 
    423   public ParamTagInfo[] paramTags() {
    424     if (mParamTags == null) {
    425       final int N = mParameters.size();
    426 
    427       String[] names = new String[N];
    428       String[] comments = new String[N];
    429       SourcePositionInfo[] positions = new SourcePositionInfo[N];
    430 
    431       // get the right names so we can handle our names being different from
    432       // our parent's names.
    433       int i = 0;
    434       for (ParameterInfo param : mParameters) {
    435         names[i] = param.name();
    436         comments[i] = "";
    437         positions[i] = param.position();
    438         i++;
    439       }
    440 
    441       // gather our comments, and complain about misnamed @param tags
    442       for (ParamTagInfo tag : comment().paramTags()) {
    443         int index = indexOfParam(tag.parameterName(), names);
    444         if (index >= 0) {
    445           comments[index] = tag.parameterComment();
    446           positions[index] = tag.position();
    447         } else {
    448           Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
    449               "@param tag with name that doesn't match the parameter list: '" + tag.parameterName()
    450                   + "'");
    451         }
    452       }
    453 
    454       // get our parent's tags to fill in the blanks
    455       MethodInfo overridden = this.findOverriddenMethod(name(), signature());
    456       if (overridden != null) {
    457         ParamTagInfo[] maternal = overridden.paramTags();
    458         for (i = 0; i < N; i++) {
    459           if (comments[i].equals("")) {
    460             comments[i] = maternal[i].parameterComment();
    461             positions[i] = maternal[i].position();
    462           }
    463         }
    464       }
    465 
    466       // construct the results, and cache them for next time
    467       mParamTags = new ParamTagInfo[N];
    468       for (i = 0; i < N; i++) {
    469         mParamTags[i] =
    470             new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], parent(),
    471                 positions[i]);
    472 
    473         // while we're here, if we find any parameters that are still undocumented at this
    474         // point, complain. (this warning is off by default, because it's really, really
    475         // common; but, it's good to be able to enforce it)
    476         if (comments[i].equals("")) {
    477           Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], "Undocumented parameter '"
    478               + names[i] + "' on method '" + name() + "'");
    479         }
    480       }
    481     }
    482     return mParamTags;
    483   }
    484 
    485   public SeeTagInfo[] seeTags() {
    486     SeeTagInfo[] result = comment().seeTags();
    487     if (result == null) {
    488       if (mOverriddenMethod != null) {
    489         result = mOverriddenMethod.seeTags();
    490       }
    491     }
    492     return result;
    493   }
    494 
    495   public TagInfo[] deprecatedTags() {
    496     TagInfo[] result = comment().deprecatedTags();
    497     if (result.length == 0) {
    498       if (comment().undeprecateTags().length == 0) {
    499         if (mOverriddenMethod != null) {
    500           result = mOverriddenMethod.deprecatedTags();
    501         }
    502       }
    503     }
    504     return result;
    505   }
    506 
    507   public ArrayList<ParameterInfo> parameters() {
    508     return mParameters;
    509   }
    510 
    511 
    512   public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) {
    513     if (mParamStrings == null) {
    514       if (mParameters.size() != params.length) {
    515         return false;
    516       }
    517       int i = 0;
    518       for (ParameterInfo mine : mParameters) {
    519         if (!mine.matchesDimension(dimensions[i], varargs)) {
    520           return false;
    521         }
    522         TypeInfo myType = mine.type();
    523         String qualifiedName = myType.qualifiedTypeName();
    524         String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName();
    525         String s = params[i];
    526         int slen = s.length();
    527         int qnlen = qualifiedName.length();
    528 
    529         // Check for a matching generic name or best known type
    530         if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) {
    531           return false;
    532         }
    533         i++;
    534       }
    535     }
    536     return true;
    537   }
    538 
    539   /**
    540    * Checks to see if a parameter from a method signature is
    541    * compatible with a parameter given in a {@code @link} tag.
    542    */
    543   private boolean matchesType(String signatureParam, String callerParam) {
    544     int signatureLength = signatureParam.length();
    545     int callerLength = callerParam.length();
    546     return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength
    547         && signatureParam.charAt(signatureLength - callerLength - 1) == '.'
    548         && signatureParam.endsWith(callerParam))));
    549   }
    550 
    551   public void makeHDF(Data data, String base) {
    552     makeHDF(data, base, Collections.<String, TypeInfo>emptyMap());
    553   }
    554 
    555   public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) {
    556     data.setValue(base + ".kind", kind());
    557     data.setValue(base + ".name", name());
    558     data.setValue(base + ".href", htmlPage());
    559     data.setValue(base + ".anchor", anchor());
    560 
    561     if (mReturnType != null) {
    562       returnType().getTypeWithArguments(typeMapping).makeHDF(
    563           data, base + ".returnType", false, typeVariables());
    564       data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
    565     }
    566 
    567     data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
    568     data.setValue(base + ".final", isFinal() ? "final" : "");
    569     data.setValue(base + ".static", isStatic() ? "static" : "");
    570 
    571     TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
    572     TagInfo.makeHDF(data, base + ".descr", inlineTags());
    573     TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
    574     TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
    575     data.setValue(base + ".since", getSince());
    576     if (isDeprecated()) {
    577       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
    578     }
    579     ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
    580     AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
    581     ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
    582     ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(
    583         new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables(), typeMapping);
    584     if (isProtected()) {
    585       data.setValue(base + ".scope", "protected");
    586     } else if (isPublic()) {
    587       data.setValue(base + ".scope", "public");
    588     }
    589     TagInfo.makeHDF(data, base + ".returns", returnTags());
    590 
    591     if (mTypeParameters != null) {
    592       TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
    593     }
    594 
    595     AnnotationInstanceInfo.makeLinkListHDF(
    596       data,
    597       base + ".showAnnotations",
    598       showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()]));
    599 
    600     setFederatedReferences(data, base);
    601   }
    602 
    603   public HashSet<String> typeVariables() {
    604     HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
    605     ClassInfo cl = containingClass();
    606     while (cl != null) {
    607         ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments();
    608       if (types != null) {
    609         TypeInfo.typeVariables(types, result);
    610       }
    611       cl = cl.containingClass();
    612     }
    613     return result;
    614   }
    615 
    616   @Override
    617   public boolean isExecutable() {
    618     return true;
    619   }
    620 
    621   public ArrayList<ClassInfo> thrownExceptions() {
    622     return mThrownExceptions;
    623   }
    624 
    625   public String typeArgumentsName(HashSet<String> typeVars) {
    626     if (mTypeParameters == null || mTypeParameters.isEmpty()) {
    627       return "";
    628     } else {
    629       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
    630     }
    631   }
    632 
    633   public boolean isAnnotationElement() {
    634     return mIsAnnotationElement;
    635   }
    636 
    637   public AnnotationValueInfo defaultAnnotationElementValue() {
    638     return mDefaultAnnotationElementValue;
    639   }
    640 
    641   public void setVarargs(boolean set) {
    642     mIsVarargs = set;
    643   }
    644 
    645   public boolean isVarArgs() {
    646     return mIsVarargs;
    647   }
    648 
    649   public boolean isEffectivelyFinal() {
    650       if (mIsFinal) {
    651           return true;
    652       }
    653       ClassInfo containingClass = containingClass();
    654       if (containingClass != null && containingClass.isEffectivelyFinal()) {
    655           return true;
    656       }
    657       return false;
    658   }
    659 
    660   @Override
    661   public String toString() {
    662     return this.name();
    663   }
    664 
    665   public void setReason(String reason) {
    666     mReasonOpened = reason;
    667   }
    668 
    669   public String getReason() {
    670     return mReasonOpened;
    671   }
    672 
    673   public void addException(String exec) {
    674     ClassInfo exceptionClass = new ClassInfo(exec);
    675 
    676     mThrownExceptions.add(exceptionClass);
    677   }
    678 
    679   public void addParameter(ParameterInfo p) {
    680     // Name information
    681     if (mParameters == null) {
    682         mParameters = new ArrayList<ParameterInfo>();
    683     }
    684 
    685     mParameters.add(p);
    686   }
    687 
    688   private String mFlatSignature;
    689   private MethodInfo mOverriddenMethod;
    690   private TypeInfo mReturnType;
    691   private boolean mIsAnnotationElement;
    692   private boolean mIsAbstract;
    693   private boolean mIsSynchronized;
    694   private boolean mIsNative;
    695   private boolean mIsVarargs;
    696   private boolean mDeprecatedKnown;
    697   private boolean mIsDeprecated;
    698   private ArrayList<ParameterInfo> mParameters;
    699   private ArrayList<ClassInfo> mThrownExceptions;
    700   private String[] mParamStrings;
    701   private ThrowsTagInfo[] mThrowsTags;
    702   private ParamTagInfo[] mParamTags;
    703   private ArrayList<TypeInfo> mTypeParameters;
    704   private AnnotationValueInfo mDefaultAnnotationElementValue;
    705   private String mReasonOpened;
    706   private ArrayList<Resolution> mResolutions;
    707 
    708   // TODO: merge with droiddoc version (above)
    709   public String qualifiedName() {
    710     String parentQName = (containingClass() != null)
    711         ? (containingClass().qualifiedName() + ".") : "";
    712     return parentQName + name();
    713   }
    714 
    715   @Override
    716   public String signature() {
    717     if (mSignature == null) {
    718       StringBuilder params = new StringBuilder("(");
    719       for (ParameterInfo pInfo : mParameters) {
    720         if (params.length() > 1) {
    721           params.append(", ");
    722         }
    723         params.append(pInfo.type().fullName());
    724       }
    725 
    726       params.append(")");
    727       mSignature = params.toString();
    728     }
    729     return mSignature;
    730   }
    731 
    732   public boolean matches(MethodInfo other) {
    733     return prettySignature().equals(other.prettySignature());
    734   }
    735 
    736   public boolean throwsException(ClassInfo exception) {
    737     for (ClassInfo e : mThrownExceptions) {
    738       if (e.qualifiedName().equals(exception.qualifiedName())) {
    739         return true;
    740       }
    741     }
    742     return false;
    743   }
    744 
    745   public boolean isConsistent(MethodInfo mInfo) {
    746     boolean consistent = true;
    747     if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) {
    748       if (!mReturnType.isPrimitive() && !mInfo.mReturnType.isPrimitive()) {
    749         // Check to see if our class extends the old class.
    750         ApiInfo infoApi = mInfo.containingClass().containingPackage().containingApi();
    751         ClassInfo infoReturnClass = infoApi.findClass(mInfo.mReturnType.qualifiedTypeName());
    752         // Find the classes.
    753         consistent = infoReturnClass != null &&
    754                      infoReturnClass.isAssignableTo(mReturnType.qualifiedTypeName());
    755       } else {
    756         consistent = false;
    757       }
    758 
    759       if (!consistent) {
    760         Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName()
    761             + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType);
    762       }
    763     }
    764 
    765     if (mIsAbstract != mInfo.mIsAbstract) {
    766       consistent = false;
    767       Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName()
    768           + " has changed 'abstract' qualifier");
    769     }
    770 
    771     if (mIsNative != mInfo.mIsNative) {
    772       consistent = false;
    773       Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName()
    774           + " has changed 'native' qualifier");
    775     }
    776 
    777     if (!mIsStatic) {
    778       // Compiler-generated methods vary in their 'final' qualifier between versions of
    779       // the compiler, so this check needs to be quite narrow. A change in 'final'
    780       // status of a method is only relevant if (a) the method is not declared 'static'
    781       // and (b) the method is not already inferred to be 'final' by virtue of its class.
    782       if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) {
    783         consistent = false;
    784         Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
    785             + " has added 'final' qualifier");
    786       } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) {
    787         consistent = false;
    788         Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
    789             + " has removed 'final' qualifier");
    790       }
    791     }
    792 
    793     if (mIsStatic != mInfo.mIsStatic) {
    794       consistent = false;
    795       Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName()
    796           + " has changed 'static' qualifier");
    797     }
    798 
    799     if (!scope().equals(mInfo.scope())) {
    800       consistent = false;
    801       Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName()
    802           + " changed scope from " + scope() + " to " + mInfo.scope());
    803     }
    804 
    805     if (!isDeprecated() == mInfo.isDeprecated()) {
    806       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName()
    807           + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated());
    808       consistent = false;
    809     }
    810 
    811     // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
    812     // "compatibility with existing binaries."
    813     /*
    814     if (mIsSynchronized != mInfo.mIsSynchronized) {
    815       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
    816           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
    817           + mInfo.mIsSynchronized);
    818       consistent = false;
    819     }
    820     */
    821 
    822     for (ClassInfo exception : thrownExceptions()) {
    823       if (!mInfo.throwsException(exception)) {
    824         // exclude 'throws' changes to finalize() overrides with no arguments
    825         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
    826           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
    827               + " no longer throws exception " + exception.qualifiedName());
    828           consistent = false;
    829         }
    830       }
    831     }
    832 
    833     for (ClassInfo exec : mInfo.thrownExceptions()) {
    834       // exclude 'throws' changes to finalize() overrides with no arguments
    835       if (!throwsException(exec)) {
    836         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
    837           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
    838               + " added thrown exception " + exec.qualifiedName());
    839           consistent = false;
    840         }
    841       }
    842     }
    843 
    844     return consistent;
    845   }
    846 
    847   public void printResolutions() {
    848       if (mResolutions == null || mResolutions.isEmpty()) {
    849           return;
    850       }
    851 
    852       System.out.println("Resolutions for Method " + mName + mFlatSignature + ":");
    853 
    854       for (Resolution r : mResolutions) {
    855           System.out.println(r);
    856       }
    857   }
    858 
    859   public void addResolution(Resolution resolution) {
    860       if (mResolutions == null) {
    861           mResolutions = new ArrayList<Resolution>();
    862       }
    863 
    864       mResolutions.add(resolution);
    865   }
    866 
    867   public boolean resolveResolutions() {
    868       ArrayList<Resolution> resolutions = mResolutions;
    869       mResolutions = new ArrayList<Resolution>();
    870 
    871       boolean allResolved = true;
    872       for (Resolution resolution : resolutions) {
    873           StringBuilder qualifiedClassName = new StringBuilder();
    874           InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
    875                   resolution.getInfoBuilder());
    876 
    877           // if we still couldn't resolve it, save it for the next pass
    878           if ("".equals(qualifiedClassName.toString())) {
    879               mResolutions.add(resolution);
    880               allResolved = false;
    881           } else if ("thrownException".equals(resolution.getVariable())) {
    882               mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString()));
    883           }
    884       }
    885 
    886       return allResolved;
    887   }
    888 }
    889