• 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 
cloneForClass(ClassInfo newContainingClass)240   public MethodInfo cloneForClass(ClassInfo newContainingClass) {
241     MethodInfo result =
242         new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(),
243             newContainingClass, realContainingClass(), isPublic(), isProtected(),
244             isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract,
245             mIsSynchronized, mIsNative, mIsAnnotationElement, kind(), mFlatSignature,
246             mOverriddenMethod, mReturnType, mParameters, mThrownExceptions, position(),
247             annotations());
248     result.init(mDefaultAnnotationElementValue);
249     return result;
250   }
251 
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)252   public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name,
253       String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic,
254       boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal,
255       boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized,
256       boolean isNative, boolean isAnnotationElement, String kind, String flatSignature,
257       MethodInfo overriddenMethod, TypeInfo returnType, ArrayList<ParameterInfo> parameters,
258       ArrayList<ClassInfo> thrownExceptions, SourcePositionInfo position,
259       ArrayList<AnnotationInstanceInfo> annotations) {
260     // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
261     // the Java5-emitted base API description.
262     super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic,
263         isProtected, isPackagePrivate, isPrivate,
264         ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
265         isStatic, isSynthetic, kind, position, annotations);
266 
267     // The underlying MethodDoc for an interface's declared methods winds up being marked
268     // non-abstract. Correct that here by looking at the immediate-parent class, and marking
269     // this method abstract if it is an unimplemented interface method.
270     if (containingClass.isInterface()) {
271       isAbstract = true;
272     }
273 
274     mReasonOpened = "0:0";
275     mIsAnnotationElement = isAnnotationElement;
276     mTypeParameters = typeParameters;
277     mIsAbstract = isAbstract;
278     mIsSynchronized = isSynchronized;
279     mIsNative = isNative;
280     mFlatSignature = flatSignature;
281     mOverriddenMethod = overriddenMethod;
282     mReturnType = returnType;
283     mParameters = parameters;
284     mThrownExceptions = thrownExceptions;
285   }
286 
init(AnnotationValueInfo defaultAnnotationElementValue)287   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
288     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
289   }
290 
isAbstract()291   public boolean isAbstract() {
292     return mIsAbstract;
293   }
294 
isSynchronized()295   public boolean isSynchronized() {
296     return mIsSynchronized;
297   }
298 
isNative()299   public boolean isNative() {
300     return mIsNative;
301   }
302 
flatSignature()303   public String flatSignature() {
304     return mFlatSignature;
305   }
306 
inlineTags()307   public InheritedTags inlineTags() {
308     return new InlineTags();
309   }
310 
firstSentenceTags()311   public InheritedTags firstSentenceTags() {
312     return new FirstSentenceTags();
313   }
314 
returnTags()315   public InheritedTags returnTags() {
316     return new ReturnTags();
317   }
318 
returnType()319   public TypeInfo returnType() {
320     return mReturnType;
321   }
322 
prettySignature()323   public String prettySignature() {
324     return name() + prettyParameters();
325   }
326 
327   /**
328    * Returns a printable version of the parameters of this method's signature.
329    */
prettyParameters()330   public String prettyParameters() {
331     StringBuilder params = new StringBuilder("(");
332     for (ParameterInfo pInfo : mParameters) {
333       if (params.length() > 1) {
334         params.append(",");
335       }
336       params.append(pInfo.type().simpleTypeName());
337     }
338 
339     params.append(")");
340     return params.toString();
341   }
342 
343   /**
344    * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}.
345    */
getHashableName()346   public String getHashableName() {
347     StringBuilder result = new StringBuilder();
348     result.append(name());
349 
350     if (mParameters == null) {
351         return result.toString();
352     }
353 
354     int i = 0;
355     for (ParameterInfo param : mParameters) {
356       result.append(":");
357       if (i == (mParameters.size()-1) && isVarArgs()) {
358         // TODO: note that this does not attempt to handle hypothetical
359         // vararg methods whose last parameter is a list of arrays, e.g.
360         // "Object[]...".
361         result.append(param.type().fullNameNoDimension(typeVariables())).append("...");
362       } else {
363         result.append(param.type().fullName(typeVariables()));
364       }
365       i++;
366     }
367     return result.toString();
368   }
369 
inList(ClassInfo item, ThrowsTagInfo[] list)370   private boolean inList(ClassInfo item, ThrowsTagInfo[] list) {
371     int len = list.length;
372     String qn = item.qualifiedName();
373     for (int i = 0; i < len; i++) {
374       ClassInfo ex = list[i].exception();
375       if (ex != null && ex.qualifiedName().equals(qn)) {
376         return true;
377       }
378     }
379     return false;
380   }
381 
throwsTags()382   public ThrowsTagInfo[] throwsTags() {
383     if (mThrowsTags == null) {
384       ThrowsTagInfo[] documented = comment().throwsTags();
385       ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
386 
387       int len = documented.length;
388       for (int i = 0; i < len; i++) {
389         rv.add(documented[i]);
390       }
391 
392       for (ClassInfo cl : mThrownExceptions) {
393         if (documented == null || !inList(cl, documented)) {
394           rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "",
395               containingClass(), position()));
396         }
397       }
398       mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]);
399     }
400     return mThrowsTags;
401   }
402 
indexOfParam(String name, String[] list)403   private static int indexOfParam(String name, String[] list) {
404     final int N = list.length;
405     for (int i = 0; i < N; i++) {
406       if (name.equals(list[i])) {
407         return i;
408       }
409     }
410     return -1;
411   }
412 
paramTags()413   public ParamTagInfo[] paramTags() {
414     if (mParamTags == null) {
415       final int N = mParameters.size();
416 
417       String[] names = new String[N];
418       String[] comments = new String[N];
419       SourcePositionInfo[] positions = new SourcePositionInfo[N];
420 
421       // get the right names so we can handle our names being different from
422       // our parent's names.
423       int i = 0;
424       for (ParameterInfo param : mParameters) {
425         names[i] = param.name();
426         comments[i] = "";
427         positions[i] = param.position();
428         i++;
429       }
430 
431       // gather our comments, and complain about misnamed @param tags
432       for (ParamTagInfo tag : comment().paramTags()) {
433         int index = indexOfParam(tag.parameterName(), names);
434         if (index >= 0) {
435           comments[index] = tag.parameterComment();
436           positions[index] = tag.position();
437         } else {
438           Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
439               "@param tag with name that doesn't match the parameter list: '" + tag.parameterName()
440                   + "'");
441         }
442       }
443 
444       // get our parent's tags to fill in the blanks
445       MethodInfo overridden = this.findOverriddenMethod(name(), signature());
446       if (overridden != null) {
447         ParamTagInfo[] maternal = overridden.paramTags();
448         for (i = 0; i < N; i++) {
449           if (comments[i].equals("")) {
450             comments[i] = maternal[i].parameterComment();
451             positions[i] = maternal[i].position();
452           }
453         }
454       }
455 
456       // construct the results, and cache them for next time
457       mParamTags = new ParamTagInfo[N];
458       for (i = 0; i < N; i++) {
459         mParamTags[i] =
460             new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], parent(),
461                 positions[i]);
462 
463         // while we're here, if we find any parameters that are still undocumented at this
464         // point, complain. (this warning is off by default, because it's really, really
465         // common; but, it's good to be able to enforce it)
466         if (comments[i].equals("")) {
467           Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], "Undocumented parameter '"
468               + names[i] + "' on method '" + name() + "'");
469         }
470       }
471     }
472     return mParamTags;
473   }
474 
seeTags()475   public SeeTagInfo[] seeTags() {
476     SeeTagInfo[] result = comment().seeTags();
477     if (result == null) {
478       if (mOverriddenMethod != null) {
479         result = mOverriddenMethod.seeTags();
480       }
481     }
482     return result;
483   }
484 
deprecatedTags()485   public TagInfo[] deprecatedTags() {
486     TagInfo[] result = comment().deprecatedTags();
487     if (result.length == 0) {
488       if (comment().undeprecateTags().length == 0) {
489         if (mOverriddenMethod != null) {
490           result = mOverriddenMethod.deprecatedTags();
491         }
492       }
493     }
494     return result;
495   }
496 
parameters()497   public ArrayList<ParameterInfo> parameters() {
498     return mParameters;
499   }
500 
501 
matchesParams(String[] params, String[] dimensions, boolean varargs)502   public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) {
503     if (mParamStrings == null) {
504       if (mParameters.size() != params.length) {
505         return false;
506       }
507       int i = 0;
508       for (ParameterInfo mine : mParameters) {
509         if (!mine.matchesDimension(dimensions[i], varargs)) {
510           return false;
511         }
512         TypeInfo myType = mine.type();
513         String qualifiedName = myType.qualifiedTypeName();
514         String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName();
515         String s = params[i];
516         int slen = s.length();
517         int qnlen = qualifiedName.length();
518 
519         // Check for a matching generic name or best known type
520         if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) {
521           return false;
522         }
523         i++;
524       }
525     }
526     return true;
527   }
528 
529   /**
530    * Checks to see if a parameter from a method signature is
531    * compatible with a parameter given in a {@code @link} tag.
532    */
matchesType(String signatureParam, String callerParam)533   private boolean matchesType(String signatureParam, String callerParam) {
534     int signatureLength = signatureParam.length();
535     int callerLength = callerParam.length();
536     return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength
537         && signatureParam.charAt(signatureLength - callerLength - 1) == '.'
538         && signatureParam.endsWith(callerParam))));
539   }
540 
makeHDF(Data data, String base)541   public void makeHDF(Data data, String base) {
542     data.setValue(base + ".kind", kind());
543     data.setValue(base + ".name", name());
544     data.setValue(base + ".href", htmlPage());
545     data.setValue(base + ".anchor", anchor());
546 
547     if (mReturnType != null) {
548       returnType().makeHDF(data, base + ".returnType", false, typeVariables());
549       data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
550     }
551 
552     data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
553     data.setValue(base + ".final", isFinal() ? "final" : "");
554     data.setValue(base + ".static", isStatic() ? "static" : "");
555 
556     TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
557     TagInfo.makeHDF(data, base + ".descr", inlineTags());
558     TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
559     TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
560     data.setValue(base + ".since", getSince());
561     if (isDeprecated()) {
562       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
563     }
564     ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
565     AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
566     ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
567     ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables());
568     if (isProtected()) {
569       data.setValue(base + ".scope", "protected");
570     } else if (isPublic()) {
571       data.setValue(base + ".scope", "public");
572     }
573     TagInfo.makeHDF(data, base + ".returns", returnTags());
574 
575     if (mTypeParameters != null) {
576       TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
577     }
578 
579     AnnotationInstanceInfo.makeLinkListHDF(
580       data,
581       base + ".showAnnotations",
582       showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()]));
583 
584     setFederatedReferences(data, base);
585   }
586 
typeVariables()587   public HashSet<String> typeVariables() {
588     HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
589     ClassInfo cl = containingClass();
590     while (cl != null) {
591         ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments();
592       if (types != null) {
593         TypeInfo.typeVariables(types, result);
594       }
595       cl = cl.containingClass();
596     }
597     return result;
598   }
599 
600   @Override
isExecutable()601   public boolean isExecutable() {
602     return true;
603   }
604 
thrownExceptions()605   public ArrayList<ClassInfo> thrownExceptions() {
606     return mThrownExceptions;
607   }
608 
typeArgumentsName(HashSet<String> typeVars)609   public String typeArgumentsName(HashSet<String> typeVars) {
610     if (mTypeParameters == null || mTypeParameters.isEmpty()) {
611       return "";
612     } else {
613       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
614     }
615   }
616 
isAnnotationElement()617   public boolean isAnnotationElement() {
618     return mIsAnnotationElement;
619   }
620 
defaultAnnotationElementValue()621   public AnnotationValueInfo defaultAnnotationElementValue() {
622     return mDefaultAnnotationElementValue;
623   }
624 
setVarargs(boolean set)625   public void setVarargs(boolean set) {
626     mIsVarargs = set;
627   }
628 
isVarArgs()629   public boolean isVarArgs() {
630     return mIsVarargs;
631   }
632 
isEffectivelyFinal()633   public boolean isEffectivelyFinal() {
634       if (mIsFinal) {
635           return true;
636       }
637       ClassInfo containingClass = containingClass();
638       if (containingClass != null && containingClass.isEffectivelyFinal()) {
639           return true;
640       }
641       return false;
642   }
643 
644   @Override
toString()645   public String toString() {
646     return this.name();
647   }
648 
setReason(String reason)649   public void setReason(String reason) {
650     mReasonOpened = reason;
651   }
652 
getReason()653   public String getReason() {
654     return mReasonOpened;
655   }
656 
addException(String exec)657   public void addException(String exec) {
658     ClassInfo exceptionClass = new ClassInfo(exec);
659 
660     mThrownExceptions.add(exceptionClass);
661   }
662 
addParameter(ParameterInfo p)663   public void addParameter(ParameterInfo p) {
664     // Name information
665     if (mParameters == null) {
666         mParameters = new ArrayList<ParameterInfo>();
667     }
668 
669     mParameters.add(p);
670   }
671 
672   private String mFlatSignature;
673   private MethodInfo mOverriddenMethod;
674   private TypeInfo mReturnType;
675   private boolean mIsAnnotationElement;
676   private boolean mIsAbstract;
677   private boolean mIsSynchronized;
678   private boolean mIsNative;
679   private boolean mIsVarargs;
680   private boolean mDeprecatedKnown;
681   private boolean mIsDeprecated;
682   private ArrayList<ParameterInfo> mParameters;
683   private ArrayList<ClassInfo> mThrownExceptions;
684   private String[] mParamStrings;
685   private ThrowsTagInfo[] mThrowsTags;
686   private ParamTagInfo[] mParamTags;
687   private ArrayList<TypeInfo> mTypeParameters;
688   private AnnotationValueInfo mDefaultAnnotationElementValue;
689   private String mReasonOpened;
690   private ArrayList<Resolution> mResolutions;
691 
692   // TODO: merge with droiddoc version (above)
qualifiedName()693   public String qualifiedName() {
694     String parentQName = (containingClass() != null)
695         ? (containingClass().qualifiedName() + ".") : "";
696     return parentQName + name();
697   }
698 
699   @Override
signature()700   public String signature() {
701     if (mSignature == null) {
702       StringBuilder params = new StringBuilder("(");
703       for (ParameterInfo pInfo : mParameters) {
704         if (params.length() > 1) {
705           params.append(", ");
706         }
707         params.append(pInfo.type().fullName());
708       }
709 
710       params.append(")");
711       mSignature = params.toString();
712     }
713     return mSignature;
714   }
715 
matches(MethodInfo other)716   public boolean matches(MethodInfo other) {
717     return prettySignature().equals(other.prettySignature());
718   }
719 
throwsException(ClassInfo exception)720   public boolean throwsException(ClassInfo exception) {
721     for (ClassInfo e : mThrownExceptions) {
722       if (e.qualifiedName().equals(exception.qualifiedName())) {
723         return true;
724       }
725     }
726     return false;
727   }
728 
isConsistent(MethodInfo mInfo)729   public boolean isConsistent(MethodInfo mInfo) {
730     boolean consistent = true;
731     if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) {
732       if (!mReturnType.isPrimitive() && !mInfo.mReturnType.isPrimitive()) {
733         // Check to see if our class extends the old class.
734         ApiInfo infoApi = mInfo.containingClass().containingPackage().containingApi();
735         ClassInfo infoReturnClass = infoApi.findClass(mInfo.mReturnType.qualifiedTypeName());
736         // Find the classes.
737         consistent = infoReturnClass != null &&
738                      infoReturnClass.isAssignableTo(mReturnType.qualifiedTypeName());
739       } else {
740         consistent = false;
741       }
742 
743       if (!consistent) {
744         Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName()
745             + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType);
746       }
747     }
748 
749     if (mIsAbstract != mInfo.mIsAbstract) {
750       consistent = false;
751       Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName()
752           + " has changed 'abstract' qualifier");
753     }
754 
755     if (mIsNative != mInfo.mIsNative) {
756       consistent = false;
757       Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName()
758           + " has changed 'native' qualifier");
759     }
760 
761     if (!mIsStatic) {
762       // Compiler-generated methods vary in their 'final' qualifier between versions of
763       // the compiler, so this check needs to be quite narrow. A change in 'final'
764       // status of a method is only relevant if (a) the method is not declared 'static'
765       // and (b) the method is not already inferred to be 'final' by virtue of its class.
766       if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) {
767         consistent = false;
768         Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
769             + " has added 'final' qualifier");
770       } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) {
771         consistent = false;
772         Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
773             + " has removed 'final' qualifier");
774       }
775     }
776 
777     if (mIsStatic != mInfo.mIsStatic) {
778       consistent = false;
779       Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName()
780           + " has changed 'static' qualifier");
781     }
782 
783     if (!scope().equals(mInfo.scope())) {
784       consistent = false;
785       Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName()
786           + " changed scope from " + scope() + " to " + mInfo.scope());
787     }
788 
789     if (!isDeprecated() == mInfo.isDeprecated()) {
790       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName()
791           + " has changed deprecation state " + isDeprecated() + " --> " + mInfo.isDeprecated());
792       consistent = false;
793     }
794 
795     // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
796     // "compatibility with existing binaries."
797     /*
798     if (mIsSynchronized != mInfo.mIsSynchronized) {
799       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
800           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
801           + mInfo.mIsSynchronized);
802       consistent = false;
803     }
804     */
805 
806     for (ClassInfo exception : thrownExceptions()) {
807       if (!mInfo.throwsException(exception)) {
808         // exclude 'throws' changes to finalize() overrides with no arguments
809         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
810           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
811               + " no longer throws exception " + exception.qualifiedName());
812           consistent = false;
813         }
814       }
815     }
816 
817     for (ClassInfo exec : mInfo.thrownExceptions()) {
818       // exclude 'throws' changes to finalize() overrides with no arguments
819       if (!throwsException(exec)) {
820         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
821           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
822               + " added thrown exception " + exec.qualifiedName());
823           consistent = false;
824         }
825       }
826     }
827 
828     return consistent;
829   }
830 
printResolutions()831   public void printResolutions() {
832       if (mResolutions == null || mResolutions.isEmpty()) {
833           return;
834       }
835 
836       System.out.println("Resolutions for Method " + mName + mFlatSignature + ":");
837 
838       for (Resolution r : mResolutions) {
839           System.out.println(r);
840       }
841   }
842 
addResolution(Resolution resolution)843   public void addResolution(Resolution resolution) {
844       if (mResolutions == null) {
845           mResolutions = new ArrayList<Resolution>();
846       }
847 
848       mResolutions.add(resolution);
849   }
850 
resolveResolutions()851   public boolean resolveResolutions() {
852       ArrayList<Resolution> resolutions = mResolutions;
853       mResolutions = new ArrayList<Resolution>();
854 
855       boolean allResolved = true;
856       for (Resolution resolution : resolutions) {
857           StringBuilder qualifiedClassName = new StringBuilder();
858           InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
859                   resolution.getInfoBuilder());
860 
861           // if we still couldn't resolve it, save it for the next pass
862           if ("".equals(qualifiedClassName.toString())) {
863               mResolutions.add(resolution);
864               allResolved = false;
865           } else if ("thrownException".equals(resolution.getVariable())) {
866               mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString()));
867           }
868       }
869 
870       return allResolved;
871   }
872 }
873