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