001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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                    checkParamTags(tags, ast, reportExpectedTags);
433                }
434                final List<ExceptionInfo> throwed =
435                    combineExceptionInfo(getThrows(ast), getThrowed(ast));
436                checkThrowsTags(tags, throwed, reportExpectedTags);
437                if (CheckUtil.isNonVoidMethod(ast)) {
438                    checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
439                }
440
441            }
442
443            // Dump out all unused tags
444            tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
445                .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
446        }
447    }
448
449    /**
450     * Validates whether the Javadoc has a short circuit tag. Currently, this is
451     * the inheritTag. Any violations are logged.
452     *
453     * @param ast the construct being checked
454     * @param tags the list of Javadoc tags associated with the construct
455     * @return true if the construct has a short circuit tag.
456     */
457    private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
458        boolean result = true;
459        // Check if it contains {@inheritDoc} tag
460        if (tags.size() == 1
461                && tags.get(0).isInheritDocTag()) {
462            // Invalid if private, a constructor, or a static method
463            if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
464                log(ast, MSG_INVALID_INHERIT_DOC);
465            }
466        }
467        else {
468            result = false;
469        }
470        return result;
471    }
472
473    /**
474     * Returns the tags in a javadoc comment. Only finds throws, exception,
475     * param, return and see tags.
476     *
477     * @param comment the Javadoc comment
478     * @return the tags found
479     */
480    private static List<JavadocTag> getMethodTags(TextBlock comment) {
481        final String[] lines = comment.getText();
482        final List<JavadocTag> tags = new ArrayList<>();
483        int currentLine = comment.getStartLineNo() - 1;
484        final int startColumnNumber = comment.getStartColNo();
485
486        for (int i = 0; i < lines.length; i++) {
487            currentLine++;
488            final Matcher javadocArgMatcher =
489                MATCH_JAVADOC_ARG.matcher(lines[i]);
490            final Matcher javadocArgMissingDescriptionMatcher =
491                MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]);
492            final Matcher javadocNoargMatcher =
493                MATCH_JAVADOC_NOARG.matcher(lines[i]);
494            final Matcher noargCurlyMatcher =
495                MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
496            final Matcher noargMultilineStart =
497                MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
498
499            if (javadocArgMatcher.find()) {
500                final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
501                tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
502                        javadocArgMatcher.group(2)));
503            }
504            else if (javadocArgMissingDescriptionMatcher.find()) {
505                final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i,
506                    startColumnNumber);
507                tags.add(new JavadocTag(currentLine, col,
508                    javadocArgMissingDescriptionMatcher.group(1),
509                    javadocArgMissingDescriptionMatcher.group(2)));
510            }
511            else if (javadocNoargMatcher.find()) {
512                final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
513                tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
514            }
515            else if (noargCurlyMatcher.find()) {
516                tags.add(new JavadocTag(currentLine, 0, noargCurlyMatcher.group(1)));
517            }
518            else if (noargMultilineStart.find()) {
519                tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
520            }
521        }
522        return tags;
523    }
524
525    /**
526     * Calculates column number using Javadoc tag matcher.
527     *
528     * @param javadocTagMatchResult found javadoc tag match result
529     * @param lineNumber line number of Javadoc tag in comment
530     * @param startColumnNumber column number of Javadoc comment beginning
531     * @return column number
532     */
533    private static int calculateTagColumn(MatchResult javadocTagMatchResult,
534            int lineNumber, int startColumnNumber) {
535        int col = javadocTagMatchResult.start(1) - 1;
536        if (lineNumber == 0) {
537            col += startColumnNumber;
538        }
539        return col;
540    }
541
542    /**
543     * Gets multiline Javadoc tags with no arguments.
544     *
545     * @param noargMultilineStart javadoc tag Matcher
546     * @param lines comment text lines
547     * @param lineIndex line number that contains the javadoc tag
548     * @param tagLine javadoc tag line number in file
549     * @return javadoc tags with no arguments
550     */
551    private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
552            final String[] lines, final int lineIndex, final int tagLine) {
553        int remIndex = lineIndex;
554        Matcher multilineCont;
555
556        do {
557            remIndex++;
558            multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
559        } while (!multilineCont.find());
560
561        final List<JavadocTag> tags = new ArrayList<>();
562        final String lFin = multilineCont.group(1);
563        if (!NEXT_TAG.equals(lFin)
564            && !END_JAVADOC.equals(lFin)) {
565            final String param1 = noargMultilineStart.group(1);
566            final int col = noargMultilineStart.start(1) - 1;
567
568            tags.add(new JavadocTag(tagLine, col, param1));
569        }
570
571        return tags;
572    }
573
574    /**
575     * Computes the parameter nodes for a method.
576     *
577     * @param ast the method node.
578     * @return the list of parameter nodes for ast.
579     */
580    private static List<DetailAST> getParameters(DetailAST ast) {
581        final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
582        final List<DetailAST> returnValue = new ArrayList<>();
583
584        DetailAST child = params.getFirstChild();
585        while (child != null) {
586            final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
587            if (ident != null) {
588                returnValue.add(ident);
589            }
590            child = child.getNextSibling();
591        }
592        return returnValue;
593    }
594
595    /**
596     * Computes the exception nodes for a method.
597     *
598     * @param ast the method node.
599     * @return the list of exception nodes for ast.
600     */
601    private static List<ExceptionInfo> getThrows(DetailAST ast) {
602        final List<ExceptionInfo> returnValue = new ArrayList<>();
603        final DetailAST throwsAST = ast
604                .findFirstToken(TokenTypes.LITERAL_THROWS);
605        if (throwsAST != null) {
606            DetailAST child = throwsAST.getFirstChild();
607            while (child != null) {
608                if (child.getType() == TokenTypes.IDENT
609                        || child.getType() == TokenTypes.DOT) {
610                    returnValue.add(getExceptionInfo(child));
611                }
612                child = child.getNextSibling();
613            }
614        }
615        return returnValue;
616    }
617
618    /**
619     * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'.
620     *
621     * @param methodAst method DetailAST object where to find exceptions
622     * @return list of ExceptionInfo
623     */
624    private static List<ExceptionInfo> getThrowed(DetailAST methodAst) {
625        final List<ExceptionInfo> returnValue = new ArrayList<>();
626        final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST);
627        if (blockAst != null) {
628            final List<DetailAST> throwLiterals = findTokensInAstByType(blockAst,
629                    TokenTypes.LITERAL_THROW);
630            for (DetailAST throwAst : throwLiterals) {
631                if (!isInIgnoreBlock(blockAst, throwAst)) {
632                    final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
633                    if (newAst.getType() == TokenTypes.LITERAL_NEW) {
634                        final DetailAST child = newAst.getFirstChild();
635                        returnValue.add(getExceptionInfo(child));
636                    }
637                }
638            }
639        }
640        return returnValue;
641    }
642
643    /**
644     * Get ExceptionInfo instance.
645     *
646     * @param ast DetailAST object where to find exceptions node;
647     * @return ExceptionInfo
648     */
649    private static ExceptionInfo getExceptionInfo(DetailAST ast) {
650        final FullIdent ident = FullIdent.createFullIdent(ast);
651        final DetailAST firstClassNameNode = getFirstClassNameNode(ast);
652        return new ExceptionInfo(firstClassNameNode,
653                new ClassInfo(new Token(ident)));
654    }
655
656    /**
657     * Get node where class name of exception starts.
658     *
659     * @param ast DetailAST object where to find exceptions node;
660     * @return exception node where class name starts
661     */
662    private static DetailAST getFirstClassNameNode(DetailAST ast) {
663        DetailAST startNode = ast;
664        while (startNode.getType() == TokenTypes.DOT) {
665            startNode = startNode.getFirstChild();
666        }
667        return startNode;
668    }
669
670    /**
671     * Checks if a 'throw' usage is contained within a block that should be ignored.
672     * Such blocks consist of try (with catch) blocks, local classes, anonymous classes,
673     * and lambda expressions. Note that a try block without catch is not considered.
674     *
675     * @param methodBodyAst DetailAST node representing the method body
676     * @param throwAst DetailAST node representing the 'throw' literal
677     * @return true if throwAst is inside a block that should be ignored
678     */
679    private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) {
680        DetailAST ancestor = throwAst;
681        while (ancestor != methodBodyAst) {
682            if (ancestor.getType() == TokenTypes.LAMBDA
683                    || ancestor.getType() == TokenTypes.OBJBLOCK
684                    || ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null) {
685                // throw is inside a lambda expression/anonymous class/local class,
686                // or throw is inside a try block, and there is a catch block
687                break;
688            }
689            if (ancestor.getType() == TokenTypes.LITERAL_CATCH
690                    || ancestor.getType() == TokenTypes.LITERAL_FINALLY) {
691                // if the throw is inside a catch or finally block,
692                // skip the immediate ancestor (try token)
693                ancestor = ancestor.getParent();
694            }
695            ancestor = ancestor.getParent();
696        }
697        return ancestor != methodBodyAst;
698    }
699
700    /**
701     * Combine ExceptionInfo collections together by matching names.
702     *
703     * @param first the first collection of ExceptionInfo
704     * @param second the second collection of ExceptionInfo
705     * @return combined list of ExceptionInfo
706     */
707    private static List<ExceptionInfo> combineExceptionInfo(Collection<ExceptionInfo> first,
708                                                            Iterable<ExceptionInfo> second) {
709        final List<ExceptionInfo> result = new ArrayList<>(first);
710        for (ExceptionInfo exceptionInfo : second) {
711            if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) {
712                result.add(exceptionInfo);
713            }
714        }
715        return result;
716    }
717
718    /**
719     * Finds node of specified type among root children, siblings, siblings children
720     * on any deep level.
721     *
722     * @param root    DetailAST
723     * @param astType value of TokenType
724     * @return {@link List} of {@link DetailAST} nodes which matches the predicate.
725     */
726    public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
727        final List<DetailAST> result = new ArrayList<>();
728        // iterative preorder depth-first search
729        DetailAST curNode = root;
730        do {
731            // process curNode
732            if (curNode.getType() == astType) {
733                result.add(curNode);
734            }
735            // process children (if any)
736            if (curNode.hasChildren()) {
737                curNode = curNode.getFirstChild();
738                continue;
739            }
740            // backtrack to parent if last child, stopping at root
741            while (curNode != root && curNode.getNextSibling() == null) {
742                curNode = curNode.getParent();
743            }
744            // explore siblings if not root
745            if (curNode != root) {
746                curNode = curNode.getNextSibling();
747            }
748        } while (curNode != root);
749        return result;
750    }
751
752    /**
753     * Checks a set of tags for matching parameters.
754     *
755     * @param tags the tags to check
756     * @param parent the node which takes the parameters
757     * @param reportExpectedTags whether we should report if do not find
758     *            expected tag
759     */
760    private void checkParamTags(final List<JavadocTag> tags,
761            final DetailAST parent, boolean reportExpectedTags) {
762        final List<DetailAST> params = getParameters(parent);
763        final List<DetailAST> typeParams = CheckUtil
764                .getTypeParameters(parent);
765
766        // Loop over the tags, checking to see they exist in the params.
767        final ListIterator<JavadocTag> tagIt = tags.listIterator();
768        while (tagIt.hasNext()) {
769            final JavadocTag tag = tagIt.next();
770
771            if (!tag.isParamTag()) {
772                continue;
773            }
774
775            tagIt.remove();
776
777            final String arg1 = tag.getFirstArg();
778            boolean found = removeMatchingParam(params, arg1);
779
780            if (arg1.startsWith(ELEMENT_START) && arg1.endsWith(ELEMENT_END)) {
781                found = searchMatchingTypeParameter(typeParams,
782                        arg1.substring(1, arg1.length() - 1));
783            }
784
785            // Handle extra JavadocTag
786            if (!found) {
787                log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
788                        "@param", arg1);
789            }
790        }
791
792        // Now dump out all type parameters/parameters without tags :- unless
793        // the user has chosen to suppress these problems
794        if (!allowMissingParamTags && reportExpectedTags) {
795            for (DetailAST param : params) {
796                log(param, MSG_EXPECTED_TAG,
797                    JavadocTagInfo.PARAM.getText(), param.getText());
798            }
799
800            for (DetailAST typeParam : typeParams) {
801                log(typeParam, MSG_EXPECTED_TAG,
802                    JavadocTagInfo.PARAM.getText(),
803                    ELEMENT_START + typeParam.findFirstToken(TokenTypes.IDENT).getText()
804                    + ELEMENT_END);
805            }
806        }
807    }
808
809    /**
810     * Returns true if required type found in type parameters.
811     *
812     * @param typeParams
813     *            collection of type parameters
814     * @param requiredTypeName
815     *            name of required type
816     * @return true if required type found in type parameters.
817     */
818    private static boolean searchMatchingTypeParameter(Iterable<DetailAST> typeParams,
819            String requiredTypeName) {
820        // Loop looking for matching type param
821        final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
822        boolean found = false;
823        while (typeParamsIt.hasNext()) {
824            final DetailAST typeParam = typeParamsIt.next();
825            if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
826                    .equals(requiredTypeName)) {
827                found = true;
828                typeParamsIt.remove();
829                break;
830            }
831        }
832        return found;
833    }
834
835    /**
836     * Remove parameter from params collection by name.
837     *
838     * @param params collection of DetailAST parameters
839     * @param paramName name of parameter
840     * @return true if parameter found and removed
841     */
842    private static boolean removeMatchingParam(Iterable<DetailAST> params, String paramName) {
843        boolean found = false;
844        final Iterator<DetailAST> paramIt = params.iterator();
845        while (paramIt.hasNext()) {
846            final DetailAST param = paramIt.next();
847            if (param.getText().equals(paramName)) {
848                found = true;
849                paramIt.remove();
850                break;
851            }
852        }
853        return found;
854    }
855
856    /**
857     * Checks for only one return tag. All return tags will be removed from the
858     * supplied list.
859     *
860     * @param tags the tags to check
861     * @param lineNo the line number of the expected tag
862     * @param reportExpectedTags whether we should report if do not find
863     *            expected tag
864     */
865    private void checkReturnTag(List<JavadocTag> tags, int lineNo,
866        boolean reportExpectedTags) {
867        // Loop over tags finding return tags. After the first one, report a
868        // violation.
869        boolean found = false;
870        final ListIterator<JavadocTag> it = tags.listIterator();
871        while (it.hasNext()) {
872            final JavadocTag javadocTag = it.next();
873            if (javadocTag.isReturnTag()) {
874                if (found) {
875                    log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
876                            MSG_DUPLICATE_TAG,
877                            JavadocTagInfo.RETURN.getText());
878                }
879                found = true;
880                it.remove();
881            }
882        }
883
884        // Handle there being no @return tags :- unless
885        // the user has chosen to suppress these problems
886        if (!found && !allowMissingReturnTag && reportExpectedTags) {
887            log(lineNo, MSG_RETURN_EXPECTED);
888        }
889    }
890
891    /**
892     * Checks a set of tags for matching throws.
893     *
894     * @param tags the tags to check
895     * @param throwsList the throws to check
896     * @param reportExpectedTags whether we should report if do not find
897     *            expected tag
898     */
899    private void checkThrowsTags(List<JavadocTag> tags,
900            List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
901        // Loop over the tags, checking to see they exist in the throws.
902        final ListIterator<JavadocTag> tagIt = tags.listIterator();
903        while (tagIt.hasNext()) {
904            final JavadocTag tag = tagIt.next();
905
906            if (!tag.isThrowsTag()) {
907                continue;
908            }
909            tagIt.remove();
910
911            // Loop looking for matching throw
912            processThrows(throwsList, tag.getFirstArg());
913        }
914        // Now dump out all throws without tags :- unless
915        // the user has chosen to suppress these problems
916        if (validateThrows && reportExpectedTags) {
917            throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
918                .forEach(exceptionInfo -> {
919                    final Token token = exceptionInfo.getName();
920                    log(exceptionInfo.getAst(),
921                        MSG_EXPECTED_TAG,
922                        JavadocTagInfo.THROWS.getText(), token.getText());
923                });
924        }
925    }
926
927    /**
928     * Verifies that documented exception is in throws.
929     *
930     * @param throwsIterable collection of throws
931     * @param documentedClassName documented exception class name
932     */
933    private static void processThrows(Iterable<ExceptionInfo> throwsIterable,
934                                      String documentedClassName) {
935        for (ExceptionInfo exceptionInfo : throwsIterable) {
936            if (isClassNamesSame(exceptionInfo.getName().getText(),
937                    documentedClassName)) {
938                exceptionInfo.setFound();
939                break;
940            }
941        }
942    }
943
944    /**
945     * Check that ExceptionInfo objects are same by name.
946     *
947     * @param info1 ExceptionInfo object
948     * @param info2 ExceptionInfo object
949     * @return true is ExceptionInfo object have the same name
950     */
951    private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
952        return isClassNamesSame(info1.getName().getText(),
953                                    info2.getName().getText());
954    }
955
956    /**
957     * Check that class names are same by short name of class. If some class name is fully
958     * qualified it is cut to short name.
959     *
960     * @param class1 class name
961     * @param class2 class name
962     * @return true is ExceptionInfo object have the same name
963     */
964    private static boolean isClassNamesSame(String class1, String class2) {
965        boolean result = false;
966        if (class1.equals(class2)) {
967            result = true;
968        }
969        else {
970            final String separator = ".";
971            if (class1.contains(separator) || class2.contains(separator)) {
972                final String class1ShortName = class1
973                        .substring(class1.lastIndexOf('.') + 1);
974                final String class2ShortName = class2
975                        .substring(class2.lastIndexOf('.') + 1);
976                result = class1ShortName.equals(class2ShortName);
977            }
978        }
979        return result;
980    }
981
982    /**
983     * Contains class's {@code Token}.
984     */
985    private static class ClassInfo {
986
987        /** {@code FullIdent} associated with this class. */
988        private final Token name;
989
990        /**
991         * Creates new instance of class information object.
992         *
993         * @param className token which represents class name.
994         * @throws IllegalArgumentException when className is nulls
995         */
996        protected ClassInfo(final Token className) {
997            name = className;
998        }
999
1000        /**
1001         * Gets class name.
1002         *
1003         * @return class name
1004         */
1005        public final Token getName() {
1006            return name;
1007        }
1008
1009    }
1010
1011    /**
1012     * Represents text element with location in the text.
1013     */
1014    private static final class Token {
1015
1016        /** Token's column number. */
1017        private final int columnNo;
1018        /** Token's line number. */
1019        private final int lineNo;
1020        /** Token's text. */
1021        private final String text;
1022
1023        /**
1024         * Creates token.
1025         *
1026         * @param text token's text
1027         * @param lineNo token's line number
1028         * @param columnNo token's column number
1029         */
1030        private Token(String text, int lineNo, int columnNo) {
1031            this.text = text;
1032            this.lineNo = lineNo;
1033            this.columnNo = columnNo;
1034        }
1035
1036        /**
1037         * Converts FullIdent to Token.
1038         *
1039         * @param fullIdent full ident to convert.
1040         */
1041        private Token(FullIdent fullIdent) {
1042            text = fullIdent.getText();
1043            lineNo = fullIdent.getLineNo();
1044            columnNo = fullIdent.getColumnNo();
1045        }
1046
1047        /**
1048         * Gets text of the token.
1049         *
1050         * @return text of the token
1051         */
1052        public String getText() {
1053            return text;
1054        }
1055
1056        @Override
1057        public String toString() {
1058            return "Token[" + text + "(" + lineNo
1059                + "x" + columnNo + ")]";
1060        }
1061
1062    }
1063
1064    /** Stores useful information about declared exception. */
1065    private static final class ExceptionInfo {
1066
1067        /** AST node representing this exception. */
1068        private final DetailAST ast;
1069
1070        /** Class information associated with this exception. */
1071        private final ClassInfo classInfo;
1072        /** Does the exception have throws tag associated with. */
1073        private boolean found;
1074
1075        /**
1076         * Creates new instance for {@code FullIdent}.
1077         *
1078         * @param ast AST node representing this exception
1079         * @param classInfo class info
1080         */
1081        private ExceptionInfo(DetailAST ast, ClassInfo classInfo) {
1082            this.ast = ast;
1083            this.classInfo = classInfo;
1084        }
1085
1086        /**
1087         * Gets the AST node representing this exception.
1088         *
1089         * @return the AST node representing this exception
1090         */
1091        private DetailAST getAst() {
1092            return ast;
1093        }
1094
1095        /** Mark that the exception has associated throws tag. */
1096        private void setFound() {
1097            found = true;
1098        }
1099
1100        /**
1101         * Checks that the exception has throws tag associated with it.
1102         *
1103         * @return whether the exception has throws tag associated with
1104         */
1105        private boolean isFound() {
1106            return found;
1107        }
1108
1109        /**
1110         * Gets exception name.
1111         *
1112         * @return exception's name
1113         */
1114        private Token getName() {
1115            return classInfo.getName();
1116        }
1117
1118    }
1119
1120}