• 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 import com.google.doclava.apicheck.ApiInfo;
22 
23 import java.util.*;
24 
25 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable {
26   public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
27     public int compare(MethodInfo a, MethodInfo b) {
28         return a.name().compareTo(b.name());
29     }
30   };
31 
32   private class InlineTags implements InheritedTags {
tags()33     public TagInfo[] tags() {
34       return comment().tags();
35     }
36 
inherited()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 
addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)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
findOverriddenMethod(String name, String signature)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 
addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)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 
findRealOverriddenMethod(String name, String signature, HashSet notStrippable)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 
findSuperclassImplementation(HashSet notStrippable)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 
findRealOverriddenClass(String name, String signature)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 {
tags()178     public TagInfo[] tags() {
179       return comment().briefTags();
180     }
181 
inherited()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 {
tags()193     public TagInfo[] tags() {
194       return comment().returnTags();
195     }
196 
inherited()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 
isDeprecated()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 
setDeprecated(boolean deprecated)231   public void setDeprecated(boolean deprecated) {
232     mDeprecatedKnown = true;
233     mIsDeprecated = deprecated;
234   }
235 
getTypeParameters()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    */
cloneForClass(ClassInfo newContainingClass, Map<String, TypeInfo> typeArgumentMapping)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 
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)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 
init(AnnotationValueInfo defaultAnnotationElementValue)297   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
298     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
299   }
300 
isAbstract()301   public boolean isAbstract() {
302     return mIsAbstract;
303   }
304 
isSynchronized()305   public boolean isSynchronized() {
306     return mIsSynchronized;
307   }
308 
isNative()309   public boolean isNative() {
310     return mIsNative;
311   }
312 
flatSignature()313   public String flatSignature() {
314     return mFlatSignature;
315   }
316 
inlineTags()317   public InheritedTags inlineTags() {
318     return new InlineTags();
319   }
320 
firstSentenceTags()321   public InheritedTags firstSentenceTags() {
322     return new FirstSentenceTags();
323   }
324 
returnTags()325   public InheritedTags returnTags() {
326     return new ReturnTags();
327   }
328 
returnType()329   public TypeInfo returnType() {
330     return mReturnType;
331   }
332 
prettySignature()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    */
prettyParameters()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    */
getHashableName()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 
inList(ClassInfo item, ThrowsTagInfo[] list)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 
throwsTags()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 
indexOfParam(String name, String[] list)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 
paramTags()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 
seeTags()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 
deprecatedTags()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 
parameters()507   public ArrayList<ParameterInfo> parameters() {
508     return mParameters;
509   }
510 
511 
matchesParams(String[] params, String[] dimensions, boolean varargs)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    */
matchesType(String signatureParam, String callerParam)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 
makeHDF(Data data, String base)551   public void makeHDF(Data data, String base) {
552     makeHDF(data, base, Collections.<String, TypeInfo>emptyMap());
553   }
554 
makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping)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 
typeVariables()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
isExecutable()617   public boolean isExecutable() {
618     return true;
619   }
620 
thrownExceptions()621   public ArrayList<ClassInfo> thrownExceptions() {
622     return mThrownExceptions;
623   }
624 
typeArgumentsName(HashSet<String> typeVars)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 
isAnnotationElement()633   public boolean isAnnotationElement() {
634     return mIsAnnotationElement;
635   }
636 
defaultAnnotationElementValue()637   public AnnotationValueInfo defaultAnnotationElementValue() {
638     return mDefaultAnnotationElementValue;
639   }
640 
setVarargs(boolean set)641   public void setVarargs(boolean set) {
642     mIsVarargs = set;
643   }
644 
isVarArgs()645   public boolean isVarArgs() {
646     return mIsVarargs;
647   }
648 
isEffectivelyFinal()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
toString()661   public String toString() {
662     return this.name();
663   }
664 
setReason(String reason)665   public void setReason(String reason) {
666     mReasonOpened = reason;
667   }
668 
getReason()669   public String getReason() {
670     return mReasonOpened;
671   }
672 
addException(String exec)673   public void addException(String exec) {
674     ClassInfo exceptionClass = new ClassInfo(exec);
675 
676     mThrownExceptions.add(exceptionClass);
677   }
678 
addParameter(ParameterInfo p)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)
qualifiedName()709   public String qualifiedName() {
710     String parentQName = (containingClass() != null)
711         ? (containingClass().qualifiedName() + ".") : "";
712     return parentQName + name();
713   }
714 
715   @Override
signature()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 
matches(MethodInfo other)732   public boolean matches(MethodInfo other) {
733     return prettySignature().equals(other.prettySignature());
734   }
735 
throwsException(ClassInfo exception)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 
isConsistent(MethodInfo mInfo)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 
printResolutions()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 
addResolution(Resolution resolution)859   public void addResolution(Resolution resolution) {
860       if (mResolutions == null) {
861           mResolutions = new ArrayList<Resolution>();
862       }
863 
864       mResolutions.add(resolution);
865   }
866 
resolveResolutions()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