• 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 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.HashSet;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
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     @Override
36     public int compare(MethodInfo a, MethodInfo b) {
37       // TODO: expand to compare signature for better sorting
38       return a.name().compareTo(b.name());
39     }
40   };
41 
42   private class InlineTags implements InheritedTags {
tags()43     public TagInfo[] tags() {
44       return comment().tags();
45     }
46 
inherited()47     public InheritedTags inherited() {
48       MethodInfo m = findOverriddenMethod(name(), signature());
49       if (m != null) {
50         return m.inlineTags();
51       } else {
52         return null;
53       }
54     }
55   }
56 
addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)57   private static void addInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) {
58     for (ClassInfo i : ifaces) {
59       queue.add(i);
60     }
61     for (ClassInfo i : ifaces) {
62       addInterfaces(i.interfaces(), queue);
63     }
64   }
65 
66   // first looks for a superclass, and then does a breadth first search to
67   // find the least far away match
findOverriddenMethod(String name, String signature)68   public MethodInfo findOverriddenMethod(String name, String signature) {
69     if (mReturnType == null) {
70       // ctor
71       return null;
72     }
73     if (mOverriddenMethod != null) {
74       return mOverriddenMethod;
75     }
76 
77     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
78     addInterfaces(containingClass().interfaces(), queue);
79     for (ClassInfo iface : queue) {
80       for (MethodInfo me : iface.methods()) {
81         if (me.name().equals(name) && me.signature().equals(signature)
82             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0) {
83           return me;
84         }
85       }
86     }
87     return null;
88   }
89 
addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue)90   private static void addRealInterfaces(ArrayList<ClassInfo> ifaces, ArrayList<ClassInfo> queue) {
91     for (ClassInfo i : ifaces) {
92       queue.add(i);
93       if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) {
94         queue.add(i.superclass());
95       }
96     }
97     for (ClassInfo i : ifaces) {
98       addInterfaces(i.realInterfaces(), queue);
99     }
100   }
101 
findRealOverriddenMethod(String name, String signature, HashSet notStrippable)102   public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) {
103     if (mReturnType == null) {
104       // ctor
105       return null;
106     }
107     if (mOverriddenMethod != null) {
108       return mOverriddenMethod;
109     }
110 
111     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
112     if (containingClass().realSuperclass() != null
113         && containingClass().realSuperclass().isAbstract()) {
114       queue.add(containingClass().realSuperclass());
115     }
116     addInterfaces(containingClass().realInterfaces(), queue);
117     for (ClassInfo iface : queue) {
118       for (MethodInfo me : iface.methods()) {
119         if (me.name().equals(name) && me.signature().equals(signature)
120             && me.inlineTags().tags() != null && me.inlineTags().tags().length > 0
121             && notStrippable.contains(me.containingClass())) {
122           return me;
123         }
124       }
125     }
126     return null;
127   }
128 
findPredicateOverriddenMethod(Predicate<MemberInfo> predicate)129   public MethodInfo findPredicateOverriddenMethod(Predicate<MemberInfo> predicate) {
130     if (mReturnType == null) {
131       // ctor
132       return null;
133     }
134     if (mOverriddenMethod != null) {
135       if (equals(mOverriddenMethod) && !mOverriddenMethod.isStatic()
136           && predicate.test(mOverriddenMethod)) {
137         return mOverriddenMethod;
138       }
139     }
140 
141     for (ClassInfo clazz : containingClass().gatherAncestorClasses()) {
142       for (MethodInfo method : clazz.getExhaustiveMethods()) {
143         if (equals(method) && !method.isStatic() && predicate.test(method)) {
144           return method;
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().realSuperclass());
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       // Check to see that the JavaDoc contains @deprecated AND the method is marked as @Deprecated.
220       // Otherwise, warn.
221       // Note: We only do this for "included" classes (i.e. those we have source code for); we do
222       // not have comments for classes from .class files but we do know whether a method is marked
223       // as @Deprecated.
224       if (mContainingClass.isIncluded() && !isHiddenOrRemoved()
225           && commentDeprecated != annotationDeprecated) {
226         Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Method "
227             + mContainingClass.qualifiedName() + "." + name()
228             + ": @Deprecated annotation (" + (annotationDeprecated ? "" : "not ")
229             + "present) and @deprecated doc tag (" + (commentDeprecated ? "" : "not ")
230             + "present) do not match");
231       }
232 
233       mIsDeprecated = commentDeprecated | annotationDeprecated;
234       mDeprecatedKnown = true;
235     }
236     return mIsDeprecated;
237   }
238 
setDeprecated(boolean deprecated)239   public void setDeprecated(boolean deprecated) {
240     mDeprecatedKnown = true;
241     mIsDeprecated = deprecated;
242   }
243 
getTypeParameters()244   public ArrayList<TypeInfo> getTypeParameters() {
245     return mTypeParameters;
246   }
247 
hasTypeParameters()248   public boolean hasTypeParameters() {
249       return mTypeParameters != null && !mTypeParameters.isEmpty();
250   }
251 
252   /**
253    * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the
254    * typeArgumentMapping to the parameters and return types.
255    */
cloneForClass(ClassInfo newContainingClass, Map<String, TypeInfo> typeArgumentMapping)256   public MethodInfo cloneForClass(ClassInfo newContainingClass,
257       Map<String, TypeInfo> typeArgumentMapping) {
258     if (newContainingClass == containingClass()) {
259       return this;
260     }
261     TypeInfo returnType = (mReturnType != null)
262         ? mReturnType.getTypeWithArguments(typeArgumentMapping)
263         : null;
264     ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
265     for (ParameterInfo pi : mParameters) {
266       parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping));
267     }
268     MethodInfo result =
269         new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(),
270             newContainingClass, realContainingClass(), isPublic(), isProtected(),
271             isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract,
272             mIsSynchronized, mIsNative, mIsDefault, mIsAnnotationElement, kind(), mFlatSignature,
273             mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(),
274             annotations());
275     result.init(mDefaultAnnotationElementValue);
276     return result;
277   }
278 
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)279   public MethodInfo(String rawCommentText, ArrayList<TypeInfo> typeParameters, String name,
280       String signature, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic,
281       boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal,
282       boolean isStatic, boolean isSynthetic, boolean isAbstract, boolean isSynchronized,
283       boolean isNative, boolean isDefault, boolean isAnnotationElement, String kind,
284       String flatSignature, MethodInfo overriddenMethod, TypeInfo returnType,
285       ArrayList<ParameterInfo> parameters, ArrayList<ClassInfo> thrownExceptions,
286       SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations) {
287     // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
288     // the Java5-emitted base API description.
289     super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic,
290         isProtected, isPackagePrivate, isPrivate,
291         ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
292         isStatic, isSynthetic, kind, position, annotations);
293 
294     mReasonOpened = "0:0";
295     mIsAnnotationElement = isAnnotationElement;
296     mTypeParameters = typeParameters;
297     mIsAbstract = isAbstract;
298     mIsSynchronized = isSynchronized;
299     mIsNative = isNative;
300     mIsDefault = isDefault;
301     mFlatSignature = flatSignature;
302     mOverriddenMethod = overriddenMethod;
303     mReturnType = returnType;
304     mParameters = parameters;
305     mThrownExceptions = thrownExceptions;
306   }
307 
init(AnnotationValueInfo defaultAnnotationElementValue)308   public void init(AnnotationValueInfo defaultAnnotationElementValue) {
309     mDefaultAnnotationElementValue = defaultAnnotationElementValue;
310   }
311 
isAbstract()312   public boolean isAbstract() {
313     return mIsAbstract;
314   }
315 
isSynchronized()316   public boolean isSynchronized() {
317     return mIsSynchronized;
318   }
319 
isNative()320   public boolean isNative() {
321     return mIsNative;
322   }
323 
isDefault()324   public boolean isDefault() {
325     return mIsDefault;
326   }
327 
flatSignature()328   public String flatSignature() {
329     return mFlatSignature;
330   }
331 
inlineTags()332   public InheritedTags inlineTags() {
333     return new InlineTags();
334   }
335 
blockTags()336   public TagInfo[] blockTags() {
337     return comment().blockTags();
338   }
339 
firstSentenceTags()340   public InheritedTags firstSentenceTags() {
341     return new FirstSentenceTags();
342   }
343 
returnTags()344   public InheritedTags returnTags() {
345     return new ReturnTags();
346   }
347 
returnType()348   public TypeInfo returnType() {
349     return mReturnType;
350   }
351 
prettySignature()352   public String prettySignature() {
353     return name() + prettyParameters();
354   }
355 
prettyQualifiedSignature()356   public String prettyQualifiedSignature() {
357     return qualifiedName() + prettyParameters();
358   }
359 
360   /**
361    * Returns a printable version of the parameters of this method's signature.
362    */
prettyParameters()363   public String prettyParameters() {
364     StringBuilder params = new StringBuilder("(");
365     for (ParameterInfo pInfo : mParameters) {
366       if (params.length() > 1) {
367         params.append(",");
368       }
369       params.append(pInfo.type().simpleTypeName());
370     }
371 
372     params.append(")");
373     return params.toString();
374   }
375 
376   /**
377    * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}.
378    */
getHashableName()379   public String getHashableName() {
380     StringBuilder result = new StringBuilder();
381     result.append(name());
382 
383     if (mParameters == null) {
384         return result.toString();
385     }
386 
387     int i = 0;
388     for (ParameterInfo param : mParameters) {
389       result.append(":");
390       if (i == (mParameters.size()-1) && isVarArgs()) {
391         // TODO: note that this does not attempt to handle hypothetical
392         // vararg methods whose last parameter is a list of arrays, e.g.
393         // "Object[]...".
394         result.append(param.type().fullNameNoDimension(typeVariables())).append("...");
395       } else {
396         result.append(param.type().fullName(typeVariables()));
397       }
398       i++;
399     }
400     return result.toString();
401   }
402 
inList(ClassInfo item, ThrowsTagInfo[] list)403   private boolean inList(ClassInfo item, ThrowsTagInfo[] list) {
404     int len = list.length;
405     String qn = item.qualifiedName();
406     for (int i = 0; i < len; i++) {
407       ClassInfo ex = list[i].exception();
408       if (ex != null && ex.qualifiedName().equals(qn)) {
409         return true;
410       }
411     }
412     return false;
413   }
414 
throwsTags()415   public ThrowsTagInfo[] throwsTags() {
416     if (mThrowsTags == null) {
417       ThrowsTagInfo[] documented = comment().throwsTags();
418       ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
419 
420       int len = documented.length;
421       for (int i = 0; i < len; i++) {
422         rv.add(documented[i]);
423       }
424 
425       for (ClassInfo cl : mThrownExceptions) {
426         if (documented == null || !inList(cl, documented)) {
427           rv.add(new ThrowsTagInfo("@throws", "@throws", cl.qualifiedName(), cl, "",
428               containingClass(), position()));
429         }
430       }
431 
432       mThrowsTags = rv.toArray(ThrowsTagInfo.getArray(rv.size()));
433     }
434     return mThrowsTags;
435   }
436 
indexOfParam(String name, ParamTagInfo[] list)437   private static int indexOfParam(String name, ParamTagInfo[] list) {
438     final int N = list.length;
439     for (int i = 0; i < N; i++) {
440       if (name.equals(list[i].parameterName())) {
441         return i;
442       }
443     }
444     return -1;
445   }
446 
447   /* Checks whether the name documented with the provided @param tag
448    * actually matches one of the method parameters. */
isParamTagInMethod(ParamTagInfo tag)449   private boolean isParamTagInMethod(ParamTagInfo tag) {
450     for (ParameterInfo paramInfo : mParameters) {
451       if (paramInfo.name().equals(tag.parameterName())) {
452         return true;
453       }
454     }
455     return false;
456   }
457 
paramTags()458   public ParamTagInfo[] paramTags() {
459     if (mParamTags == null) {
460       final int N = mParameters.size();
461       final String DEFAULT_COMMENT = "<!-- no parameter comment -->";
462 
463       if (N == 0) {
464           // Early out for empty case.
465           mParamTags = ParamTagInfo.EMPTY_ARRAY;
466           return ParamTagInfo.EMPTY_ARRAY;
467       }
468       // Where we put each result
469       mParamTags = ParamTagInfo.getArray(N);
470 
471       // collect all the @param tag info
472       ParamTagInfo[] paramTags = comment().paramTags();
473 
474       // Complain about misnamed @param tags
475       for (ParamTagInfo tag : paramTags) {
476         if (!isParamTagInMethod(tag) && !tag.isTypeParameter()){
477           Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
478               "@param tag with name that doesn't match the parameter list: '"
479               + tag.parameterName() + "'");
480         }
481       }
482 
483       // Loop the parameters known from the method signature...
484       // Start by getting the known parameter name and data type. Then, if
485       // there's an @param tag that matches the current parameter name, get the
486       // javadoc comments. But if there's no @param comments here, then
487       // check if it's available from the parent class.
488       int i = 0;
489       for (ParameterInfo param : mParameters) {
490         String name = param.name();
491         String type = param.type().simpleTypeName();
492         String comment = DEFAULT_COMMENT;
493         SourcePositionInfo position = param.position();
494 
495         // Find the matching param from the @param tags in order to get
496         // the parameter comments
497         int index = indexOfParam(name, paramTags);
498         if (index >= 0) {
499           comment = paramTags[index].parameterComment();
500           position = paramTags[index].position();
501         }
502 
503         // get our parent's tags to fill in the blanks
504         MethodInfo overridden = this.findOverriddenMethod(name(), signature());
505         if (overridden != null) {
506           ParamTagInfo[] maternal = overridden.paramTags();
507           if (comment.equals(DEFAULT_COMMENT)) {
508             comment = maternal[i].parameterComment();
509             position = maternal[i].position();
510           }
511         }
512 
513         // Collect all docs requested by annotations
514         TagInfo[] auxTags = Doclava.auxSource.paramAuxTags(this, param, comment);
515 
516         // Okay, now add the collected parameter information to the method data
517         mParamTags[i] =
518             new ParamTagInfo("@param", type, name + " " + comment, parent(),
519                 position, auxTags);
520 
521         // while we're here, if we find any parameters that are still
522         // undocumented at this point, complain. This warning is off by
523         // default, because it's really, really common;
524         // but, it's good to be able to enforce it.
525         if (comment.equals(DEFAULT_COMMENT)) {
526           Errors.error(Errors.UNDOCUMENTED_PARAMETER, position,
527               "Undocumented parameter '" + name + "' on method '"
528               + name() + "'");
529         } else {
530           Doclava.linter.lintParameter(this, param, position, mParamTags[i]);
531         }
532         i++;
533       }
534     }
535     return mParamTags;
536   }
537 
seeTags()538   public SeeTagInfo[] seeTags() {
539     SeeTagInfo[] result = comment().seeTags();
540     if (result == null) {
541       if (mOverriddenMethod != null) {
542         result = mOverriddenMethod.seeTags();
543       }
544     }
545     return result;
546   }
547 
deprecatedTags()548   public TagInfo[] deprecatedTags() {
549     TagInfo[] result = comment().deprecatedTags();
550     if (result.length == 0) {
551       if (comment().undeprecateTags().length == 0) {
552         if (mOverriddenMethod != null) {
553           result = mOverriddenMethod.deprecatedTags();
554         }
555       }
556     }
557     return result;
558   }
559 
parameters()560   public ArrayList<ParameterInfo> parameters() {
561     return mParameters;
562   }
563 
564 
matchesParams(String[] params, String[] dimensions, boolean varargs)565   public boolean matchesParams(String[] params, String[] dimensions, boolean varargs) {
566     if (mParamStrings == null) {
567       if (mParameters.size() != params.length) {
568         return false;
569       }
570       int i = 0;
571       for (ParameterInfo mine : mParameters) {
572         // If the method we're matching against is a varargs method (varargs == true), then
573         // only its last parameter is varargs.
574         if (!mine.matchesDimension(dimensions[i], (i == params.length - 1) ? varargs : false)) {
575           return false;
576         }
577         TypeInfo myType = mine.type();
578         String qualifiedName = myType.qualifiedTypeName();
579         String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName();
580         String s = params[i];
581 
582         // Check for a matching generic name or best known type
583         if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) {
584           return false;
585         }
586         i++;
587       }
588     }
589     return true;
590   }
591 
592   /**
593    * Checks to see if a parameter from a method signature is
594    * compatible with a parameter given in a {@code @link} tag.
595    */
matchesType(String signatureParam, String callerParam)596   private boolean matchesType(String signatureParam, String callerParam) {
597     int signatureLength = signatureParam.length();
598     int callerLength = callerParam.length();
599     return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength
600         && signatureParam.charAt(signatureLength - callerLength - 1) == '.'
601         && signatureParam.endsWith(callerParam))));
602   }
603 
makeHDF(Data data, String base)604   public void makeHDF(Data data, String base) {
605     makeHDF(data, base, Collections.<String, TypeInfo>emptyMap());
606   }
607 
makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping)608   public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) {
609     data.setValue(base + ".kind", kind());
610     data.setValue(base + ".name", name());
611     data.setValue(base + ".href", htmlPage());
612     data.setValue(base + ".anchor", anchor());
613 
614     if (mReturnType != null) {
615       returnType().getTypeWithArguments(typeMapping).makeHDF(
616           data, base + ".returnType", false, typeVariables());
617       data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
618     }
619 
620     data.setValue(base + ".default", mIsDefault ? "default" : "");
621     data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
622     data.setValue(base + ".final", isFinal() ? "final" : "");
623     data.setValue(base + ".static", isStatic() ? "static" : "");
624 
625     TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
626     TagInfo.makeHDF(data, base + ".descr", inlineTags());
627     TagInfo.makeHDF(data, base + ".descrAux", Doclava.auxSource.methodAuxTags(this));
628     TagInfo.makeHDF(data, base + ".blockTags", blockTags());
629     TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
630     TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
631     data.setValue(base + ".since", getSince());
632     data.setValue(base + ".sdkextsince", getSdkExtSince());
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