• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
tags()32     public TagInfo[] tags() {
33       return comment().tags();
34     }
35 
inherited()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 
addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)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
findOverriddenMethod(String name, String signature)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 
addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)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 
findRealOverriddenMethod(String name, String signature, HashSet notStrippable)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 
findSuperclassImplementation(HashSet notStrippable)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 
findRealOverriddenClass(String name, String signature)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 {
tags()177     public TagInfo[] tags() {
178       return comment().briefTags();
179     }
180 
inherited()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 {
tags()192     public TagInfo[] tags() {
193       return comment().returnTags();
194     }
195 
inherited()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 
isDeprecated()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 
setDeprecated(boolean deprecated)230   public void setDeprecated(boolean deprecated) {
231     mDeprecatedKnown = true;
232     mIsDeprecated = deprecated;
233   }
234 
getTypeParameters()235   public ArrayList<TypeInfo> getTypeParameters() {
236     return mTypeParameters;
237   }
238 
cloneForClass(ClassInfo newContainingClass)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 
MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name, String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized, boolean isNative, boolean isAnnotationElement, String kind, String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations)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 
init(AnnotationValueInfo defaultAnnotationElementValue)286   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
287     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
288   }
289 
isAbstract()290   public boolean isAbstract() {
291     return mIsAbstract;
292   }
293 
isSynchronized()294   public boolean isSynchronized() {
295     return mIsSynchronized;
296   }
297 
isNative()298   public boolean isNative() {
299     return mIsNative;
300   }
301 
flatSignature()302   public String flatSignature() {
303     return mFlatSignature;
304   }
305 
inlineTags()306   public InheritedTags inlineTags() {
307     return new InlineTags();
308   }
309 
firstSentenceTags()310   public InheritedTags firstSentenceTags() {
311     return new FirstSentenceTags();
312   }
313 
returnTags()314   public InheritedTags returnTags() {
315     return new ReturnTags();
316   }
317 
returnType()318   public TypeInfo returnType() {
319     return mReturnType;
320   }
321 
prettySignature()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    */
prettyParameters()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    */
getHashableName()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 
inList(ClassInfo item, ThrowsTagInfo[] list)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 
throwsTags()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 
indexOfParam(String name, String[] list)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 
paramTags()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 
seeTags()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 
deprecatedTags()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 
parameters()496   public ArrayList<ParameterInfo> parameters() {
497     return mParameters;
498   }
499 
500 
matchesParams(String[] params, String[] dimensions, boolean varargs)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    */
matchesType(String signatureParam, String callerParam)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 
makeHDF(Data data, String base)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     if (isDeprecated()) {
561       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
562     }
563     ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
564     AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
565     ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
566     ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables());
567     if (isProtected()) {
568       data.setValue(base + ".scope", "protected");
569     } else if (isPublic()) {
570       data.setValue(base + ".scope", "public");
571     }
572     TagInfo.makeHDF(data, base + ".returns", returnTags());
573 
574     if (mTypeParameters != null) {
575       TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
576     }
577 
578     setFederatedReferences(data, base);
579   }
580 
typeVariables()581   public HashSet<String> typeVariables() {
582     HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
583     ClassInfo cl = containingClass();
584     while (cl != null) {
585         ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments();
586       if (types != null) {
587         TypeInfo.typeVariables(types, result);
588       }
589       cl = cl.containingClass();
590     }
591     return result;
592   }
593 
594   @Override
isExecutable()595   public boolean isExecutable() {
596     return true;
597   }
598 
thrownExceptions()599   public ArrayList<ClassInfo> thrownExceptions() {
600     return mThrownExceptions;
601   }
602 
typeArgumentsName(HashSet<String> typeVars)603   public String typeArgumentsName(HashSet<String> typeVars) {
604     if (mTypeParameters == null || mTypeParameters.isEmpty()) {
605       return "";
606     } else {
607       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
608     }
609   }
610 
isAnnotationElement()611   public boolean isAnnotationElement() {
612     return mIsAnnotationElement;
613   }
614 
defaultAnnotationElementValue()615   public AnnotationValueInfo defaultAnnotationElementValue() {
616     return mDefaultAnnotationElementValue;
617   }
618 
setVarargs(boolean set)619   public void setVarargs(boolean set) {
620     mIsVarargs = set;
621   }
622 
isVarArgs()623   public boolean isVarArgs() {
624     return mIsVarargs;
625   }
626 
627   @Override
toString()628   public String toString() {
629     return this.name();
630   }
631 
setReason(String reason)632   public void setReason(String reason) {
633     mReasonOpened = reason;
634   }
635 
getReason()636   public String getReason() {
637     return mReasonOpened;
638   }
639 
addException(String exec)640   public void addException(String exec) {
641     ClassInfo exceptionClass = new ClassInfo(exec);
642 
643     mThrownExceptions.add(exceptionClass);
644   }
645 
addParameter(ParameterInfo p)646   public void addParameter(ParameterInfo p) {
647     // Name information
648     if (mParameters == null) {
649         mParameters = new ArrayList<ParameterInfo>();
650     }
651 
652     mParameters.add(p);
653   }
654 
655   private String mFlatSignature;
656   private MethodInfo mOverriddenMethod;
657   private TypeInfo mReturnType;
658   private boolean mIsAnnotationElement;
659   private boolean mIsAbstract;
660   private boolean mIsSynchronized;
661   private boolean mIsNative;
662   private boolean mIsVarargs;
663   private boolean mDeprecatedKnown;
664   private boolean mIsDeprecated;
665   private ArrayList<ParameterInfo> mParameters;
666   private ArrayList<ClassInfo> mThrownExceptions;
667   private String[] mParamStrings;
668   private ThrowsTagInfo[] mThrowsTags;
669   private ParamTagInfo[] mParamTags;
670   private ArrayList<TypeInfo> mTypeParameters;
671   private AnnotationValueInfo mDefaultAnnotationElementValue;
672   private String mReasonOpened;
673   private ArrayList<Resolution> mResolutions;
674 
675   // TODO: merge with droiddoc version (above)
qualifiedName()676   public String qualifiedName() {
677     String parentQName = (containingClass() != null)
678         ? (containingClass().qualifiedName() + ".") : "";
679     return parentQName + name();
680   }
681 
682   @Override
signature()683   public String signature() {
684     if (mSignature == null) {
685       StringBuilder params = new StringBuilder("(");
686       for (ParameterInfo pInfo : mParameters) {
687         if (params.length() > 1) {
688           params.append(", ");
689         }
690         params.append(pInfo.type().fullName());
691       }
692 
693       params.append(")");
694       mSignature = params.toString();
695     }
696     return mSignature;
697   }
698 
matches(MethodInfo other)699   public boolean matches(MethodInfo other) {
700     return prettySignature().equals(other.prettySignature());
701   }
702 
throwsException(ClassInfo exception)703   public boolean throwsException(ClassInfo exception) {
704     for (ClassInfo e : mThrownExceptions) {
705       if (e.qualifiedName().equals(exception.qualifiedName())) {
706         return true;
707       }
708     }
709     return false;
710   }
711 
isConsistent(MethodInfo mInfo)712   public boolean isConsistent(MethodInfo mInfo) {
713     boolean consistent = true;
714     if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) {
715       consistent = false;
716       Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName()
717           + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType);
718     }
719 
720     if (mIsAbstract != mInfo.mIsAbstract) {
721       consistent = false;
722       Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName()
723           + " has changed 'abstract' qualifier");
724     }
725 
726     if (mIsNative != mInfo.mIsNative) {
727       consistent = false;
728       Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName()
729           + " has changed 'native' qualifier");
730     }
731 
732     if (mIsFinal != mInfo.mIsFinal) {
733       // Compiler-generated methods vary in their 'final' qual between versions of
734       // the compiler, so this check needs to be quite narrow. A change in 'final'
735       // status of a method is only relevant if (a) the method is not declared 'static'
736       // and (b) the method's class is not itself 'final'.
737       if (!mIsStatic) {
738         if ((containingClass() == null) || (!containingClass().isFinal())) {
739           consistent = false;
740           Errors.error(Errors.CHANGED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
741               + " has changed 'final' qualifier");
742         }
743       }
744     }
745 
746     if (mIsStatic != mInfo.mIsStatic) {
747       consistent = false;
748       Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName()
749           + " has changed 'static' qualifier");
750     }
751 
752     if (!scope().equals(mInfo.scope())) {
753       consistent = false;
754       Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName()
755           + " changed scope from " + scope() + " to " + mInfo.scope());
756     }
757 
758     if (!isDeprecated() == mInfo.isDeprecated()) {
759       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName()
760           + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated());
761       consistent = false;
762     }
763 
764     // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
765     // "compatibility with existing binaries."
766     /*
767     if (mIsSynchronized != mInfo.mIsSynchronized) {
768       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
769           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
770           + mInfo.mIsSynchronized);
771       consistent = false;
772     }
773     */
774 
775     for (ClassInfo exception : thrownExceptions()) {
776       if (!mInfo.throwsException(exception)) {
777         // exclude 'throws' changes to finalize() overrides with no arguments
778         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
779           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
780               + " no longer throws exception " + exception.qualifiedName());
781           consistent = false;
782         }
783       }
784     }
785 
786     for (ClassInfo exec : mInfo.thrownExceptions()) {
787       // exclude 'throws' changes to finalize() overrides with no arguments
788       if (!throwsException(exec)) {
789         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
790           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
791               + " added thrown exception " + exec.qualifiedName());
792           consistent = false;
793         }
794       }
795     }
796 
797     return consistent;
798   }
799 
printResolutions()800   public void printResolutions() {
801       if (mResolutions == null || mResolutions.isEmpty()) {
802           return;
803       }
804 
805       System.out.println("Resolutions for Method " + mName + mFlatSignature + ":");
806 
807       for (Resolution r : mResolutions) {
808           System.out.println(r);
809       }
810   }
811 
addResolution(Resolution resolution)812   public void addResolution(Resolution resolution) {
813       if (mResolutions == null) {
814           mResolutions = new ArrayList<Resolution>();
815       }
816 
817       mResolutions.add(resolution);
818   }
819 
resolveResolutions()820   public boolean resolveResolutions() {
821       ArrayList<Resolution> resolutions = mResolutions;
822       mResolutions = new ArrayList<Resolution>();
823 
824       boolean allResolved = true;
825       for (Resolution resolution : resolutions) {
826           StringBuilder qualifiedClassName = new StringBuilder();
827           InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
828                   resolution.getInfoBuilder());
829 
830           // if we still couldn't resolve it, save it for the next pass
831           if ("".equals(qualifiedClassName.toString())) {
832               mResolutions.add(resolution);
833               allResolved = false;
834           } else if ("thrownException".equals(resolution.getVariable())) {
835               mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString()));
836           }
837       }
838 
839       return allResolved;
840   }
841 }
842