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