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
17package com.google.doclava;
18
19import com.google.clearsilver.jsilver.data.Data;
20import com.google.doclava.apicheck.AbstractMethodInfo;
21
22import java.util.*;
23
24public 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