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