View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.Collection;
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.MatchResult;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import com.puppycrawl.tools.checkstyle.StatelessCheck;
35  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
36  import com.puppycrawl.tools.checkstyle.api.DetailAST;
37  import com.puppycrawl.tools.checkstyle.api.FileContents;
38  import com.puppycrawl.tools.checkstyle.api.FullIdent;
39  import com.puppycrawl.tools.checkstyle.api.TextBlock;
40  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
41  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
42  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
43  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
44  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
45  import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
46  
47  /**
48   * <p>
49   * Checks the Javadoc of a method or constructor.
50   * </p>
51   * <p>
52   * Violates parameters and type parameters for which no param tags are present can
53   * be suppressed by defining property {@code allowMissingParamTags}.
54   * </p>
55   * <p>
56   * Violates methods which return non-void but for which no return tag is present can
57   * be suppressed by defining property {@code allowMissingReturnTag}.
58   * </p>
59   * <p>
60   * Violates exceptions which are declared to be thrown (by {@code throws} in the method
61   * signature or by {@code throw new} in the method body), but for which no throws tag is
62   * present by activation of property {@code validateThrows}.
63   * Note that {@code throw new} is not checked in the following places:
64   * </p>
65   * <ul>
66   * <li>
67   * Inside a try block (with catch). It is not possible to determine if the thrown
68   * exception can be caught by the catch block as there is no knowledge of the
69   * inheritance hierarchy, so the try block is ignored entirely. However, catch
70   * and finally blocks, as well as try blocks without catch, are still checked.
71   * </li>
72   * <li>
73   * Local classes, anonymous classes and lambda expressions. It is not known when the
74   * throw statements inside such classes are going to be evaluated, so they are ignored.
75   * </li>
76   * </ul>
77   * <p>
78   * ATTENTION: Checkstyle does not have information about hierarchy of exception types
79   * so usage of base class is considered as separate exception type.
80   * As workaround, you need to specify both types in javadoc (parent and exact type).
81   * </p>
82   * <p>
83   * Javadoc is not required on a method that is tagged with the {@code @Override}
84   * annotation. However, under Java 5 it is not possible to mark a method required
85   * for an interface (this was <i>corrected</i> under Java 6). Hence, Checkstyle
86   * supports using the convention of using a single {@code {@inheritDoc}} tag
87   * instead of all the other tags.
88   * </p>
89   * <p>
90   * Note that only inheritable items will allow the {@code {@inheritDoc}}
91   * tag to be used in place of comments. Static methods at all visibilities,
92   * private non-static methods and constructors are not inheritable.
93   * </p>
94   * <p>
95   * For example, if the following method is implementing a method required by
96   * an interface, then the Javadoc could be done as:
97   * </p>
98   * <pre>
99   * &#47;** {&#64;inheritDoc} *&#47;
100  * public int checkReturnTag(final int aTagIndex,
101  *                           JavadocTag[] aTags,
102  *                           int aLineNo)
103  * </pre>
104  * <ul>
105  * <li>
106  * Property {@code accessModifiers} - Specify the access modifiers where Javadoc comments are
107  * checked.
108  * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}.
109  * Default value is {@code public, protected, package, private}.
110  * </li>
111  * <li>
112  * Property {@code allowMissingParamTags} - Control whether to ignore violations
113  * when a method has parameters but does not have matching {@code param} tags in the javadoc.
114  * Type is {@code boolean}.
115  * Default value is {@code false}.
116  * </li>
117  * <li>
118  * Property {@code allowMissingReturnTag} - Control whether to ignore violations
119  * when a method returns non-void type and does not have a {@code return} tag in the javadoc.
120  * Type is {@code boolean}.
121  * Default value is {@code false}.
122  * </li>
123  * <li>
124  * Property {@code allowedAnnotations} - Specify annotations that allow missed documentation.
125  * Type is {@code java.lang.String[]}.
126  * Default value is {@code Override}.
127  * </li>
128  * <li>
129  * Property {@code validateThrows} - Control whether to validate {@code throws} tags.
130  * Type is {@code boolean}.
131  * Default value is {@code false}.
132  * </li>
133  * <li>
134  * Property {@code tokens} - tokens to check
135  * Type is {@code java.lang.String[]}.
136  * Validation type is {@code tokenSet}.
137  * Default value is:
138  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
139  * METHOD_DEF</a>,
140  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
141  * CTOR_DEF</a>,
142  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
143  * ANNOTATION_FIELD_DEF</a>,
144  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
145  * COMPACT_CTOR_DEF</a>.
146  * </li>
147  * </ul>
148  * <p>
149  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
150  * </p>
151  * <p>
152  * Violation Message Keys:
153  * </p>
154  * <ul>
155  * <li>
156  * {@code javadoc.classInfo}
157  * </li>
158  * <li>
159  * {@code javadoc.duplicateTag}
160  * </li>
161  * <li>
162  * {@code javadoc.expectedTag}
163  * </li>
164  * <li>
165  * {@code javadoc.invalidInheritDoc}
166  * </li>
167  * <li>
168  * {@code javadoc.return.expected}
169  * </li>
170  * <li>
171  * {@code javadoc.unusedTag}
172  * </li>
173  * <li>
174  * {@code javadoc.unusedTagGeneral}
175  * </li>
176  * </ul>
177  *
178  * @since 3.0
179  */
180 @StatelessCheck
181 public class JavadocMethodCheck extends AbstractCheck {
182 
183     /**
184      * A key is pointing to the warning message text in "messages.properties"
185      * file.
186      */
187     public static final String MSG_CLASS_INFO = "javadoc.classInfo";
188 
189     /**
190      * A key is pointing to the warning message text in "messages.properties"
191      * file.
192      */
193     public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
194 
195     /**
196      * A key is pointing to the warning message text in "messages.properties"
197      * file.
198      */
199     public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
200 
201     /**
202      * A key is pointing to the warning message text in "messages.properties"
203      * file.
204      */
205     public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
206 
207     /**
208      * A key is pointing to the warning message text in "messages.properties"
209      * file.
210      */
211     public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
212 
213     /**
214      * A key is pointing to the warning message text in "messages.properties"
215      * file.
216      */
217     public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
218 
219     /**
220      * A key is pointing to the warning message text in "messages.properties"
221      * file.
222      */
223     public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
224 
225     /** Html element start symbol. */
226     private static final String ELEMENT_START = "<";
227 
228     /** Html element end symbol. */
229     private static final String ELEMENT_END = ">";
230 
231     /** Compiled regexp to match Javadoc tags that take an argument. */
232     private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern(
233             "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
234     /** Compiled regexp to match Javadoc tags with argument but with missing description. */
235     private static final Pattern MATCH_JAVADOC_ARG_MISSING_DESCRIPTION =
236         CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+"
237             + "(\\S[^*]*)(?:(\\s+|\\*\\/))?");
238 
239     /** Compiled regexp to look for a continuation of the comment. */
240     private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
241             CommonUtil.createPattern("(\\*\\/|@|[^\\s\\*])");
242 
243     /** Multiline finished at end of comment. */
244     private static final String END_JAVADOC = "*/";
245     /** Multiline finished at next Javadoc. */
246     private static final String NEXT_TAG = "@";
247 
248     /** Compiled regexp to match Javadoc tags with no argument. */
249     private static final Pattern MATCH_JAVADOC_NOARG =
250             CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S");
251     /** Compiled regexp to match first part of multilineJavadoc tags. */
252     private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
253             CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$");
254     /** Compiled regexp to match Javadoc tags with no argument and {}. */
255     private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
256             CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
257 
258     /** Specify the access modifiers where Javadoc comments are checked. */
259     private AccessModifierOption[] accessModifiers = {
260         AccessModifierOption.PUBLIC,
261         AccessModifierOption.PROTECTED,
262         AccessModifierOption.PACKAGE,
263         AccessModifierOption.PRIVATE,
264     };
265 
266     /**
267      * Control whether to validate {@code throws} tags.
268      */
269     private boolean validateThrows;
270 
271     /**
272      * Control whether to ignore violations when a method has parameters but does
273      * not have matching {@code param} tags in the javadoc.
274      */
275     private boolean allowMissingParamTags;
276 
277     /**
278      * Control whether to ignore violations when a method returns non-void type
279      * and does not have a {@code return} tag in the javadoc.
280      */
281     private boolean allowMissingReturnTag;
282 
283     /** Specify annotations that allow missed documentation. */
284     private Set<String> allowedAnnotations = Set.of("Override");
285 
286     /**
287      * Setter to control whether to validate {@code throws} tags.
288      *
289      * @param value user's value.
290      * @since 6.0
291      */
292     public void setValidateThrows(boolean value) {
293         validateThrows = value;
294     }
295 
296     /**
297      * Setter to specify annotations that allow missed documentation.
298      *
299      * @param userAnnotations user's value.
300      * @since 6.0
301      */
302     public void setAllowedAnnotations(String... userAnnotations) {
303         allowedAnnotations = Set.of(userAnnotations);
304     }
305 
306     /**
307      * Setter to specify the access modifiers where Javadoc comments are checked.
308      *
309      * @param accessModifiers access modifiers.
310      * @since 8.42
311      */
312     public void setAccessModifiers(AccessModifierOption... accessModifiers) {
313         this.accessModifiers =
314             UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
315     }
316 
317     /**
318      * Setter to control whether to ignore violations when a method has parameters
319      * but does not have matching {@code param} tags in the javadoc.
320      *
321      * @param flag a {@code Boolean} value
322      * @since 3.1
323      */
324     public void setAllowMissingParamTags(boolean flag) {
325         allowMissingParamTags = flag;
326     }
327 
328     /**
329      * Setter to control whether to ignore violations when a method returns non-void type
330      * and does not have a {@code return} tag in the javadoc.
331      *
332      * @param flag a {@code Boolean} value
333      * @since 3.1
334      */
335     public void setAllowMissingReturnTag(boolean flag) {
336         allowMissingReturnTag = flag;
337     }
338 
339     @Override
340     public final int[] getRequiredTokens() {
341         return new int[] {
342             TokenTypes.CLASS_DEF,
343             TokenTypes.INTERFACE_DEF,
344             TokenTypes.ENUM_DEF,
345             TokenTypes.RECORD_DEF,
346         };
347     }
348 
349     @Override
350     public int[] getDefaultTokens() {
351         return getAcceptableTokens();
352     }
353 
354     @Override
355     public int[] getAcceptableTokens() {
356         return new int[] {
357             TokenTypes.CLASS_DEF,
358             TokenTypes.ENUM_DEF,
359             TokenTypes.INTERFACE_DEF,
360             TokenTypes.METHOD_DEF,
361             TokenTypes.CTOR_DEF,
362             TokenTypes.ANNOTATION_FIELD_DEF,
363             TokenTypes.RECORD_DEF,
364             TokenTypes.COMPACT_CTOR_DEF,
365         };
366     }
367 
368     @Override
369     public final void visitToken(DetailAST ast) {
370         if (ast.getType() == TokenTypes.METHOD_DEF
371                  || ast.getType() == TokenTypes.CTOR_DEF
372                  || ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF
373                  || ast.getType() == TokenTypes.COMPACT_CTOR_DEF) {
374             processAST(ast);
375         }
376     }
377 
378     /**
379      * Called to process an AST when visiting it.
380      *
381      * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or
382      *             IMPORT tokens.
383      */
384     // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
385     @SuppressWarnings("deprecation")
386     private void processAST(DetailAST ast) {
387         if (shouldCheck(ast)) {
388             final FileContents contents = getFileContents();
389             final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
390 
391             if (textBlock != null) {
392                 checkComment(ast, textBlock);
393             }
394         }
395     }
396 
397     /**
398      * Whether we should check this node.
399      *
400      * @param ast a given node.
401      * @return whether we should check a given node.
402      */
403     private boolean shouldCheck(final DetailAST ast) {
404         final AccessModifierOption surroundingAccessModifier = CheckUtil
405                 .getSurroundingAccessModifier(ast);
406         final AccessModifierOption accessModifier = CheckUtil
407                 .getAccessModifierFromModifiersToken(ast);
408         return surroundingAccessModifier != null
409                 && Arrays.stream(accessModifiers)
410                         .anyMatch(modifier -> modifier == surroundingAccessModifier)
411                 && Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier);
412     }
413 
414     /**
415      * Checks the Javadoc for a method.
416      *
417      * @param ast the token for the method
418      * @param comment the Javadoc comment
419      */
420     private void checkComment(DetailAST ast, TextBlock comment) {
421         final List<JavadocTag> tags = getMethodTags(comment);
422 
423         if (!hasShortCircuitTag(ast, tags)) {
424             if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
425                 checkReturnTag(tags, ast.getLineNo(), true);
426             }
427             else {
428                 final Iterator<JavadocTag> it = tags.iterator();
429                 // Check for inheritDoc
430                 boolean hasInheritDocTag = false;
431                 while (!hasInheritDocTag && it.hasNext()) {
432                     hasInheritDocTag = it.next().isInheritDocTag();
433                 }
434                 final boolean reportExpectedTags = !hasInheritDocTag
435                     && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
436 
437                 // COMPACT_CTOR_DEF has no parameters
438                 if (ast.getType() != TokenTypes.COMPACT_CTOR_DEF) {
439                     checkParamTags(tags, ast, reportExpectedTags);
440                 }
441                 final List<ExceptionInfo> throwed =
442                     combineExceptionInfo(getThrows(ast), getThrowed(ast));
443                 checkThrowsTags(tags, throwed, reportExpectedTags);
444                 if (CheckUtil.isNonVoidMethod(ast)) {
445                     checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
446                 }
447 
448             }
449 
450             // Dump out all unused tags
451             tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
452                 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
453         }
454     }
455 
456     /**
457      * Validates whether the Javadoc has a short circuit tag. Currently, this is
458      * the inheritTag. Any violations are logged.
459      *
460      * @param ast the construct being checked
461      * @param tags the list of Javadoc tags associated with the construct
462      * @return true if the construct has a short circuit tag.
463      */
464     private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
465         boolean result = true;
466         // Check if it contains {@inheritDoc} tag
467         if (tags.size() == 1
468                 && tags.get(0).isInheritDocTag()) {
469             // Invalid if private, a constructor, or a static method
470             if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
471                 log(ast, MSG_INVALID_INHERIT_DOC);
472             }
473         }
474         else {
475             result = false;
476         }
477         return result;
478     }
479 
480     /**
481      * Returns the tags in a javadoc comment. Only finds throws, exception,
482      * param, return and see tags.
483      *
484      * @param comment the Javadoc comment
485      * @return the tags found
486      */
487     private static List<JavadocTag> getMethodTags(TextBlock comment) {
488         final String[] lines = comment.getText();
489         final List<JavadocTag> tags = new ArrayList<>();
490         int currentLine = comment.getStartLineNo() - 1;
491         final int startColumnNumber = comment.getStartColNo();
492 
493         for (int i = 0; i < lines.length; i++) {
494             currentLine++;
495             final Matcher javadocArgMatcher =
496                 MATCH_JAVADOC_ARG.matcher(lines[i]);
497             final Matcher javadocArgMissingDescriptionMatcher =
498                 MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]);
499             final Matcher javadocNoargMatcher =
500                 MATCH_JAVADOC_NOARG.matcher(lines[i]);
501             final Matcher noargCurlyMatcher =
502                 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
503             final Matcher noargMultilineStart =
504                 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
505 
506             if (javadocArgMatcher.find()) {
507                 final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
508                 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
509                         javadocArgMatcher.group(2)));
510             }
511             else if (javadocArgMissingDescriptionMatcher.find()) {
512                 final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i,
513                     startColumnNumber);
514                 tags.add(new JavadocTag(currentLine, col,
515                     javadocArgMissingDescriptionMatcher.group(1),
516                     javadocArgMissingDescriptionMatcher.group(2)));
517             }
518             else if (javadocNoargMatcher.find()) {
519                 final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
520                 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
521             }
522             else if (noargCurlyMatcher.find()) {
523                 tags.add(new JavadocTag(currentLine, 0, noargCurlyMatcher.group(1)));
524             }
525             else if (noargMultilineStart.find()) {
526                 tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
527             }
528         }
529         return tags;
530     }
531 
532     /**
533      * Calculates column number using Javadoc tag matcher.
534      *
535      * @param javadocTagMatchResult found javadoc tag match result
536      * @param lineNumber line number of Javadoc tag in comment
537      * @param startColumnNumber column number of Javadoc comment beginning
538      * @return column number
539      */
540     private static int calculateTagColumn(MatchResult javadocTagMatchResult,
541             int lineNumber, int startColumnNumber) {
542         int col = javadocTagMatchResult.start(1) - 1;
543         if (lineNumber == 0) {
544             col += startColumnNumber;
545         }
546         return col;
547     }
548 
549     /**
550      * Gets multiline Javadoc tags with no arguments.
551      *
552      * @param noargMultilineStart javadoc tag Matcher
553      * @param lines comment text lines
554      * @param lineIndex line number that contains the javadoc tag
555      * @param tagLine javadoc tag line number in file
556      * @return javadoc tags with no arguments
557      */
558     private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
559             final String[] lines, final int lineIndex, final int tagLine) {
560         int remIndex = lineIndex;
561         Matcher multilineCont;
562 
563         do {
564             remIndex++;
565             multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
566         } while (!multilineCont.find());
567 
568         final List<JavadocTag> tags = new ArrayList<>();
569         final String lFin = multilineCont.group(1);
570         if (!NEXT_TAG.equals(lFin)
571             && !END_JAVADOC.equals(lFin)) {
572             final String param1 = noargMultilineStart.group(1);
573             final int col = noargMultilineStart.start(1) - 1;
574 
575             tags.add(new JavadocTag(tagLine, col, param1));
576         }
577 
578         return tags;
579     }
580 
581     /**
582      * Computes the parameter nodes for a method.
583      *
584      * @param ast the method node.
585      * @return the list of parameter nodes for ast.
586      */
587     private static List<DetailAST> getParameters(DetailAST ast) {
588         final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
589         final List<DetailAST> returnValue = new ArrayList<>();
590 
591         DetailAST child = params.getFirstChild();
592         while (child != null) {
593             final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
594             if (ident != null) {
595                 returnValue.add(ident);
596             }
597             child = child.getNextSibling();
598         }
599         return returnValue;
600     }
601 
602     /**
603      * Computes the exception nodes for a method.
604      *
605      * @param ast the method node.
606      * @return the list of exception nodes for ast.
607      */
608     private static List<ExceptionInfo> getThrows(DetailAST ast) {
609         final List<ExceptionInfo> returnValue = new ArrayList<>();
610         final DetailAST throwsAST = ast
611                 .findFirstToken(TokenTypes.LITERAL_THROWS);
612         if (throwsAST != null) {
613             DetailAST child = throwsAST.getFirstChild();
614             while (child != null) {
615                 if (child.getType() == TokenTypes.IDENT
616                         || child.getType() == TokenTypes.DOT) {
617                     returnValue.add(getExceptionInfo(child));
618                 }
619                 child = child.getNextSibling();
620             }
621         }
622         return returnValue;
623     }
624 
625     /**
626      * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'.
627      *
628      * @param methodAst method DetailAST object where to find exceptions
629      * @return list of ExceptionInfo
630      */
631     private static List<ExceptionInfo> getThrowed(DetailAST methodAst) {
632         final List<ExceptionInfo> returnValue = new ArrayList<>();
633         final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST);
634         if (blockAst != null) {
635             final List<DetailAST> throwLiterals = findTokensInAstByType(blockAst,
636                     TokenTypes.LITERAL_THROW);
637             for (DetailAST throwAst : throwLiterals) {
638                 if (!isInIgnoreBlock(blockAst, throwAst)) {
639                     final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
640                     if (newAst.getType() == TokenTypes.LITERAL_NEW) {
641                         final DetailAST child = newAst.getFirstChild();
642                         returnValue.add(getExceptionInfo(child));
643                     }
644                 }
645             }
646         }
647         return returnValue;
648     }
649 
650     /**
651      * Get ExceptionInfo instance.
652      *
653      * @param ast DetailAST object where to find exceptions node;
654      * @return ExceptionInfo
655      */
656     private static ExceptionInfo getExceptionInfo(DetailAST ast) {
657         final FullIdent ident = FullIdent.createFullIdent(ast);
658         final DetailAST firstClassNameNode = getFirstClassNameNode(ast);
659         return new ExceptionInfo(firstClassNameNode,
660                 new ClassInfo(new Token(ident)));
661     }
662 
663     /**
664      * Get node where class name of exception starts.
665      *
666      * @param ast DetailAST object where to find exceptions node;
667      * @return exception node where class name starts
668      */
669     private static DetailAST getFirstClassNameNode(DetailAST ast) {
670         DetailAST startNode = ast;
671         while (startNode.getType() == TokenTypes.DOT) {
672             startNode = startNode.getFirstChild();
673         }
674         return startNode;
675     }
676 
677     /**
678      * Checks if a 'throw' usage is contained within a block that should be ignored.
679      * Such blocks consist of try (with catch) blocks, local classes, anonymous classes,
680      * and lambda expressions. Note that a try block without catch is not considered.
681      *
682      * @param methodBodyAst DetailAST node representing the method body
683      * @param throwAst DetailAST node representing the 'throw' literal
684      * @return true if throwAst is inside a block that should be ignored
685      */
686     private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) {
687         DetailAST ancestor = throwAst;
688         while (ancestor != methodBodyAst) {
689             if (ancestor.getType() == TokenTypes.LAMBDA
690                     || ancestor.getType() == TokenTypes.OBJBLOCK
691                     || ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null) {
692                 // throw is inside a lambda expression/anonymous class/local class,
693                 // or throw is inside a try block, and there is a catch block
694                 break;
695             }
696             if (ancestor.getType() == TokenTypes.LITERAL_CATCH
697                     || ancestor.getType() == TokenTypes.LITERAL_FINALLY) {
698                 // if the throw is inside a catch or finally block,
699                 // skip the immediate ancestor (try token)
700                 ancestor = ancestor.getParent();
701             }
702             ancestor = ancestor.getParent();
703         }
704         return ancestor != methodBodyAst;
705     }
706 
707     /**
708      * Combine ExceptionInfo collections together by matching names.
709      *
710      * @param first the first collection of ExceptionInfo
711      * @param second the second collection of ExceptionInfo
712      * @return combined list of ExceptionInfo
713      */
714     private static List<ExceptionInfo> combineExceptionInfo(Collection<ExceptionInfo> first,
715                                                             Iterable<ExceptionInfo> second) {
716         final List<ExceptionInfo> result = new ArrayList<>(first);
717         for (ExceptionInfo exceptionInfo : second) {
718             if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) {
719                 result.add(exceptionInfo);
720             }
721         }
722         return result;
723     }
724 
725     /**
726      * Finds node of specified type among root children, siblings, siblings children
727      * on any deep level.
728      *
729      * @param root    DetailAST
730      * @param astType value of TokenType
731      * @return {@link List} of {@link DetailAST} nodes which matches the predicate.
732      */
733     public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
734         final List<DetailAST> result = new ArrayList<>();
735         // iterative preorder depth-first search
736         DetailAST curNode = root;
737         do {
738             // process curNode
739             if (curNode.getType() == astType) {
740                 result.add(curNode);
741             }
742             // process children (if any)
743             if (curNode.hasChildren()) {
744                 curNode = curNode.getFirstChild();
745                 continue;
746             }
747             // backtrack to parent if last child, stopping at root
748             while (curNode != root && curNode.getNextSibling() == null) {
749                 curNode = curNode.getParent();
750             }
751             // explore siblings if not root
752             if (curNode != root) {
753                 curNode = curNode.getNextSibling();
754             }
755         } while (curNode != root);
756         return result;
757     }
758 
759     /**
760      * Checks a set of tags for matching parameters.
761      *
762      * @param tags the tags to check
763      * @param parent the node which takes the parameters
764      * @param reportExpectedTags whether we should report if do not find
765      *            expected tag
766      */
767     private void checkParamTags(final List<JavadocTag> tags,
768             final DetailAST parent, boolean reportExpectedTags) {
769         final List<DetailAST> params = getParameters(parent);
770         final List<DetailAST> typeParams = CheckUtil
771                 .getTypeParameters(parent);
772 
773         // Loop over the tags, checking to see they exist in the params.
774         final ListIterator<JavadocTag> tagIt = tags.listIterator();
775         while (tagIt.hasNext()) {
776             final JavadocTag tag = tagIt.next();
777 
778             if (!tag.isParamTag()) {
779                 continue;
780             }
781 
782             tagIt.remove();
783 
784             final String arg1 = tag.getFirstArg();
785             boolean found = removeMatchingParam(params, arg1);
786 
787             if (arg1.startsWith(ELEMENT_START) && arg1.endsWith(ELEMENT_END)) {
788                 found = searchMatchingTypeParameter(typeParams,
789                         arg1.substring(1, arg1.length() - 1));
790             }
791 
792             // Handle extra JavadocTag
793             if (!found) {
794                 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
795                         "@param", arg1);
796             }
797         }
798 
799         // Now dump out all type parameters/parameters without tags :- unless
800         // the user has chosen to suppress these problems
801         if (!allowMissingParamTags && reportExpectedTags) {
802             for (DetailAST param : params) {
803                 log(param, MSG_EXPECTED_TAG,
804                     JavadocTagInfo.PARAM.getText(), param.getText());
805             }
806 
807             for (DetailAST typeParam : typeParams) {
808                 log(typeParam, MSG_EXPECTED_TAG,
809                     JavadocTagInfo.PARAM.getText(),
810                     ELEMENT_START + typeParam.findFirstToken(TokenTypes.IDENT).getText()
811                     + ELEMENT_END);
812             }
813         }
814     }
815 
816     /**
817      * Returns true if required type found in type parameters.
818      *
819      * @param typeParams
820      *            collection of type parameters
821      * @param requiredTypeName
822      *            name of required type
823      * @return true if required type found in type parameters.
824      */
825     private static boolean searchMatchingTypeParameter(Iterable<DetailAST> typeParams,
826             String requiredTypeName) {
827         // Loop looking for matching type param
828         final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
829         boolean found = false;
830         while (typeParamsIt.hasNext()) {
831             final DetailAST typeParam = typeParamsIt.next();
832             if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
833                     .equals(requiredTypeName)) {
834                 found = true;
835                 typeParamsIt.remove();
836                 break;
837             }
838         }
839         return found;
840     }
841 
842     /**
843      * Remove parameter from params collection by name.
844      *
845      * @param params collection of DetailAST parameters
846      * @param paramName name of parameter
847      * @return true if parameter found and removed
848      */
849     private static boolean removeMatchingParam(Iterable<DetailAST> params, String paramName) {
850         boolean found = false;
851         final Iterator<DetailAST> paramIt = params.iterator();
852         while (paramIt.hasNext()) {
853             final DetailAST param = paramIt.next();
854             if (param.getText().equals(paramName)) {
855                 found = true;
856                 paramIt.remove();
857                 break;
858             }
859         }
860         return found;
861     }
862 
863     /**
864      * Checks for only one return tag. All return tags will be removed from the
865      * supplied list.
866      *
867      * @param tags the tags to check
868      * @param lineNo the line number of the expected tag
869      * @param reportExpectedTags whether we should report if do not find
870      *            expected tag
871      */
872     private void checkReturnTag(List<JavadocTag> tags, int lineNo,
873         boolean reportExpectedTags) {
874         // Loop over tags finding return tags. After the first one, report a
875         // violation.
876         boolean found = false;
877         final ListIterator<JavadocTag> it = tags.listIterator();
878         while (it.hasNext()) {
879             final JavadocTag javadocTag = it.next();
880             if (javadocTag.isReturnTag()) {
881                 if (found) {
882                     log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
883                             MSG_DUPLICATE_TAG,
884                             JavadocTagInfo.RETURN.getText());
885                 }
886                 found = true;
887                 it.remove();
888             }
889         }
890 
891         // Handle there being no @return tags :- unless
892         // the user has chosen to suppress these problems
893         if (!found && !allowMissingReturnTag && reportExpectedTags) {
894             log(lineNo, MSG_RETURN_EXPECTED);
895         }
896     }
897 
898     /**
899      * Checks a set of tags for matching throws.
900      *
901      * @param tags the tags to check
902      * @param throwsList the throws to check
903      * @param reportExpectedTags whether we should report if do not find
904      *            expected tag
905      */
906     private void checkThrowsTags(List<JavadocTag> tags,
907             List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
908         // Loop over the tags, checking to see they exist in the throws.
909         // The foundThrows used for performance only
910         final Set<String> foundThrows = new HashSet<>();
911         final ListIterator<JavadocTag> tagIt = tags.listIterator();
912         while (tagIt.hasNext()) {
913             final JavadocTag tag = tagIt.next();
914 
915             if (!tag.isThrowsTag()) {
916                 continue;
917             }
918             tagIt.remove();
919 
920             // Loop looking for matching throw
921             final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
922                     .getColumnNo());
923             final ClassInfo documentedClassInfo = new ClassInfo(token);
924             processThrows(throwsList, documentedClassInfo, foundThrows);
925         }
926         // Now dump out all throws without tags :- unless
927         // the user has chosen to suppress these problems
928         if (validateThrows && reportExpectedTags) {
929             throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
930                 .forEach(exceptionInfo -> {
931                     final Token token = exceptionInfo.getName();
932                     log(exceptionInfo.getAst(),
933                         MSG_EXPECTED_TAG,
934                         JavadocTagInfo.THROWS.getText(), token.getText());
935                 });
936         }
937     }
938 
939     /**
940      * Verifies that documented exception is in throws.
941      *
942      * @param throwsIterable collection of throws
943      * @param documentedClassInfo documented exception class info
944      * @param foundThrows previously found throws
945      */
946     private static void processThrows(Iterable<ExceptionInfo> throwsIterable,
947                                       ClassInfo documentedClassInfo, Set<String> foundThrows) {
948         ExceptionInfo foundException = null;
949 
950         // First look for matches on the exception name
951         for (ExceptionInfo exceptionInfo : throwsIterable) {
952             if (isClassNamesSame(exceptionInfo.getName().getText(),
953                     documentedClassInfo.getName().getText())) {
954                 foundException = exceptionInfo;
955                 break;
956             }
957         }
958 
959         if (foundException != null) {
960             foundException.setFound();
961             foundThrows.add(documentedClassInfo.getName().getText());
962         }
963     }
964 
965     /**
966      * Check that ExceptionInfo objects are same by name.
967      *
968      * @param info1 ExceptionInfo object
969      * @param info2 ExceptionInfo object
970      * @return true is ExceptionInfo object have the same name
971      */
972     private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
973         return isClassNamesSame(info1.getName().getText(),
974                                     info2.getName().getText());
975     }
976 
977     /**
978      * Check that class names are same by short name of class. If some class name is fully
979      * qualified it is cut to short name.
980      *
981      * @param class1 class name
982      * @param class2 class name
983      * @return true is ExceptionInfo object have the same name
984      */
985     private static boolean isClassNamesSame(String class1, String class2) {
986         boolean result = false;
987         if (class1.equals(class2)) {
988             result = true;
989         }
990         else {
991             final String separator = ".";
992             if (class1.contains(separator) || class2.contains(separator)) {
993                 final String class1ShortName = class1
994                         .substring(class1.lastIndexOf('.') + 1);
995                 final String class2ShortName = class2
996                         .substring(class2.lastIndexOf('.') + 1);
997                 result = class1ShortName.equals(class2ShortName);
998             }
999         }
1000         return result;
1001     }
1002 
1003     /**
1004      * Contains class's {@code Token}.
1005      */
1006     private static class ClassInfo {
1007 
1008         /** {@code FullIdent} associated with this class. */
1009         private final Token name;
1010 
1011         /**
1012          * Creates new instance of class information object.
1013          *
1014          * @param className token which represents class name.
1015          * @throws IllegalArgumentException when className is nulls
1016          */
1017         protected ClassInfo(final Token className) {
1018             name = className;
1019         }
1020 
1021         /**
1022          * Gets class name.
1023          *
1024          * @return class name
1025          */
1026         public final Token getName() {
1027             return name;
1028         }
1029 
1030     }
1031 
1032     /**
1033      * Represents text element with location in the text.
1034      */
1035     private static final class Token {
1036 
1037         /** Token's column number. */
1038         private final int columnNo;
1039         /** Token's line number. */
1040         private final int lineNo;
1041         /** Token's text. */
1042         private final String text;
1043 
1044         /**
1045          * Creates token.
1046          *
1047          * @param text token's text
1048          * @param lineNo token's line number
1049          * @param columnNo token's column number
1050          */
1051         private Token(String text, int lineNo, int columnNo) {
1052             this.text = text;
1053             this.lineNo = lineNo;
1054             this.columnNo = columnNo;
1055         }
1056 
1057         /**
1058          * Converts FullIdent to Token.
1059          *
1060          * @param fullIdent full ident to convert.
1061          */
1062         private Token(FullIdent fullIdent) {
1063             text = fullIdent.getText();
1064             lineNo = fullIdent.getLineNo();
1065             columnNo = fullIdent.getColumnNo();
1066         }
1067 
1068         /**
1069          * Gets text of the token.
1070          *
1071          * @return text of the token
1072          */
1073         public String getText() {
1074             return text;
1075         }
1076 
1077         @Override
1078         public String toString() {
1079             return "Token[" + text + "(" + lineNo
1080                 + "x" + columnNo + ")]";
1081         }
1082 
1083     }
1084 
1085     /** Stores useful information about declared exception. */
1086     private static final class ExceptionInfo {
1087 
1088         /** AST node representing this exception. */
1089         private final DetailAST ast;
1090 
1091         /** Class information associated with this exception. */
1092         private final ClassInfo classInfo;
1093         /** Does the exception have throws tag associated with. */
1094         private boolean found;
1095 
1096         /**
1097          * Creates new instance for {@code FullIdent}.
1098          *
1099          * @param ast AST node representing this exception
1100          * @param classInfo class info
1101          */
1102         private ExceptionInfo(DetailAST ast, ClassInfo classInfo) {
1103             this.ast = ast;
1104             this.classInfo = classInfo;
1105         }
1106 
1107         /**
1108          * Gets the AST node representing this exception.
1109          *
1110          * @return the AST node representing this exception
1111          */
1112         private DetailAST getAst() {
1113             return ast;
1114         }
1115 
1116         /** Mark that the exception has associated throws tag. */
1117         private void setFound() {
1118             found = true;
1119         }
1120 
1121         /**
1122          * Checks that the exception has throws tag associated with it.
1123          *
1124          * @return whether the exception has throws tag associated with
1125          */
1126         private boolean isFound() {
1127             return found;
1128         }
1129 
1130         /**
1131          * Gets exception name.
1132          *
1133          * @return exception's name
1134          */
1135         private Token getName() {
1136             return classInfo.getName();
1137         }
1138 
1139     }
1140 
1141 }