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