1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.javadoc;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.ListIterator;
29  import java.util.Set;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  
33  import com.puppycrawl.tools.checkstyle.api.DetailAST;
34  import com.puppycrawl.tools.checkstyle.api.FileContents;
35  import com.puppycrawl.tools.checkstyle.api.FullIdent;
36  import com.puppycrawl.tools.checkstyle.api.Scope;
37  import com.puppycrawl.tools.checkstyle.api.TextBlock;
38  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
39  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
40  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
42  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
43  
44  /**
45   * Checks the Javadoc of a method or constructor.
46   *
47   *
48   * @noinspection deprecation
49   */
50  public class JavadocMethodCheck extends AbstractTypeAwareCheck {
51  
52      /**
53       * A key is pointing to the warning message text in "messages.properties"
54       * file.
55       */
56      public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
57  
58      /**
59       * A key is pointing to the warning message text in "messages.properties"
60       * file.
61       */
62      public static final String MSG_CLASS_INFO = "javadoc.classInfo";
63  
64      /**
65       * A key is pointing to the warning message text in "messages.properties"
66       * file.
67       */
68      public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
69  
70      /**
71       * A key is pointing to the warning message text in "messages.properties"
72       * file.
73       */
74      public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
75  
76      /**
77       * A key is pointing to the warning message text in "messages.properties"
78       * file.
79       */
80      public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
81  
82      /**
83       * A key is pointing to the warning message text in "messages.properties"
84       * file.
85       */
86      public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
87  
88      /**
89       * A key is pointing to the warning message text in "messages.properties"
90       * file.
91       */
92      public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
93  
94      /**
95       * A key is pointing to the warning message text in "messages.properties"
96       * file.
97       */
98      public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
99  
100     /** Compiled regexp to match Javadoc tags that take an argument. */
101     private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern(
102             "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
103 
104     /** Compiled regexp to match first part of multilineJavadoc tags. */
105     private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START = CommonUtil.createPattern(
106             "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s*$");
107 
108     /** Compiled regexp to look for a continuation of the comment. */
109     private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
110             CommonUtil.createPattern("(\\*/|@|[^\\s\\*])");
111 
112     /** Multiline finished at end of comment. */
113     private static final String END_JAVADOC = "*/";
114     /** Multiline finished at next Javadoc. */
115     private static final String NEXT_TAG = "@";
116 
117     /** Compiled regexp to match Javadoc tags with no argument. */
118     private static final Pattern MATCH_JAVADOC_NOARG =
119             CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S");
120     /** Compiled regexp to match first part of multilineJavadoc tags. */
121     private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
122             CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$");
123     /** Compiled regexp to match Javadoc tags with no argument and {}. */
124     private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
125             CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
126 
127     /** Default value of minimal amount of lines in method to allow no documentation.*/
128     private static final int DEFAULT_MIN_LINE_COUNT = -1;
129 
130     /** The visibility scope where Javadoc comments are checked. */
131     private Scope scope = Scope.PRIVATE;
132 
133     /** The visibility scope where Javadoc comments shouldn't be checked. */
134     private Scope excludeScope;
135 
136     /** Minimal amount of lines in method to allow no documentation.*/
137     private int minLineCount = DEFAULT_MIN_LINE_COUNT;
138 
139     /**
140      * Controls whether to allow documented exceptions that are not declared if
141      * they are a subclass of java.lang.RuntimeException.
142      */
143     // -@cs[AbbreviationAsWordInName] We can not change it as,
144     // check's property is part of API (used in configurations).
145     private boolean allowUndeclaredRTE;
146 
147     /**
148      * Allows validating throws tags.
149      */
150     private boolean validateThrows;
151 
152     /**
153      * Controls whether to allow documented exceptions that are subclass of one
154      * of declared exception. Defaults to false (backward compatibility).
155      */
156     private boolean allowThrowsTagsForSubclasses;
157 
158     /**
159      * Controls whether to ignore errors when a method has parameters but does
160      * not have matching param tags in the javadoc. Defaults to false.
161      */
162     private boolean allowMissingParamTags;
163 
164     /**
165      * Controls whether to ignore errors when a method declares that it throws
166      * exceptions but does not have matching throws tags in the javadoc.
167      * Defaults to false.
168      */
169     private boolean allowMissingThrowsTags;
170 
171     /**
172      * Controls whether to ignore errors when a method returns non-void type
173      * but does not have a return tag in the javadoc. Defaults to false.
174      */
175     private boolean allowMissingReturnTag;
176 
177     /**
178      * Controls whether to ignore errors when there is no javadoc. Defaults to
179      * false.
180      */
181     private boolean allowMissingJavadoc;
182 
183     /**
184      * Controls whether to allow missing Javadoc on accessor methods for
185      * properties (setters and getters).
186      */
187     private boolean allowMissingPropertyJavadoc;
188 
189     /** List of annotations that allow missed documentation. */
190     private List<String> allowedAnnotations = Collections.singletonList("Override");
191 
192     /** Method names that match this pattern do not require javadoc blocks. */
193     private Pattern ignoreMethodNamesRegex;
194 
195     /**
196      * Set regex for matching method names to ignore.
197      * @param pattern a pattern.
198      */
199     public void setIgnoreMethodNamesRegex(Pattern pattern) {
200         ignoreMethodNamesRegex = pattern;
201     }
202 
203     /**
204      * Sets minimal amount of lines in method to allow no documentation.
205      * @param value user's value.
206      */
207     public void setMinLineCount(int value) {
208         minLineCount = value;
209     }
210 
211     /**
212      * Allow validating throws tag.
213      * @param value user's value.
214      */
215     public void setValidateThrows(boolean value) {
216         validateThrows = value;
217     }
218 
219     /**
220      * Sets list of annotations.
221      * @param userAnnotations user's value.
222      */
223     public void setAllowedAnnotations(String... userAnnotations) {
224         allowedAnnotations = Arrays.asList(userAnnotations);
225     }
226 
227     /**
228      * Set the scope.
229      *
230      * @param scope a scope.
231      */
232     public void setScope(Scope scope) {
233         this.scope = scope;
234     }
235 
236     /**
237      * Set the excludeScope.
238      *
239      * @param excludeScope a scope.
240      */
241     public void setExcludeScope(Scope excludeScope) {
242         this.excludeScope = excludeScope;
243     }
244 
245     /**
246      * Controls whether to allow documented exceptions that are not declared if
247      * they are a subclass of java.lang.RuntimeException.
248      *
249      * @param flag a {@code Boolean} value
250      */
251     // -@cs[AbbreviationAsWordInName] We can not change it as,
252     // check's property is part of API (used in configurations).
253     public void setAllowUndeclaredRTE(boolean flag) {
254         allowUndeclaredRTE = flag;
255     }
256 
257     /**
258      * Controls whether to allow documented exception that are subclass of one
259      * of declared exceptions.
260      *
261      * @param flag a {@code Boolean} value
262      */
263     public void setAllowThrowsTagsForSubclasses(boolean flag) {
264         allowThrowsTagsForSubclasses = flag;
265     }
266 
267     /**
268      * Controls whether to allow a method which has parameters to omit matching
269      * param tags in the javadoc. Defaults to false.
270      *
271      * @param flag a {@code Boolean} value
272      */
273     public void setAllowMissingParamTags(boolean flag) {
274         allowMissingParamTags = flag;
275     }
276 
277     /**
278      * Controls whether to allow a method which declares that it throws
279      * exceptions to omit matching throws tags in the javadoc. Defaults to
280      * false.
281      *
282      * @param flag a {@code Boolean} value
283      */
284     public void setAllowMissingThrowsTags(boolean flag) {
285         allowMissingThrowsTags = flag;
286     }
287 
288     /**
289      * Controls whether to allow a method which returns non-void type to omit
290      * the return tag in the javadoc. Defaults to false.
291      *
292      * @param flag a {@code Boolean} value
293      */
294     public void setAllowMissingReturnTag(boolean flag) {
295         allowMissingReturnTag = flag;
296     }
297 
298     /**
299      * Controls whether to ignore errors when there is no javadoc. Defaults to
300      * false.
301      *
302      * @param flag a {@code Boolean} value
303      */
304     public void setAllowMissingJavadoc(boolean flag) {
305         allowMissingJavadoc = flag;
306     }
307 
308     /**
309      * Controls whether to ignore errors when there is no javadoc for a
310      * property accessor (setter/getter methods). Defaults to false.
311      *
312      * @param flag a {@code Boolean} value
313      */
314     public void setAllowMissingPropertyJavadoc(final boolean flag) {
315         allowMissingPropertyJavadoc = flag;
316     }
317 
318     @Override
319     public int[] getDefaultTokens() {
320         return getAcceptableTokens();
321     }
322 
323     @Override
324     public int[] getAcceptableTokens() {
325         return new int[] {
326             TokenTypes.PACKAGE_DEF,
327             TokenTypes.IMPORT,
328             TokenTypes.CLASS_DEF,
329             TokenTypes.ENUM_DEF,
330             TokenTypes.INTERFACE_DEF,
331             TokenTypes.METHOD_DEF,
332             TokenTypes.CTOR_DEF,
333             TokenTypes.ANNOTATION_FIELD_DEF,
334         };
335     }
336 
337     @Override
338     protected final void processAST(DetailAST ast) {
339         final Scope theScope = calculateScope(ast);
340         if (shouldCheck(ast, theScope)) {
341             final FileContents contents = getFileContents();
342             final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
343 
344             if (textBlock == null) {
345                 if (!isMissingJavadocAllowed(ast)) {
346                     log(ast, MSG_JAVADOC_MISSING);
347                 }
348             }
349             else {
350                 checkComment(ast, textBlock);
351             }
352         }
353     }
354 
355     /**
356      * Some javadoc.
357      * @param methodDef Some javadoc.
358      * @return Some javadoc.
359      */
360     private static int getMethodsNumberOfLine(DetailAST methodDef) {
361         final int numberOfLines;
362         final DetailAST lcurly = methodDef.getLastChild();
363         final DetailAST rcurly = lcurly.getLastChild();
364 
365         if (lcurly.getFirstChild() == rcurly) {
366             numberOfLines = 1;
367         }
368         else {
369             numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
370         }
371         return numberOfLines;
372     }
373 
374     @Override
375     protected final void logLoadError(Token ident) {
376         logLoadErrorImpl(ident.getLineNo(), ident.getColumnNo(),
377             MSG_CLASS_INFO,
378             JavadocTagInfo.THROWS.getText(), ident.getText());
379     }
380 
381     /**
382      * Checks if a missing Javadoc is allowed by the check's configuration.
383      * @param ast the tree node for the method or constructor.
384      * @return True if this method or constructor doesn't need Javadoc.
385      */
386     private boolean isMissingJavadocAllowed(final DetailAST ast) {
387         return allowMissingJavadoc
388             || allowMissingPropertyJavadoc
389                 && (CheckUtil.isSetterMethod(ast) || CheckUtil.isGetterMethod(ast))
390             || matchesSkipRegex(ast)
391             || isContentsAllowMissingJavadoc(ast);
392     }
393 
394     /**
395      * Checks if the Javadoc can be missing if the method or constructor is
396      * below the minimum line count or has a special annotation.
397      *
398      * @param ast the tree node for the method or constructor.
399      * @return True if this method or constructor doesn't need Javadoc.
400      */
401     private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
402         return (ast.getType() == TokenTypes.METHOD_DEF || ast.getType() == TokenTypes.CTOR_DEF)
403                 && (getMethodsNumberOfLine(ast) <= minLineCount
404                     || AnnotationUtil.containsAnnotation(ast, allowedAnnotations));
405     }
406 
407     /**
408      * Checks if the given method name matches the regex. In that case
409      * we skip enforcement of javadoc for this method
410      * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
411      * @return true if given method name matches the regex.
412      */
413     private boolean matchesSkipRegex(DetailAST methodDef) {
414         boolean result = false;
415         if (ignoreMethodNamesRegex != null) {
416             final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
417             final String methodName = ident.getText();
418 
419             final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
420             if (matcher.matches()) {
421                 result = true;
422             }
423         }
424         return result;
425     }
426 
427     /**
428      * Whether we should check this node.
429      *
430      * @param ast a given node.
431      * @param nodeScope the scope of the node.
432      * @return whether we should check a given node.
433      */
434     private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
435         final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
436 
437         return (excludeScope == null
438                 || nodeScope != excludeScope
439                 && surroundingScope != excludeScope)
440             && nodeScope.isIn(scope)
441             && surroundingScope.isIn(scope);
442     }
443 
444     /**
445      * Checks the Javadoc for a method.
446      *
447      * @param ast the token for the method
448      * @param comment the Javadoc comment
449      */
450     private void checkComment(DetailAST ast, TextBlock comment) {
451         final List<JavadocTag> tags = getMethodTags(comment);
452 
453         if (!hasShortCircuitTag(ast, tags)) {
454             if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
455                 checkReturnTag(tags, ast.getLineNo(), true);
456             }
457             else {
458                 final Iterator<JavadocTag> it = tags.iterator();
459                 // Check for inheritDoc
460                 boolean hasInheritDocTag = false;
461                 while (!hasInheritDocTag && it.hasNext()) {
462                     hasInheritDocTag = it.next().isInheritDocTag();
463                 }
464                 final boolean reportExpectedTags = !hasInheritDocTag
465                     && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
466 
467                 checkParamTags(tags, ast, reportExpectedTags);
468                 checkThrowsTags(tags, getThrows(ast), reportExpectedTags);
469                 if (CheckUtil.isNonVoidMethod(ast)) {
470                     checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
471                 }
472             }
473 
474             // Dump out all unused tags
475             tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
476                 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
477         }
478     }
479 
480     /**
481      * Validates whether the Javadoc has a short circuit tag. Currently this is
482      * the inheritTag. Any errors are logged.
483      *
484      * @param ast the construct being checked
485      * @param tags the list of Javadoc tags associated with the construct
486      * @return true if the construct has a short circuit tag.
487      */
488     private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
489         boolean result = true;
490         // Check if it contains {@inheritDoc} tag
491         if (tags.size() == 1
492                 && tags.get(0).isInheritDocTag()) {
493             // Invalid if private, a constructor, or a static method
494             if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
495                 log(ast, MSG_INVALID_INHERIT_DOC);
496             }
497         }
498         else {
499             result = false;
500         }
501         return result;
502     }
503 
504     /**
505      * Returns the scope for the method/constructor at the specified AST. If
506      * the method is in an interface or annotation block, the scope is assumed
507      * to be public.
508      *
509      * @param ast the token of the method/constructor
510      * @return the scope of the method/constructor
511      */
512     private static Scope calculateScope(final DetailAST ast) {
513         final Scope scope;
514 
515         if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
516             scope = Scope.PUBLIC;
517         }
518         else {
519             final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
520             scope = ScopeUtil.getScopeFromMods(mods);
521         }
522         return scope;
523     }
524 
525     /**
526      * Returns the tags in a javadoc comment. Only finds throws, exception,
527      * param, return and see tags.
528      *
529      * @param comment the Javadoc comment
530      * @return the tags found
531      */
532     private static List<JavadocTag> getMethodTags(TextBlock comment) {
533         final String[] lines = comment.getText();
534         final List<JavadocTag> tags = new ArrayList<>();
535         int currentLine = comment.getStartLineNo() - 1;
536         final int startColumnNumber = comment.getStartColNo();
537 
538         for (int i = 0; i < lines.length; i++) {
539             currentLine++;
540             final Matcher javadocArgMatcher =
541                 MATCH_JAVADOC_ARG.matcher(lines[i]);
542             final Matcher javadocNoargMatcher =
543                 MATCH_JAVADOC_NOARG.matcher(lines[i]);
544             final Matcher noargCurlyMatcher =
545                 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
546             final Matcher argMultilineStart =
547                 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]);
548             final Matcher noargMultilineStart =
549                 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
550 
551             if (javadocArgMatcher.find()) {
552                 final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
553                 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
554                         javadocArgMatcher.group(2)));
555             }
556             else if (javadocNoargMatcher.find()) {
557                 final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
558                 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
559             }
560             else if (noargCurlyMatcher.find()) {
561                 final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
562                 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
563             }
564             else if (argMultilineStart.find()) {
565                 final int col = calculateTagColumn(argMultilineStart, i, startColumnNumber);
566                 tags.addAll(getMultilineArgTags(argMultilineStart, col, lines, i, currentLine));
567             }
568             else if (noargMultilineStart.find()) {
569                 tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
570             }
571         }
572         return tags;
573     }
574 
575     /**
576      * Calculates column number using Javadoc tag matcher.
577      * @param javadocTagMatcher found javadoc tag matcher
578      * @param lineNumber line number of Javadoc tag in comment
579      * @param startColumnNumber column number of Javadoc comment beginning
580      * @return column number
581      */
582     private static int calculateTagColumn(Matcher javadocTagMatcher,
583             int lineNumber, int startColumnNumber) {
584         int col = javadocTagMatcher.start(1) - 1;
585         if (lineNumber == 0) {
586             col += startColumnNumber;
587         }
588         return col;
589     }
590 
591     /**
592      * Gets multiline Javadoc tags with arguments.
593      * @param argMultilineStart javadoc tag Matcher
594      * @param column column number of Javadoc tag
595      * @param lines comment text lines
596      * @param lineIndex line number that contains the javadoc tag
597      * @param tagLine javadoc tag line number in file
598      * @return javadoc tags with arguments
599      */
600     private static List<JavadocTag> getMultilineArgTags(final Matcher argMultilineStart,
601             final int column, final String[] lines, final int lineIndex, final int tagLine) {
602         final List<JavadocTag> tags = new ArrayList<>();
603         final String param1 = argMultilineStart.group(1);
604         final String param2 = argMultilineStart.group(2);
605         for (int remIndex = lineIndex + 1; remIndex < lines.length; remIndex++) {
606             final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
607             if (multilineCont.find()) {
608                 final String lFin = multilineCont.group(1);
609                 if (!lFin.equals(NEXT_TAG)
610                     && !lFin.equals(END_JAVADOC)) {
611                     tags.add(new JavadocTag(tagLine, column, param1, param2));
612                 }
613                 break;
614             }
615         }
616 
617         return tags;
618     }
619 
620     /**
621      * Gets multiline Javadoc tags with no arguments.
622      * @param noargMultilineStart javadoc tag Matcher
623      * @param lines comment text lines
624      * @param lineIndex line number that contains the javadoc tag
625      * @param tagLine javadoc tag line number in file
626      * @return javadoc tags with no arguments
627      */
628     private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
629             final String[] lines, final int lineIndex, final int tagLine) {
630         int remIndex = lineIndex;
631         Matcher multilineCont;
632 
633         do {
634             remIndex++;
635             multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
636         } while (!multilineCont.find());
637 
638         final List<JavadocTag> tags = new ArrayList<>();
639         final String lFin = multilineCont.group(1);
640         if (!lFin.equals(NEXT_TAG)
641             && !lFin.equals(END_JAVADOC)) {
642             final String param1 = noargMultilineStart.group(1);
643             final int col = noargMultilineStart.start(1) - 1;
644 
645             tags.add(new JavadocTag(tagLine, col, param1));
646         }
647 
648         return tags;
649     }
650 
651     /**
652      * Computes the parameter nodes for a method.
653      *
654      * @param ast the method node.
655      * @return the list of parameter nodes for ast.
656      */
657     private static List<DetailAST> getParameters(DetailAST ast) {
658         final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
659         final List<DetailAST> returnValue = new ArrayList<>();
660 
661         DetailAST child = params.getFirstChild();
662         while (child != null) {
663             if (child.getType() == TokenTypes.PARAMETER_DEF) {
664                 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
665                 if (ident != null) {
666                     returnValue.add(ident);
667                 }
668             }
669             child = child.getNextSibling();
670         }
671         return returnValue;
672     }
673 
674     /**
675      * Computes the exception nodes for a method.
676      *
677      * @param ast the method node.
678      * @return the list of exception nodes for ast.
679      */
680     private List<ExceptionInfo> getThrows(DetailAST ast) {
681         final List<ExceptionInfo> returnValue = new ArrayList<>();
682         final DetailAST throwsAST = ast
683                 .findFirstToken(TokenTypes.LITERAL_THROWS);
684         if (throwsAST != null) {
685             DetailAST child = throwsAST.getFirstChild();
686             while (child != null) {
687                 if (child.getType() == TokenTypes.IDENT
688                         || child.getType() == TokenTypes.DOT) {
689                     final FullIdent ident = FullIdent.createFullIdent(child);
690                     final ExceptionInfo exceptionInfo = new ExceptionInfo(
691                             createClassInfo(new Token(ident), getCurrentClassName()));
692                     returnValue.add(exceptionInfo);
693                 }
694                 child = child.getNextSibling();
695             }
696         }
697         return returnValue;
698     }
699 
700     /**
701      * Checks a set of tags for matching parameters.
702      *
703      * @param tags the tags to check
704      * @param parent the node which takes the parameters
705      * @param reportExpectedTags whether we should report if do not find
706      *            expected tag
707      */
708     private void checkParamTags(final List<JavadocTag> tags,
709             final DetailAST parent, boolean reportExpectedTags) {
710         final List<DetailAST> params = getParameters(parent);
711         final List<DetailAST> typeParams = CheckUtil
712                 .getTypeParameters(parent);
713 
714         // Loop over the tags, checking to see they exist in the params.
715         final ListIterator<JavadocTag> tagIt = tags.listIterator();
716         while (tagIt.hasNext()) {
717             final JavadocTag tag = tagIt.next();
718 
719             if (!tag.isParamTag()) {
720                 continue;
721             }
722 
723             tagIt.remove();
724 
725             final String arg1 = tag.getFirstArg();
726             boolean found = removeMatchingParam(params, arg1);
727 
728             if (CommonUtil.startsWithChar(arg1, '<') && CommonUtil.endsWithChar(arg1, '>')) {
729                 found = searchMatchingTypeParameter(typeParams,
730                         arg1.substring(1, arg1.length() - 1));
731             }
732 
733             // Handle extra JavadocTag
734             if (!found) {
735                 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
736                         "@param", arg1);
737             }
738         }
739 
740         // Now dump out all type parameters/parameters without tags :- unless
741         // the user has chosen to suppress these problems
742         if (!allowMissingParamTags && reportExpectedTags) {
743             for (DetailAST param : params) {
744                 log(param, MSG_EXPECTED_TAG,
745                     JavadocTagInfo.PARAM.getText(), param.getText());
746             }
747 
748             for (DetailAST typeParam : typeParams) {
749                 log(typeParam, MSG_EXPECTED_TAG,
750                     JavadocTagInfo.PARAM.getText(),
751                     "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
752                     + ">");
753             }
754         }
755     }
756 
757     /**
758      * Returns true if required type found in type parameters.
759      * @param typeParams
760      *            list of type parameters
761      * @param requiredTypeName
762      *            name of required type
763      * @return true if required type found in type parameters.
764      */
765     private static boolean searchMatchingTypeParameter(List<DetailAST> typeParams,
766             String requiredTypeName) {
767         // Loop looking for matching type param
768         final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
769         boolean found = false;
770         while (typeParamsIt.hasNext()) {
771             final DetailAST typeParam = typeParamsIt.next();
772             if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
773                     .equals(requiredTypeName)) {
774                 found = true;
775                 typeParamsIt.remove();
776                 break;
777             }
778         }
779         return found;
780     }
781 
782     /**
783      * Remove parameter from params collection by name.
784      * @param params collection of DetailAST parameters
785      * @param paramName name of parameter
786      * @return true if parameter found and removed
787      */
788     private static boolean removeMatchingParam(List<DetailAST> params, String paramName) {
789         boolean found = false;
790         final Iterator<DetailAST> paramIt = params.iterator();
791         while (paramIt.hasNext()) {
792             final DetailAST param = paramIt.next();
793             if (param.getText().equals(paramName)) {
794                 found = true;
795                 paramIt.remove();
796                 break;
797             }
798         }
799         return found;
800     }
801 
802     /**
803      * Checks for only one return tag. All return tags will be removed from the
804      * supplied list.
805      *
806      * @param tags the tags to check
807      * @param lineNo the line number of the expected tag
808      * @param reportExpectedTags whether we should report if do not find
809      *            expected tag
810      */
811     private void checkReturnTag(List<JavadocTag> tags, int lineNo,
812         boolean reportExpectedTags) {
813         // Loop over tags finding return tags. After the first one, report an
814         // error.
815         boolean found = false;
816         final ListIterator<JavadocTag> it = tags.listIterator();
817         while (it.hasNext()) {
818             final JavadocTag javadocTag = it.next();
819             if (javadocTag.isReturnTag()) {
820                 if (found) {
821                     log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
822                             MSG_DUPLICATE_TAG,
823                             JavadocTagInfo.RETURN.getText());
824                 }
825                 found = true;
826                 it.remove();
827             }
828         }
829 
830         // Handle there being no @return tags :- unless
831         // the user has chosen to suppress these problems
832         if (!found && !allowMissingReturnTag && reportExpectedTags) {
833             log(lineNo, MSG_RETURN_EXPECTED);
834         }
835     }
836 
837     /**
838      * Checks a set of tags for matching throws.
839      *
840      * @param tags the tags to check
841      * @param throwsList the throws to check
842      * @param reportExpectedTags whether we should report if do not find
843      *            expected tag
844      */
845     private void checkThrowsTags(List<JavadocTag> tags,
846             List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
847         // Loop over the tags, checking to see they exist in the throws.
848         // The foundThrows used for performance only
849         final Set<String> foundThrows = new HashSet<>();
850         final ListIterator<JavadocTag> tagIt = tags.listIterator();
851         while (tagIt.hasNext()) {
852             final JavadocTag tag = tagIt.next();
853 
854             if (!tag.isThrowsTag()) {
855                 continue;
856             }
857             tagIt.remove();
858 
859             // Loop looking for matching throw
860             final String documentedEx = tag.getFirstArg();
861             final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
862                     .getColumnNo());
863             final AbstractClassInfo documentedClassInfo = createClassInfo(token,
864                     getCurrentClassName());
865             final boolean found = foundThrows.contains(documentedEx)
866                     || isInThrows(throwsList, documentedClassInfo, foundThrows);
867 
868             // Handle extra JavadocTag.
869             if (!found) {
870                 boolean reqd = true;
871                 if (allowUndeclaredRTE) {
872                     reqd = !isUnchecked(documentedClassInfo.getClazz());
873                 }
874 
875                 if (reqd && validateThrows) {
876                     log(tag.getLineNo(), tag.getColumnNo(),
877                         MSG_UNUSED_TAG,
878                         JavadocTagInfo.THROWS.getText(), tag.getFirstArg());
879                 }
880             }
881         }
882         // Now dump out all throws without tags :- unless
883         // the user has chosen to suppress these problems
884         if (!allowMissingThrowsTags && reportExpectedTags) {
885             throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
886                 .forEach(exceptionInfo -> {
887                     final Token token = exceptionInfo.getName();
888                     log(token.getLineNo(), token.getColumnNo(),
889                         MSG_EXPECTED_TAG,
890                         JavadocTagInfo.THROWS.getText(), token.getText());
891                 });
892         }
893     }
894 
895     /**
896      * Verifies that documented exception is in throws.
897      *
898      * @param throwsList list of throws
899      * @param documentedClassInfo documented exception class info
900      * @param foundThrows previously found throws
901      * @return true if documented exception is in throws.
902      */
903     private boolean isInThrows(List<ExceptionInfo> throwsList,
904             AbstractClassInfo documentedClassInfo, Set<String> foundThrows) {
905         boolean found = false;
906         ExceptionInfo foundException = null;
907 
908         // First look for matches on the exception name
909         for (ExceptionInfo exceptionInfo : throwsList) {
910             if (exceptionInfo.getName().getText().equals(
911                     documentedClassInfo.getName().getText())) {
912                 found = true;
913                 foundException = exceptionInfo;
914                 break;
915             }
916         }
917 
918         // Now match on the exception type
919         final ListIterator<ExceptionInfo> exceptionInfoIt = throwsList.listIterator();
920         while (!found && exceptionInfoIt.hasNext()) {
921             final ExceptionInfo exceptionInfo = exceptionInfoIt.next();
922 
923             if (documentedClassInfo.getClazz() == exceptionInfo.getClazz()) {
924                 found = true;
925                 foundException = exceptionInfo;
926             }
927             else if (allowThrowsTagsForSubclasses) {
928                 found = isSubclass(documentedClassInfo.getClazz(), exceptionInfo.getClazz());
929             }
930         }
931 
932         if (foundException != null) {
933             foundException.setFound();
934             foundThrows.add(documentedClassInfo.getName().getText());
935         }
936 
937         return found;
938     }
939 
940     /** Stores useful information about declared exception. */
941     private static class ExceptionInfo {
942 
943         /** Class information associated with this exception. */
944         private final AbstractClassInfo classInfo;
945         /** Does the exception have throws tag associated with. */
946         private boolean found;
947 
948         /**
949          * Creates new instance for {@code FullIdent}.
950          *
951          * @param classInfo class info
952          */
953         /* package */ ExceptionInfo(AbstractClassInfo classInfo) {
954             this.classInfo = classInfo;
955         }
956 
957         /** Mark that the exception has associated throws tag. */
958         private void setFound() {
959             found = true;
960         }
961 
962         /**
963          * Checks that the exception has throws tag associated with it.
964          * @return whether the exception has throws tag associated with
965          */
966         private boolean isFound() {
967             return found;
968         }
969 
970         /**
971          * Gets exception name.
972          * @return exception's name
973          */
974         private Token getName() {
975             return classInfo.getName();
976         }
977 
978         /**
979          * Gets exception class.
980          * @return class for this exception
981          */
982         private Class<?> getClazz() {
983             return classInfo.getClazz();
984         }
985 
986     }
987 
988 }