• 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.ArrayList;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.HashSet;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.Queue;
32 import java.util.function.Predicate;
33 
34 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable {
35   public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
36     @Override
37     public int compare(MethodInfo a, MethodInfo b) {
38       // TODO: expand to compare signature for better sorting
39       return a.name().compareTo(b.name());
40     }
41   };
42 
43   private class InlineTags implements InheritedTags {
tags()44     public TagInfo[] tags() {
45       return comment().tags();
46     }
47 
inherited()48     public InheritedTags inherited() {
49       MethodInfo m = findOverriddenMethod(name(), signature());
50       if (m != null) {
51         return m.inlineTags();
52       } else {
53         return null;
54       }
55     }
56   }
57 
addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)58   private static void addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) {
59     for (ClassInfo i : ifaces) {
60       queue.add(i);
61     }
62     for (ClassInfo i : ifaces) {
63       addInterfaces(i.interfaces(), queue);
64     }
65   }
66 
67   // first looks for a superclass, and then does a breadth first search to
68   // find the least far away match
findOverriddenMethod(String name, String signature)69   public MethodInfo findOverriddenMethod(String name, String signature) {
70     if (mReturnType == null) {
71       // ctor
72       return null;
73     }
74     if (mOverriddenMethod != null) {
75       return mOverriddenMethod;
76     }
77 
78     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
79     addInterfaces(containingClass().interfaces(), queue);
80     for (ClassInfo iface : queue) {
81       for (MethodInfo me : iface.methods()) {
82         if (me.name().equals(name) && me.signature().equals(signature)
83             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) {
84           return me;
85         }
86       }
87     }
88     return null;
89   }
90 
addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)91   private static void addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) {
92     for (ClassInfo i : ifaces) {
93       queue.add(i);
94       if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) {
95         queue.add(i.superclass());
96       }
97     }
98     for (ClassInfo i : ifaces) {
99       addInterfaces(i.realInterfaces(), queue);
100     }
101   }
102 
findRealOverriddenMethod(String name, String signature, HashSet notStrippable)103   public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) {
104     if (mReturnType == null) {
105       // ctor
106       return null;
107     }
108     if (mOverriddenMethod != null) {
109       return mOverriddenMethod;
110     }
111 
112     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
113     if (containingClass().realSuperclass() != null
114         && containingClass().realSuperclass().isAbstract()) {
115       queue.add(containingClass().realSuperclass());
116     }
117     addInterfaces(containingClass().realInterfaces(), queue);
118     for (ClassInfo iface : queue) {
119       for (MethodInfo me : iface.methods()) {
120         if (me.name().equals(name) && me.signature().equals(signature)
121             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0
122             && notStrippable.contains(me.containingClass())) {
123           return me;
124         }
125       }
126     }
127     return null;
128   }
129 
findPredicateOverriddenMethod(Predicate<MemberInfo> predicate)130   public MethodInfo findPredicateOverriddenMethod(Predicate<MemberInfo> predicate) {
131     if (mReturnType == null) {
132       // ctor
133       return null;
134     }
135     if (mOverriddenMethod != null) {
136       if (equals(mOverriddenMethod) && !mOverriddenMethod.isStatic()
137           && predicate.test(mOverriddenMethod)) {
138         return mOverriddenMethod;
139       }
140     }
141 
142     for (ClassInfo clazz : containingClass().gatherAncestorClasses()) {
143       for (MethodInfo method : clazz.getExhaustiveMethods()) {
144         if (equals(method) && !method.isStatic() && predicate.test(method)) {
145           return method;
146         }
147       }
148     }
149     return null;
150   }
151 
findRealOverriddenClass(String name, String signature)152   public ClassInfo findRealOverriddenClass(String name, String signature) {
153     if (mReturnType == null) {
154       // ctor
155       return null;
156     }
157     if (mOverriddenMethod != null) {
158       return mOverriddenMethod.mRealContainingClass;
159     }
160 
161     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
162     if (containingClass().realSuperclass() != null
163         && containingClass().realSuperclass().isAbstract()) {
164       queue.add(containingClass().realSuperclass());
165     }
166     addInterfaces(containingClass().realInterfaces(), queue);
167     for (ClassInfo iface : queue) {
168       for (MethodInfo me : iface.methods()) {
169         if (me.name().equals(name) && me.signature().equals(signature)
170             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) {
171           return iface;
172         }
173       }
174     }
175     return null;
176   }
177 
178   private class FirstSentenceTags implements InheritedTags {
tags()179     public TagInfo[] tags() {
180       return comment().briefTags();
181     }
182 
inherited()183     public InheritedTags inherited() {
184       MethodInfo m = findOverriddenMethod(name(), signature());
185       if (m != null) {
186         return m.firstSentenceTags();
187       } else {
188         return null;
189       }
190     }
191   }
192 
193   private class ReturnTags implements InheritedTags {
tags()194     public TagInfo[] tags() {
195       return comment().returnTags();
196     }
197 
inherited()198     public InheritedTags inherited() {
199       MethodInfo m = findOverriddenMethod(name(), signature());
200       if (m != null) {
201         return m.returnTags();
202       } else {
203         return null;
204       }
205     }
206   }
207 
isDeprecated()208   public boolean isDeprecated() {
209     boolean deprecated = false;
210     if (!mDeprecatedKnown) {
211       boolean commentDeprecated = comment().isDeprecated();
212       boolean annotationDeprecated = false;
213       for (AnnotationInstanceInfo annotation : annotations()) {
214         if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
215           annotationDeprecated = true;
216           break;
217         }
218       }
219 
220       // Check to see that the JavaDoc contains @deprecated AND the method is marked as @Deprecated.
221       // Otherwise, warn.
222       // Note: We only do this for "included" classes (i.e. those we have source code for); we do
223       // not have comments for classes from .class files but we do know whether a method is marked
224       // as @Deprecated.
225       if (mContainingClass.isIncluded() && !isHiddenOrRemoved()
226           && commentDeprecated != annotationDeprecated) {
227         Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Method "
228             + mContainingClass.qualifiedName() + "." + name()
229             + ": @Deprecated annotation (" + (annotationDeprecated ? "" : "not ")
230             + "present) and @deprecated doc tag (" + (commentDeprecated ? "" : "not ")
231             + "present) do not match");
232       }
233 
234       mIsDeprecated = commentDeprecated | annotationDeprecated;
235       mDeprecatedKnown = true;
236     }
237     return mIsDeprecated;
238   }
239 
setDeprecated(boolean deprecated)240   public void setDeprecated(boolean deprecated) {
241     mDeprecatedKnown = true;
242     mIsDeprecated = deprecated;
243   }
244 
getTypeParameters()245   public ArrayList<TypeInfo> getTypeParameters() {
246     return mTypeParameters;
247   }
248 
hasTypeParameters()249   public boolean hasTypeParameters() {
250       return mTypeParameters != null && !mTypeParameters.isEmpty();
251   }
252 
253   /**
254    * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the
255    * typeArgumentMapping to the parameters and return types.
256    */
cloneForClass(ClassInfo newContainingClass, Map<String, TypeInfo> typeArgumentMapping)257   public MethodInfo cloneForClass(ClassInfo newContainingClass,
258       Map<String, TypeInfo> typeArgumentMapping) {
259     if (newContainingClass == containingClass()) {
260       return this;
261     }
262     TypeInfo returnType = (mReturnType != null)
263         ? mReturnType.getTypeWithArguments(typeArgumentMapping)
264         : null;
265     ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
266     for (ParameterInfo pi : mParameters) {
267       parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping));
268     }
269     MethodInfo result =
270         new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(),
271             newContainingClass, realContainingClass(), isPublic(), isProtected(),
272             isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract,
273             mIsSynchronized, mIsNative, mIsDefault, mIsAnnotationElement, kind(), mFlatSignature,
274             mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(),
275             annotations());
276     result.init(mDefaultAnnotationElementValue);
277     return result;
278   }
279 
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)280   public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name,
281       String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic,
282       boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal,
283       boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized,
284       boolean isNative, boolean isDefault, boolean isAnnotationElement, String kind,
285       String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType,
286       ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions,
287       SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations) {
288     // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
289     // the Java5-emitted base API description.
290     super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic,
291         isProtected, isPackagePrivate, isPrivate,
292         ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
293         isStatic, isSynthetic, kind, position, annotations);
294 
295     mReasonOpened = "0:0";
296     mIsAnnotationElement = isAnnotationElement;
297     mTypeParameters = typeParameters;
298     mIsAbstract = isAbstract;
299     mIsSynchronized = isSynchronized;
300     mIsNative = isNative;
301     mIsDefault = isDefault;
302     mFlatSignature = flatSignature;
303     mOverriddenMethod = overriddenMethod;
304     mReturnType = returnType;
305     mParameters = parameters;
306     mThrownExceptions = thrownExceptions;
307   }
308 
init(AnnotationValueInfo defaultAnnotationElementValue)309   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
310     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
311   }
312 
isAbstract()313   public boolean isAbstract() {
314     return mIsAbstract;
315   }
316 
isSynchronized()317   public boolean isSynchronized() {
318     return mIsSynchronized;
319   }
320 
isNative()321   public boolean isNative() {
322     return mIsNative;
323   }
324 
isDefault()325   public boolean isDefault() {
326     return mIsDefault;
327   }
328 
flatSignature()329   public String flatSignature() {
330     return mFlatSignature;
331   }
332 
inlineTags()333   public InheritedTags inlineTags() {
334     return new InlineTags();
335   }
336 
blockTags()337   public TagInfo[] blockTags() {
338     return comment().blockTags();
339   }
340 
firstSentenceTags()341   public InheritedTags firstSentenceTags() {
342     return new FirstSentenceTags();
343   }
344 
returnTags()345   public InheritedTags returnTags() {
346     return new ReturnTags();
347   }
348 
returnType()349   public TypeInfo returnType() {
350     return mReturnType;
351   }
352 
prettySignature()353   public String prettySignature() {
354     return name() + prettyParameters();
355   }
356 
prettyQualifiedSignature()357   public String prettyQualifiedSignature() {
358     return qualifiedName() + prettyParameters();
359   }
360 
361   /**
362    * Returns a printable version of the parameters of this method's signature.
363    */
prettyParameters()364   public String prettyParameters() {
365     StringBuilder params = new StringBuilder("(");
366     for (ParameterInfo pInfo : mParameters) {
367       if (params.length() > 1) {
368         params.append(",");
369       }
370       params.append(pInfo.type().simpleTypeName());
371     }
372 
373     params.append(")");
374     return params.toString();
375   }
376 
377   /**
378    * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}.
379    */
getHashableName()380   public String getHashableName() {
381     StringBuilder result = new StringBuilder();
382     result.append(name());
383 
384     if (mParameters == null) {
385         return result.toString();
386     }
387 
388     int i = 0;
389     for (ParameterInfo param : mParameters) {
390       result.append(":");
391       if (i == (mParameters.size()-1) && isVarArgs()) {
392         // TODO: note that this does not attempt to handle hypothetical
393         // vararg methods whose last parameter is a list of arrays, e.g.
394         // "Object[]...".
395         result.append(param.type().fullNameNoDimension(typeVariables())).append("...");
396       } else {
397         result.append(param.type().fullName(typeVariables()));
398       }
399       i++;
400     }
401     return result.toString();
402   }
403 
inList(ClassInfo item, ThrowsTagInfo[] list)404   private boolean inList(ClassInfo item, ThrowsTagInfo[] list) {
405     int len = list.length;
406     String qn = item.qualifiedName();
407     for (int i = 0; i < len; i++) {
408       ClassInfo ex = list[i].exception();
409       if (ex != null && ex.qualifiedName().equals(qn)) {
410         return true;
411       }
412     }
413     return false;
414   }
415 
throwsTags()416   public ThrowsTagInfo[] throwsTags() {
417     if (mThrowsTags == null) {
418       ThrowsTagInfo[] documented = comment().throwsTags();
419       ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
420 
421       int len = documented.length;
422       for (int i = 0; i < len; i++) {
423         rv.add(documented[i]);
424       }
425 
426       for (ClassInfo cl : mThrownExceptions) {
427         if (documented == null || !inList(cl, documented)) {
428           rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "",
429               containingClass(), position()));
430         }
431       }
432 
433       mThrowsTags = rv.toArray(ThrowsTagInfo.getArray(rv.size()));
434     }
435     return mThrowsTags;
436   }
437 
indexOfParam(String name, ParamTagInfo[] list)438   private static int indexOfParam(String name, ParamTagInfo[] list) {
439     final int N = list.length;
440     for (int i = 0; i < N; i++) {
441       if (name.equals(list[i].parameterName())) {
442         return i;
443       }
444     }
445     return -1;
446   }
447 
448   /* Checks whether the name documented with the provided @param tag
449    * actually matches one of the method parameters. */
isParamTagInMethod(ParamTagInfo tag)450   private boolean isParamTagInMethod(ParamTagInfo tag) {
451     for (ParameterInfo paramInfo : mParameters) {
452       if (paramInfo.name().equals(tag.parameterName())) {
453         return true;
454       }
455     }
456     return false;
457   }
458 
paramTags()459   public ParamTagInfo[] paramTags() {
460     if (mParamTags == null) {
461       final int N = mParameters.size();
462       final String DEFAULT_COMMENT = "<!-- no parameter comment -->";
463 
464       if (N == 0) {
465           // Early out for empty case.
466           mParamTags = ParamTagInfo.EMPTY_ARRAY;
467           return ParamTagInfo.EMPTY_ARRAY;
468       }
469       // Where we put each result
470       mParamTags = ParamTagInfo.getArray(N);
471 
472       // collect all the @param tag info
473       ParamTagInfo[] paramTags = comment().paramTags();
474 
475       // Complain about misnamed @param tags
476       for (ParamTagInfo tag : paramTags) {
477         if (!isParamTagInMethod(tag) && !tag.isTypeParameter()){
478           Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
479               "@param tag with name that doesn't match the parameter list: '"
480               + tag.parameterName() + "'");
481         }
482       }
483 
484       // Loop the parameters known from the method signature...
485       // Start by getting the known parameter name and data type. Then, if
486       // there's an @param tag that matches the current parameter name, get the
487       // javadoc comments. But if there's no @param comments here, then
488       // check if it's available from the parent class.
489       int i = 0;
490       for (ParameterInfo param : mParameters) {
491         String name = param.name();
492         String type = param.type().simpleTypeName();
493         String comment = DEFAULT_COMMENT;
494         SourcePositionInfo position = param.position();
495 
496         // Find the matching param from the @param tags in order to get
497         // the parameter comments
498         int index = indexOfParam(name, paramTags);
499         if (index >= 0) {
500           comment = paramTags[index].parameterComment();
501           position = paramTags[index].position();
502         }
503 
504         // get our parent's tags to fill in the blanks
505         MethodInfo overridden = this.findOverriddenMethod(name(), signature());
506         if (overridden != null) {
507           ParamTagInfo[] maternal = overridden.paramTags();
508           if (comment.equals(DEFAULT_COMMENT)) {
509             comment = maternal[i].parameterComment();
510             position = maternal[i].position();
511           }
512         }
513 
514         // Collect all docs requested by annotations
515         TagInfo[] auxTags = Doclava.auxSource.paramAuxTags(this, param, comment);
516 
517         // Okay, now add the collected parameter information to the method data
518         mParamTags[i] =
519             new ParamTagInfo("@param", type, name + " " + comment, parent(),
520                 position, auxTags);
521 
522         // while we're here, if we find any parameters that are still
523         // undocumented at this point, complain. This warning is off by
524         // default, because it's really, really common;
525         // but, it's good to be able to enforce it.
526         if (comment.equals(DEFAULT_COMMENT)) {
527           Errors.error(Errors.UNDOCUMENTED_PARAMETER, position,
528               "Undocumented parameter '" + name + "' on method '"
529               + name() + "'");
530         } else {
531           Doclava.linter.lintParameter(this, param, position, mParamTags[i]);
532         }
533         i++;
534       }
535     }
536     return mParamTags;
537   }
538 
seeTags()539   public SeeTagInfo[] seeTags() {
540     SeeTagInfo[] result = comment().seeTags();
541     if (result == null) {
542       if (mOverriddenMethod != null) {
543         result = mOverriddenMethod.seeTags();
544       }
545     }
546     return result;
547   }
548 
deprecatedTags()549   public TagInfo[] deprecatedTags() {
550     TagInfo[] result = comment().deprecatedTags();
551     if (result.length == 0) {
552       if (comment().undeprecateTags().length == 0) {
553         if (mOverriddenMethod != null) {
554           result = mOverriddenMethod.deprecatedTags();
555         }
556       }
557     }
558     return result;
559   }
560 
parameters()561   public ArrayList<ParameterInfo> parameters() {
562     return mParameters;
563   }
564 
565 
matchesParams(String[] params, String[] dimensions, boolean varargs)566   public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) {
567     if (mParamStrings == null) {
568       if (mParameters.size() != params.length) {
569         return false;
570       }
571       int i = 0;
572       for (ParameterInfo mine : mParameters) {
573         // If the method we're matching against is a varargs method (varargs == true), then
574         // only its last parameter is varargs.
575         if (!mine.matchesDimension(dimensions[i], (i == params.length - 1) ? varargs : false)) {
576           return false;
577         }
578         TypeInfo myType = mine.type();
579         String qualifiedName = myType.qualifiedTypeName();
580         String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName();
581         String s = params[i];
582 
583         // Check for a matching generic name or best known type
584         if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) {
585           return false;
586         }
587         i++;
588       }
589     }
590     return true;
591   }
592 
593   /**
594    * Checks to see if a parameter from a method signature is
595    * compatible with a parameter given in a {@code @link} tag.
596    */
matchesType(String signatureParam, String callerParam)597   private boolean matchesType(String signatureParam, String callerParam) {
598     int signatureLength = signatureParam.length();
599     int callerLength = callerParam.length();
600     return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength
601         && signatureParam.charAt(signatureLength - callerLength - 1) == '.'
602         && signatureParam.endsWith(callerParam))));
603   }
604 
makeHDF(Data data, String base)605   public void makeHDF(Data data, String base) {
606     makeHDF(data, base, Collections.<String, TypeInfo>emptyMap());
607   }
608 
makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping)609   public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) {
610     data.setValue(base + ".kind", kind());
611     data.setValue(base + ".name", name());
612     data.setValue(base + ".href", htmlPage());
613     data.setValue(base + ".anchor", anchor());
614 
615     if (mReturnType != null) {
616       returnType().getTypeWithArguments(typeMapping).makeHDF(
617           data, base + ".returnType", false, typeVariables());
618       data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
619     }
620 
621     data.setValue(base + ".default", mIsDefault ? "default" : "");
622     data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
623     data.setValue(base + ".final", isFinal() ? "final" : "");
624     data.setValue(base + ".static", isStatic() ? "static" : "");
625 
626     TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
627     TagInfo.makeHDF(data, base + ".descr", inlineTags());
628     TagInfo.makeHDF(data, base + ".descrAux", Doclava.auxSource.methodAuxTags(this));
629     TagInfo.makeHDF(data, base + ".blockTags", blockTags());
630     TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
631     TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
632     data.setValue(base + ".since", getSince());
633     if (isDeprecated()) {
634       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
635     }
636     ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
637     AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
638     ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
639     ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(
640         new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables(), typeMapping);
641     if (isProtected()) {
642       data.setValue(base + ".scope", "protected");
643     } else if (isPublic()) {
644       data.setValue(base + ".scope", "public");
645     }
646     TagInfo.makeHDF(data, base + ".returns", returnTags());
647     TagInfo.makeHDF(data, base + ".returnsAux", Doclava.auxSource.returnAuxTags(this));
648 
649     if (mTypeParameters != null) {
650       TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
651     }
652 
653     int numAnnotationDocumentation = 0;
654     for (AnnotationInstanceInfo aii : annotations()) {
655       String annotationDocumentation = Doclava.getDocumentationStringForAnnotation(
656           aii.type().qualifiedName());
657       if (annotationDocumentation != null) {
658         data.setValue(base + ".annotationdocumentation." + numAnnotationDocumentation + ".text",
659             annotationDocumentation);
660         numAnnotationDocumentation++;
661       }
662     }
663 
664     AnnotationInstanceInfo.makeLinkListHDF(
665       data,
666       base + ".showAnnotations",
667       showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()]));
668 
669     setFederatedReferences(data, base);
670 
671     Doclava.linter.lintMethod(this);
672   }
673 
typeVariables()674   public HashSet<String> typeVariables() {
675     HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
676     ClassInfo cl = containingClass();
677     while (cl != null) {
678         ArrayList<TypeInfo> types = cl.asTypeInfo().typeArguments();
679       if (types != null) {
680         TypeInfo.typeVariables(types, result);
681       }
682       cl = cl.containingClass();
683     }
684     return result;
685   }
686 
687   @Override
isExecutable()688   public boolean isExecutable() {
689     return true;
690   }
691 
thrownExceptions()692   public ArrayList<ClassInfo> thrownExceptions() {
693     return mThrownExceptions;
694   }
695 
typeArgumentsName(HashSet<String> typeVars)696   public String typeArgumentsName(HashSet<String> typeVars) {
697     if (!hasTypeParameters()) {
698       return "";
699     } else {
700       return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
701     }
702   }
703 
isAnnotationElement()704   public boolean isAnnotationElement() {
705     return mIsAnnotationElement;
706   }
707 
defaultAnnotationElementValue()708   public AnnotationValueInfo defaultAnnotationElementValue() {
709     return mDefaultAnnotationElementValue;
710   }
711 
setVarargs(boolean set)712   public void setVarargs(boolean set) {
713     mIsVarargs = set;
714   }
715 
isVarArgs()716   public boolean isVarArgs() {
717     return mIsVarargs;
718   }
719 
isEffectivelyFinal()720   public boolean isEffectivelyFinal() {
721       if (mIsFinal) {
722           return true;
723       }
724       ClassInfo containingClass = containingClass();
725       if (containingClass != null && containingClass.isEffectivelyFinal()) {
726           return true;
727       }
728       return false;
729   }
730 
731   @Override
toString()732   public String toString() {
733     return this.name();
734   }
735 
736   @Override
equals(Object o)737   public boolean equals(Object o) {
738     if (this == o) {
739       return true;
740     } else if (o instanceof MethodInfo) {
741       final MethodInfo m = (MethodInfo) o;
742       return mName.equals(m.mName) && signature().equals(m.signature());
743     } else {
744       return false;
745     }
746   }
747 
748   @Override
hashCode()749   public int hashCode() {
750     return Objects.hash(mName, signature());
751   }
752 
setReason(String reason)753   public void setReason(String reason) {
754     mReasonOpened = reason;
755   }
756 
getReason()757   public String getReason() {
758     return mReasonOpened;
759   }
760 
addException(String exec)761   public void addException(String exec) {
762     ClassInfo exceptionClass = new ClassInfo(exec);
763 
764     mThrownExceptions.add(exceptionClass);
765   }
766 
addParameter(ParameterInfo p)767   public void addParameter(ParameterInfo p) {
768     // Name information
769     if (mParameters == null) {
770         mParameters = new ArrayList<ParameterInfo>();
771     }
772 
773     mParameters.add(p);
774   }
775 
776   private String mFlatSignature;
777   private MethodInfo mOverriddenMethod;
778   private TypeInfo mReturnType;
779   private boolean mIsAnnotationElement;
780   private boolean mIsAbstract;
781   private boolean mIsSynchronized;
782   private boolean mIsNative;
783   private boolean mIsVarargs;
784   private boolean mDeprecatedKnown;
785   private boolean mIsDeprecated;
786   private boolean mIsDefault;
787   private ArrayList<ParameterInfo> mParameters;
788   private ArrayList<ClassInfo> mThrownExceptions;
789   private String[] mParamStrings;
790   private ThrowsTagInfo[] mThrowsTags;
791   private ParamTagInfo[] mParamTags;
792   private ArrayList<TypeInfo> mTypeParameters;
793   private AnnotationValueInfo mDefaultAnnotationElementValue;
794   private String mReasonOpened;
795   private ArrayList<Resolution> mResolutions;
796 
797   // TODO: merge with droiddoc version (above)
qualifiedName()798   public String qualifiedName() {
799     String parentQName = (containingClass() != null)
800         ? (containingClass().qualifiedName() + ".") : "";
801     // TODO: This logic doesn't work well with constructors, as name() for constructors already
802     // contains the containingClass's name, leading to things like A.B.B() being rendered as A.B.A.B()
803     return parentQName + name();
804   }
805 
806   @Override
signature()807   public String signature() {
808     if (mSignature == null) {
809       StringBuilder params = new StringBuilder("(");
810       for (ParameterInfo pInfo : mParameters) {
811         if (params.length() > 1) {
812           params.append(", ");
813         }
814         params.append(pInfo.type().fullName());
815       }
816 
817       params.append(")");
818       mSignature = params.toString();
819     }
820     return mSignature;
821   }
822 
matches(MethodInfo other)823   public boolean matches(MethodInfo other) {
824     return prettySignature().equals(other.prettySignature());
825   }
826 
throwsException(ClassInfo exception)827   public boolean throwsException(ClassInfo exception) {
828     for (ClassInfo e : mThrownExceptions) {
829       if (e.qualifiedName().equals(exception.qualifiedName())) {
830         return true;
831       }
832     }
833     return false;
834   }
835 
isConsistent(MethodInfo mInfo)836   public boolean isConsistent(MethodInfo mInfo) {
837     boolean consistent = true;
838     if (!mReturnType.isTypeVariable() && !mInfo.mReturnType.isTypeVariable()) {
839       if (!mReturnType.equals(mInfo.mReturnType) ||
840           mReturnType.dimension() != mInfo.mReturnType.dimension()) {
841         consistent = false;
842       }
843     } else if (!mReturnType.isTypeVariable() && mInfo.mReturnType.isTypeVariable()) {
844       List<ClassInfo> constraints = mInfo.resolveConstraints(mInfo.mReturnType);
845       for (ClassInfo constraint : constraints) {
846         if (!constraint.isAssignableTo(mReturnType.qualifiedTypeName())) {
847           consistent = false;
848         }
849       }
850     } else if (mReturnType.isTypeVariable() && !mInfo.mReturnType.isTypeVariable()) {
851       // It's never valid to go from being a parameterized type to not being one.
852       // This would drop the implicit cast breaking backwards compatibility.
853       consistent = false;
854     } else {
855       // If both return types are parameterized then the constraints must be
856       // exactly the same.
857       List<ClassInfo> currentConstraints = mInfo.resolveConstraints(mReturnType);
858       List<ClassInfo> newConstraints = mInfo.resolveConstraints(mInfo.mReturnType);
859       if (currentConstraints.size() != newConstraints.size() ||
860           currentConstraints.retainAll(newConstraints)) {
861         consistent = false;
862       }
863     }
864 
865     if (!consistent) {
866       Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method "
867           + mInfo.prettyQualifiedSignature() + " has changed return type from " + mReturnType
868           + " to " + mInfo.mReturnType);
869     }
870 
871     if (mIsAbstract != mInfo.mIsAbstract) {
872       consistent = false;
873       Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method "
874           + mInfo.prettyQualifiedSignature() + " has changed 'abstract' qualifier");
875     }
876 
877     if (mIsNative != mInfo.mIsNative) {
878       consistent = false;
879       Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method "
880           + mInfo.prettyQualifiedSignature() + " has changed 'native' qualifier");
881     }
882 
883     if (!mIsStatic) {
884       // Compiler-generated methods vary in their 'final' qualifier between versions of
885       // the compiler, so this check needs to be quite narrow. A change in 'final'
886       // status of a method is only relevant if (a) the method is not declared 'static'
887       // and (b) the method is not already inferred to be 'final' by virtue of its class.
888       if (!isEffectivelyFinal() && mInfo.isEffectivelyFinal()) {
889         consistent = false;
890         Errors.error(Errors.ADDED_FINAL, mInfo.position(), "Method "
891             + mInfo.prettyQualifiedSignature() + " has added 'final' qualifier");
892       } else if (isEffectivelyFinal() && !mInfo.isEffectivelyFinal()) {
893         consistent = false;
894         Errors.error(Errors.REMOVED_FINAL, mInfo.position(), "Method "
895             + mInfo.prettyQualifiedSignature() + " has removed 'final' qualifier");
896       }
897     }
898 
899     if (mIsStatic != mInfo.mIsStatic) {
900       consistent = false;
901       Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method "
902           + mInfo.prettyQualifiedSignature() + " has changed 'static' qualifier");
903     }
904 
905     if (!scope().equals(mInfo.scope())) {
906       consistent = false;
907       Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method "
908           + mInfo.prettyQualifiedSignature() + " changed scope from " + scope()
909           + " to " + mInfo.scope());
910     }
911 
912     if (!isDeprecated() == mInfo.isDeprecated()) {
913       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method "
914           + mInfo.prettyQualifiedSignature() + " has changed deprecation state " + isDeprecated()
915           + " --> " + mInfo.isDeprecated());
916       consistent = false;
917     }
918 
919     // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
920     // "compatibility with existing binaries."
921     /*
922     if (mIsSynchronized != mInfo.mIsSynchronized) {
923       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
924           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
925           + mInfo.mIsSynchronized);
926       consistent = false;
927     }
928     */
929 
930     for (ClassInfo exception : thrownExceptions()) {
931       if (!mInfo.throwsException(exception)) {
932         // exclude 'throws' changes to finalize() overrides with no arguments
933         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
934           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method "
935               + mInfo.prettyQualifiedSignature() + " no longer throws exception "
936               + exception.qualifiedName());
937           consistent = false;
938         }
939       }
940     }
941 
942     for (ClassInfo exec : mInfo.thrownExceptions()) {
943       // exclude 'throws' changes to finalize() overrides with no arguments
944       if (!throwsException(exec)) {
945         if (!name().equals("finalize") || (!mParameters.isEmpty())) {
946           Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method "
947               + mInfo.prettyQualifiedSignature() + " added thrown exception "
948               + exec.qualifiedName());
949           consistent = false;
950         }
951       }
952     }
953 
954     return consistent;
955   }
956 
getTypeParameter(String qualifiedTypeName)957   public TypeInfo getTypeParameter(String qualifiedTypeName) {
958     if (hasTypeParameters()) {
959       for (TypeInfo parameter : mTypeParameters) {
960         if (parameter.qualifiedTypeName().equals(qualifiedTypeName)) {
961           return parameter;
962         }
963       }
964     }
965     return containingClass().getTypeParameter(qualifiedTypeName);
966   }
967 
968   // Given a type parameter it returns a list of all of the classes and interfaces it must extend
969   // and implement.
resolveConstraints(TypeInfo type)970   private List<ClassInfo> resolveConstraints(TypeInfo type) {
971     ApiInfo api = containingClass().containingPackage().containingApi();
972     List<ClassInfo> classes = new ArrayList<>();
973     Queue<TypeInfo> types = new LinkedList<>();
974     types.add(type);
975     while (!types.isEmpty()) {
976       type = types.poll();
977       if (!type.isTypeVariable()) {
978         ClassInfo cl = api.findClass(type.qualifiedTypeName());
979         if (cl != null) {
980           classes.add(cl);
981         }
982       } else {
983         TypeInfo parameter = getTypeParameter(type.qualifiedTypeName());
984         if (parameter.extendsBounds() != null) {
985           for (TypeInfo bound : parameter.extendsBounds()) {
986             types.add(bound);
987           }
988         }
989       }
990     }
991     return classes;
992   }
993 
printResolutions()994   public void printResolutions() {
995       if (mResolutions == null || mResolutions.isEmpty()) {
996           return;
997       }
998 
999       System.out.println("Resolutions for Method " + mName + mFlatSignature + ":");
1000 
1001       for (Resolution r : mResolutions) {
1002           System.out.println(r);
1003       }
1004   }
1005 
addResolution(Resolution resolution)1006   public void addResolution(Resolution resolution) {
1007       if (mResolutions == null) {
1008           mResolutions = new ArrayList<Resolution>();
1009       }
1010 
1011       mResolutions.add(resolution);
1012   }
1013 
resolveResolutions()1014   public boolean resolveResolutions() {
1015       ArrayList<Resolution> resolutions = mResolutions;
1016       mResolutions = new ArrayList<Resolution>();
1017 
1018       boolean allResolved = true;
1019       for (Resolution resolution : resolutions) {
1020           StringBuilder qualifiedClassName = new StringBuilder();
1021           InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
1022                   resolution.getInfoBuilder());
1023 
1024           // if we still couldn't resolve it, save it for the next pass
1025           if ("".equals(qualifiedClassName.toString())) {
1026               mResolutions.add(resolution);
1027               allResolved = false;
1028           } else if ("thrownException".equals(resolution.getVariable())) {
1029               mThrownExceptions.add(InfoBuilder.Caches.obtainClass(qualifiedClassName.toString()));
1030           }
1031       }
1032 
1033       return allResolved;
1034   }
1035 }
1036