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