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