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;
21import com.google.doclava.apicheck.ApiInfo;
22
23import java.util.*;
24
25public 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