View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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_sukhodoslky
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      * The JavadocMethodCheck is about to report a missing Javadoc.
414      * This hook can be used by derived classes to allow a missing javadoc
415      * in some situations.  The default implementation checks
416      * {@code allowMissingJavadoc} and
417      * {@code allowMissingPropertyJavadoc} properties, do not forget
418      * to call {@code super.isMissingJavadocAllowed(ast)} in case
419      * you want to keep this logic.
420      * @param ast the tree node for the method or constructor.
421      * @return True if this method or constructor doesn't need Javadoc.
422      */
423     private boolean isMissingJavadocAllowed(final DetailAST ast) {
424         return allowMissingJavadoc
425             || allowMissingPropertyJavadoc
426                 && (CheckUtils.isSetterMethod(ast) || CheckUtils.isGetterMethod(ast))
427             || matchesSkipRegex(ast)
428             || isContentsAllowMissingJavadoc(ast);
429     }
430 
431     /**
432      * Checks if the Javadoc can be missing if the method or constructor is
433      * below the minimum line count or has a special annotation.
434      *
435      * @param ast the tree node for the method or constructor.
436      * @return True if this method or constructor doesn't need Javadoc.
437      */
438     private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
439         return (ast.getType() == TokenTypes.METHOD_DEF || ast.getType() == TokenTypes.CTOR_DEF)
440                 && (getMethodsNumberOfLine(ast) <= minLineCount || hasAllowedAnnotations(ast));
441     }
442 
443     /**
444      * Checks if the given method name matches the regex. In that case
445      * we skip enforcement of javadoc for this method
446      * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
447      * @return true if given method name matches the regex.
448      */
449     private boolean matchesSkipRegex(DetailAST methodDef) {
450         boolean result = false;
451         if (ignoreMethodNamesRegex != null) {
452             final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
453             final String methodName = ident.getText();
454 
455             final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
456             if (matcher.matches()) {
457                 result = true;
458             }
459         }
460         return result;
461     }
462 
463     /**
464      * Whether we should check this node.
465      *
466      * @param ast a given node.
467      * @param nodeScope the scope of the node.
468      * @return whether we should check a given node.
469      */
470     private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
471         final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
472 
473         return (excludeScope == null
474                 || nodeScope != excludeScope
475                 && surroundingScope != excludeScope)
476             && nodeScope.isIn(scope)
477             && surroundingScope.isIn(scope);
478     }
479 
480     /**
481      * Checks the Javadoc for a method.
482      *
483      * @param ast the token for the method
484      * @param comment the Javadoc comment
485      */
486     private void checkComment(DetailAST ast, TextBlock comment) {
487         final List<JavadocTag> tags = getMethodTags(comment);
488 
489         if (!hasShortCircuitTag(ast, tags)) {
490             if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
491                 checkReturnTag(tags, ast.getLineNo(), true);
492             }
493             else {
494                 final Iterator<JavadocTag> it = tags.iterator();
495                 // Check for inheritDoc
496                 boolean hasInheritDocTag = false;
497                 while (!hasInheritDocTag && it.hasNext()) {
498                     hasInheritDocTag = it.next().isInheritDocTag();
499                 }
500                 final boolean reportExpectedTags = !hasInheritDocTag && !hasAllowedAnnotations(ast);
501 
502                 checkParamTags(tags, ast, reportExpectedTags);
503                 checkThrowsTags(tags, getThrows(ast), reportExpectedTags);
504                 if (CheckUtils.isNonVoidMethod(ast)) {
505                     checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
506                 }
507             }
508 
509             // Dump out all unused tags
510             tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
511                 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
512         }
513     }
514 
515     /**
516      * Validates whether the Javadoc has a short circuit tag. Currently this is
517      * the inheritTag. Any errors are logged.
518      *
519      * @param ast the construct being checked
520      * @param tags the list of Javadoc tags associated with the construct
521      * @return true if the construct has a short circuit tag.
522      */
523     private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
524         boolean result = true;
525         // Check if it contains {@inheritDoc} tag
526         if (tags.size() == 1
527                 && tags.get(0).isInheritDocTag()) {
528             // Invalid if private, a constructor, or a static method
529             if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
530                 log(ast, MSG_INVALID_INHERIT_DOC);
531             }
532         }
533         else {
534             result = false;
535         }
536         return result;
537     }
538 
539     /**
540      * Returns the scope for the method/constructor at the specified AST. If
541      * the method is in an interface or annotation block, the scope is assumed
542      * to be public.
543      *
544      * @param ast the token of the method/constructor
545      * @return the scope of the method/constructor
546      */
547     private static Scope calculateScope(final DetailAST ast) {
548         final Scope scope;
549 
550         if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
551             scope = Scope.PUBLIC;
552         }
553         else {
554             final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
555             scope = ScopeUtils.getScopeFromMods(mods);
556         }
557         return scope;
558     }
559 
560     /**
561      * Returns the tags in a javadoc comment. Only finds throws, exception,
562      * param, return and see tags.
563      *
564      * @param comment the Javadoc comment
565      * @return the tags found
566      */
567     private static List<JavadocTag> getMethodTags(TextBlock comment) {
568         final String[] lines = comment.getText();
569         final List<JavadocTag> tags = new ArrayList<>();
570         int currentLine = comment.getStartLineNo() - 1;
571         final int startColumnNumber = comment.getStartColNo();
572 
573         for (int i = 0; i < lines.length; i++) {
574             currentLine++;
575             final Matcher javadocArgMatcher =
576                 MATCH_JAVADOC_ARG.matcher(lines[i]);
577             final Matcher javadocNoargMatcher =
578                 MATCH_JAVADOC_NOARG.matcher(lines[i]);
579             final Matcher noargCurlyMatcher =
580                 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
581             final Matcher argMultilineStart =
582                 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]);
583             final Matcher noargMultilineStart =
584                 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
585 
586             if (javadocArgMatcher.find()) {
587                 final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
588                 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
589                         javadocArgMatcher.group(2)));
590             }
591             else if (javadocNoargMatcher.find()) {
592                 final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
593                 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
594             }
595             else if (noargCurlyMatcher.find()) {
596                 final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
597                 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
598             }
599             else if (argMultilineStart.find()) {
600                 final int col = calculateTagColumn(argMultilineStart, i, startColumnNumber);
601                 tags.addAll(getMultilineArgTags(argMultilineStart, col, lines, i, currentLine));
602             }
603             else if (noargMultilineStart.find()) {
604                 tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
605             }
606         }
607         return tags;
608     }
609 
610     /**
611      * Calculates column number using Javadoc tag matcher.
612      * @param javadocTagMatcher found javadoc tag matcher
613      * @param lineNumber line number of Javadoc tag in comment
614      * @param startColumnNumber column number of Javadoc comment beginning
615      * @return column number
616      */
617     private static int calculateTagColumn(Matcher javadocTagMatcher,
618             int lineNumber, int startColumnNumber) {
619         int col = javadocTagMatcher.start(1) - 1;
620         if (lineNumber == 0) {
621             col += startColumnNumber;
622         }
623         return col;
624     }
625 
626     /**
627      * Gets multiline Javadoc tags with arguments.
628      * @param argMultilineStart javadoc tag Matcher
629      * @param column column number of Javadoc tag
630      * @param lines comment text lines
631      * @param lineIndex line number that contains the javadoc tag
632      * @param tagLine javadoc tag line number in file
633      * @return javadoc tags with arguments
634      */
635     private static List<JavadocTag> getMultilineArgTags(final Matcher argMultilineStart,
636             final int column, final String[] lines, final int lineIndex, final int tagLine) {
637         final List<JavadocTag> tags = new ArrayList<>();
638         final String param1 = argMultilineStart.group(1);
639         final String param2 = argMultilineStart.group(2);
640         int remIndex = lineIndex + 1;
641         while (remIndex < lines.length) {
642             final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
643             if (multilineCont.find()) {
644                 remIndex = lines.length;
645                 final String lFin = multilineCont.group(1);
646                 if (!lFin.equals(NEXT_TAG)
647                     && !lFin.equals(END_JAVADOC)) {
648                     tags.add(new JavadocTag(tagLine, column, param1, param2));
649                 }
650             }
651             remIndex++;
652         }
653         return tags;
654     }
655 
656     /**
657      * Gets multiline Javadoc tags with no arguments.
658      * @param noargMultilineStart javadoc tag Matcher
659      * @param lines comment text lines
660      * @param lineIndex line number that contains the javadoc tag
661      * @param tagLine javadoc tag line number in file
662      * @return javadoc tags with no arguments
663      */
664     private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
665             final String[] lines, final int lineIndex, final int tagLine) {
666         final String param1 = noargMultilineStart.group(1);
667         final int col = noargMultilineStart.start(1) - 1;
668         final List<JavadocTag> tags = new ArrayList<>();
669         int remIndex = lineIndex + 1;
670         while (remIndex < lines.length) {
671             final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT
672                     .matcher(lines[remIndex]);
673             if (multilineCont.find()) {
674                 remIndex = lines.length;
675                 final String lFin = multilineCont.group(1);
676                 if (!lFin.equals(NEXT_TAG)
677                     && !lFin.equals(END_JAVADOC)) {
678                     tags.add(new JavadocTag(tagLine, col, param1));
679                 }
680             }
681             remIndex++;
682         }
683 
684         return tags;
685     }
686 
687     /**
688      * Computes the parameter nodes for a method.
689      *
690      * @param ast the method node.
691      * @return the list of parameter nodes for ast.
692      */
693     private static List<DetailAST> getParameters(DetailAST ast) {
694         final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
695         final List<DetailAST> returnValue = new ArrayList<>();
696 
697         DetailAST child = params.getFirstChild();
698         while (child != null) {
699             if (child.getType() == TokenTypes.PARAMETER_DEF) {
700                 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
701                 if (ident != null) {
702                     returnValue.add(ident);
703                 }
704             }
705             child = child.getNextSibling();
706         }
707         return returnValue;
708     }
709 
710     /**
711      * Computes the exception nodes for a method.
712      *
713      * @param ast the method node.
714      * @return the list of exception nodes for ast.
715      */
716     private List<ExceptionInfo> getThrows(DetailAST ast) {
717         final List<ExceptionInfo> returnValue = new ArrayList<>();
718         final DetailAST throwsAST = ast
719                 .findFirstToken(TokenTypes.LITERAL_THROWS);
720         if (throwsAST != null) {
721             DetailAST child = throwsAST.getFirstChild();
722             while (child != null) {
723                 if (child.getType() == TokenTypes.IDENT
724                         || child.getType() == TokenTypes.DOT) {
725                     final FullIdent ident = FullIdent.createFullIdent(child);
726                     final ExceptionInfo exceptionInfo = new ExceptionInfo(
727                             createClassInfo(new Token(ident), getCurrentClassName()));
728                     returnValue.add(exceptionInfo);
729                 }
730                 child = child.getNextSibling();
731             }
732         }
733         return returnValue;
734     }
735 
736     /**
737      * Checks a set of tags for matching parameters.
738      *
739      * @param tags the tags to check
740      * @param parent the node which takes the parameters
741      * @param reportExpectedTags whether we should report if do not find
742      *            expected tag
743      */
744     private void checkParamTags(final List<JavadocTag> tags,
745             final DetailAST parent, boolean reportExpectedTags) {
746         final List<DetailAST> params = getParameters(parent);
747         final List<DetailAST> typeParams = CheckUtils
748                 .getTypeParameters(parent);
749 
750         // Loop over the tags, checking to see they exist in the params.
751         final ListIterator<JavadocTag> tagIt = tags.listIterator();
752         while (tagIt.hasNext()) {
753             final JavadocTag tag = tagIt.next();
754 
755             if (!tag.isParamTag()) {
756                 continue;
757             }
758 
759             tagIt.remove();
760 
761             final String arg1 = tag.getFirstArg();
762             boolean found = removeMatchingParam(params, arg1);
763 
764             if (CommonUtils.startsWithChar(arg1, '<') && CommonUtils.endsWithChar(arg1, '>')) {
765                 found = searchMatchingTypeParameter(typeParams,
766                         arg1.substring(1, arg1.length() - 1));
767 
768             }
769 
770             // Handle extra JavadocTag
771             if (!found) {
772                 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
773                         "@param", arg1);
774             }
775         }
776 
777         // Now dump out all type parameters/parameters without tags :- unless
778         // the user has chosen to suppress these problems
779         if (!allowMissingParamTags && reportExpectedTags) {
780             for (DetailAST param : params) {
781                 log(param, MSG_EXPECTED_TAG,
782                     JavadocTagInfo.PARAM.getText(), param.getText());
783             }
784 
785             for (DetailAST typeParam : typeParams) {
786                 log(typeParam, MSG_EXPECTED_TAG,
787                     JavadocTagInfo.PARAM.getText(),
788                     "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
789                     + ">");
790             }
791         }
792     }
793 
794     /**
795      * Returns true if required type found in type parameters.
796      * @param typeParams
797      *            list of type parameters
798      * @param requiredTypeName
799      *            name of required type
800      * @return true if required type found in type parameters.
801      */
802     private static boolean searchMatchingTypeParameter(List<DetailAST> typeParams,
803             String requiredTypeName) {
804         // Loop looking for matching type param
805         final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
806         boolean found = false;
807         while (typeParamsIt.hasNext()) {
808             final DetailAST typeParam = typeParamsIt.next();
809             if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
810                     .equals(requiredTypeName)) {
811                 found = true;
812                 typeParamsIt.remove();
813                 break;
814             }
815         }
816         return found;
817     }
818 
819     /**
820      * Remove parameter from params collection by name.
821      * @param params collection of DetailAST parameters
822      * @param paramName name of parameter
823      * @return true if parameter found and removed
824      */
825     private static boolean removeMatchingParam(List<DetailAST> params, String paramName) {
826         boolean found = false;
827         final Iterator<DetailAST> paramIt = params.iterator();
828         while (paramIt.hasNext()) {
829             final DetailAST param = paramIt.next();
830             if (param.getText().equals(paramName)) {
831                 found = true;
832                 paramIt.remove();
833                 break;
834             }
835         }
836         return found;
837     }
838 
839     /**
840      * Checks for only one return tag. All return tags will be removed from the
841      * supplied list.
842      *
843      * @param tags the tags to check
844      * @param lineNo the line number of the expected tag
845      * @param reportExpectedTags whether we should report if do not find
846      *            expected tag
847      */
848     private void checkReturnTag(List<JavadocTag> tags, int lineNo,
849         boolean reportExpectedTags) {
850         // Loop over tags finding return tags. After the first one, report an
851         // error.
852         boolean found = false;
853         final ListIterator<JavadocTag> it = tags.listIterator();
854         while (it.hasNext()) {
855             final JavadocTag javadocTag = it.next();
856             if (javadocTag.isReturnTag()) {
857                 if (found) {
858                     log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
859                             MSG_DUPLICATE_TAG,
860                             JavadocTagInfo.RETURN.getText());
861                 }
862                 found = true;
863                 it.remove();
864             }
865         }
866 
867         // Handle there being no @return tags :- unless
868         // the user has chosen to suppress these problems
869         if (!found && !allowMissingReturnTag && reportExpectedTags) {
870             log(lineNo, MSG_RETURN_EXPECTED);
871         }
872     }
873 
874     /**
875      * Checks a set of tags for matching throws.
876      *
877      * @param tags the tags to check
878      * @param throwsList the throws to check
879      * @param reportExpectedTags whether we should report if do not find
880      *            expected tag
881      */
882     private void checkThrowsTags(List<JavadocTag> tags,
883             List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
884         // Loop over the tags, checking to see they exist in the throws.
885         // The foundThrows used for performance only
886         final Set<String> foundThrows = new HashSet<>();
887         final ListIterator<JavadocTag> tagIt = tags.listIterator();
888         while (tagIt.hasNext()) {
889             final JavadocTag tag = tagIt.next();
890 
891             if (!tag.isThrowsTag()) {
892                 continue;
893             }
894             tagIt.remove();
895 
896             // Loop looking for matching throw
897             final String documentedEx = tag.getFirstArg();
898             final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
899                     .getColumnNo());
900             final AbstractClassInfo documentedClassInfo = createClassInfo(token,
901                     getCurrentClassName());
902             final boolean found = foundThrows.contains(documentedEx)
903                     || isInThrows(throwsList, documentedClassInfo, foundThrows);
904 
905             // Handle extra JavadocTag.
906             if (!found) {
907                 boolean reqd = true;
908                 if (allowUndeclaredRTE) {
909                     reqd = !isUnchecked(documentedClassInfo.getClazz());
910                 }
911 
912                 if (reqd && validateThrows) {
913                     log(tag.getLineNo(), tag.getColumnNo(),
914                         MSG_UNUSED_TAG,
915                         JavadocTagInfo.THROWS.getText(), tag.getFirstArg());
916 
917                 }
918             }
919         }
920         // Now dump out all throws without tags :- unless
921         // the user has chosen to suppress these problems
922         if (!allowMissingThrowsTags && reportExpectedTags) {
923             throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
924                 .forEach(exceptionInfo -> {
925                     final Token token = exceptionInfo.getName();
926                     log(token.getLineNo(), token.getColumnNo(),
927                         MSG_EXPECTED_TAG,
928                         JavadocTagInfo.THROWS.getText(), token.getText());
929                 });
930         }
931     }
932 
933     /**
934      * Verifies that documented exception is in throws.
935      *
936      * @param throwsList list of throws
937      * @param documentedClassInfo documented exception class info
938      * @param foundThrows previously found throws
939      * @return true if documented exception is in throws.
940      */
941     private boolean isInThrows(List<ExceptionInfo> throwsList,
942             AbstractClassInfo documentedClassInfo, Set<String> foundThrows) {
943         boolean found = false;
944         ExceptionInfo foundException = null;
945 
946         // First look for matches on the exception name
947         for (ExceptionInfo exceptionInfo : throwsList) {
948             if (exceptionInfo.getName().getText().equals(
949                     documentedClassInfo.getName().getText())) {
950                 found = true;
951                 foundException = exceptionInfo;
952                 break;
953             }
954         }
955 
956         // Now match on the exception type
957         final ListIterator<ExceptionInfo> exceptionInfoIt = throwsList.listIterator();
958         while (!found && exceptionInfoIt.hasNext()) {
959             final ExceptionInfo exceptionInfo = exceptionInfoIt.next();
960 
961             if (documentedClassInfo.getClazz() == exceptionInfo.getClazz()) {
962                 found = true;
963                 foundException = exceptionInfo;
964             }
965             else if (allowThrowsTagsForSubclasses) {
966                 found = isSubclass(documentedClassInfo.getClazz(), exceptionInfo.getClazz());
967             }
968         }
969 
970         if (foundException != null) {
971             foundException.setFound();
972             foundThrows.add(documentedClassInfo.getName().getText());
973         }
974 
975         return found;
976     }
977 
978     /** Stores useful information about declared exception. */
979     private static class ExceptionInfo {
980         /** Class information associated with this exception. */
981         private final AbstractClassInfo classInfo;
982         /** Does the exception have throws tag associated with. */
983         private boolean found;
984 
985         /**
986          * Creates new instance for {@code FullIdent}.
987          *
988          * @param classInfo class info
989          */
990         ExceptionInfo(AbstractClassInfo classInfo) {
991             this.classInfo = classInfo;
992         }
993 
994         /** Mark that the exception has associated throws tag. */
995         private void setFound() {
996             found = true;
997         }
998 
999         /**
1000          * Checks that the exception has throws tag associated with it.
1001          * @return whether the exception has throws tag associated with
1002          */
1003         private boolean isFound() {
1004             return found;
1005         }
1006 
1007         /**
1008          * Gets exception name.
1009          * @return exception's name
1010          */
1011         private Token getName() {
1012             return classInfo.getName();
1013         }
1014 
1015         /**
1016          * Gets exception class.
1017          * @return class for this exception
1018          */
1019         private Class<?> getClazz() {
1020             return classInfo.getClazz();
1021         }
1022     }
1023 }