001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.Collections;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.List;
028import java.util.ListIterator;
029import java.util.Set;
030import java.util.regex.Matcher;
031import java.util.regex.Pattern;
032
033import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
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.Scope;
039import com.puppycrawl.tools.checkstyle.api.TextBlock;
040import com.puppycrawl.tools.checkstyle.api.TokenTypes;
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.ScopeUtil;
045
046/**
047 * <p>
048 * Checks the Javadoc of a method or constructor.
049 * The scope to verify is specified using the {@code Scope} class and defaults
050 * to {@code Scope.PRIVATE}. To verify another scope, set property scope to
051 * a different <a href="https://checkstyle.org/property_types.html#Scope">scope</a>.
052 * </p>
053 * <p>
054 * Violates parameters and type parameters for which no param tags are present can
055 * be suppressed by defining property {@code allowMissingParamTags}.
056 * </p>
057 * <p>
058 * Violates methods which return non-void but for which no return tag is present can
059 * be suppressed by defining property {@code allowMissingReturnTag}.
060 * </p>
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 * <p>
080 * ATTENTION: Checkstyle does not have information about hierarchy of exception types
081 * so usage of base class is considered as separate exception type.
082 * As workaround you need to specify both types in javadoc (parent and exact type).
083 * </p>
084 * <p>
085 * Javadoc is not required on a method that is tagged with the {@code @Override}
086 * annotation. However under Java 5 it is not possible to mark a method required
087 * for an interface (this was <i>corrected</i> under Java 6). Hence Checkstyle
088 * supports using the convention of using a single {@code {@inheritDoc}} tag
089 * instead of all the other tags.
090 * </p>
091 * <p>
092 * Note that only inheritable items will allow the {@code {@inheritDoc}}
093 * tag to be used in place of comments. Static methods at all visibilities,
094 * private non-static methods and constructors are not inheritable.
095 * </p>
096 * <p>
097 * For example, if the following method is implementing a method required by
098 * an interface, then the Javadoc could be done as:
099 * </p>
100 * <pre>
101 * &#47;** {&#64;inheritDoc} *&#47;
102 * public int checkReturnTag(final int aTagIndex,
103 *                           JavadocTag[] aTags,
104 *                           int aLineNo)
105 * </pre>
106 * <ul>
107 * <li>
108 * Property {@code allowedAnnotations} - Specify the list of annotations
109 * that allow missed documentation.
110 * Type is {@code java.lang.String[]}.
111 * Default value is {@code Override}.
112 * </li>
113 * <li>
114 * Property {@code validateThrows} - Control whether to validate {@code throws} tags.
115 * Type is {@code boolean}.
116 * Default value is {@code false}.
117 * </li>
118 * <li>
119 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked.
120 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
121 * Default value is {@code private}.
122 * </li>
123 * <li>
124 * Property {@code excludeScope} - Specify the visibility scope where Javadoc comments
125 * are not checked.
126 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
127 * Default value is {@code null}.
128 * </li>
129 * <li>
130 * Property {@code allowMissingParamTags} - Control whether to ignore violations
131 * when a method has parameters but does not have matching {@code param} tags in the javadoc.
132 * Type is {@code boolean}.
133 * Default value is {@code false}.
134 * </li>
135 * <li>
136 * Property {@code allowMissingReturnTag} - Control whether to ignore violations
137 * when a method returns non-void type and does not have a {@code return} tag in the javadoc.
138 * Type is {@code boolean}.
139 * Default value is {@code false}.
140 * </li>
141 * <li>
142 * Property {@code tokens} - tokens to check
143 * Type is {@code int[]}.
144 * Default value is:
145 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
146 * METHOD_DEF</a>,
147 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
148 * CTOR_DEF</a>,
149 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
150 * ANNOTATION_FIELD_DEF</a>.
151 * </li>
152 * </ul>
153 * <p>
154 * To configure the default check:
155 * </p>
156 * <pre>
157 * &lt;module name="JavadocMethod"/&gt;
158 * </pre>
159 * <p>
160 * To configure the check for {@code public} scope, ignoring any missing param tags is:
161 * </p>
162 * <pre>
163 * &lt;module name="JavadocMethod"&gt;
164 *   &lt;property name="scope" value="public"/&gt;
165 *   &lt;property name="allowMissingParamTags" value="true"/&gt;
166 * &lt;/module&gt;
167 * </pre>
168 * <p>
169 * To configure the check for methods which are in {@code private},
170 * but not in {@code protected} scope:
171 * </p>
172 * <pre>
173 * &lt;module name="JavadocMethod"&gt;
174 *   &lt;property name="scope" value="private"/&gt;
175 *   &lt;property name="excludeScope" value="protected"/&gt;
176 * &lt;/module&gt;
177 * </pre>
178 * <p>
179 * To configure the check to validate {@code throws} tags, you can use following config.
180 * </p>
181 * <pre>
182 * &lt;module name="JavadocMethod"&gt;
183 *   &lt;property name="validateThrows" value="true"/&gt;
184 * &lt;/module&gt;
185 * </pre>
186 * <pre>
187 * &#47;**
188 *  * Actual exception thrown is child class of class that is declared in throws.
189 *  * It is limitation of checkstyle (as checkstyle does not know type hierarchy).
190 *  * Javadoc is valid not declaring FileNotFoundException
191 *  * BUT checkstyle can not distinguish relationship between exceptions.
192 *  * &#64;param file some file
193 *  * &#64;throws IOException if some problem
194 *  *&#47;
195 * public void doSomething8(File file) throws IOException {
196 *     if (file == null) {
197 *         throw new FileNotFoundException(); // violation
198 *     }
199 * }
200 *
201 * &#47;**
202 *  * Exact throw type referencing in javadoc even first is parent of second type.
203 *  * It is a limitation of checkstyle (as checkstyle does not know type hierarchy).
204 *  * This javadoc is valid for checkstyle and for javadoc tool.
205 *  * &#64;param file some file
206 *  * &#64;throws IOException if some problem
207 *  * &#64;throws FileNotFoundException if file is not found
208 *  *&#47;
209 * public void doSomething9(File file) throws IOException {
210 *     if (file == null) {
211 *         throw new FileNotFoundException();
212 *     }
213 * }
214 *
215 * &#47;**
216 *  * Ignore try block, but keep catch and finally blocks.
217 *  *
218 *  * &#64;param s String to parse
219 *  * &#64;return A positive integer
220 *  *&#47;
221 * public int parsePositiveInt(String s) {
222 *     try {
223 *         int value = Integer.parseInt(s);
224 *         if (value &lt;= 0) {
225 *             throw new NumberFormatException(value + " is negative/zero"); // ok, try
226 *         }
227 *         return value;
228 *     } catch (NumberFormatException ex) {
229 *         throw new IllegalArgumentException("Invalid number", ex); // violation, catch
230 *     } finally {
231 *         throw new IllegalStateException("Should never reach here"); // violation, finally
232 *     }
233 * }
234 *
235 * &#47;**
236 *  * Try block without catch is not ignored.
237 *  *
238 *  * &#64;return a String from standard input, if there is one
239 *  *&#47;
240 * public String readLine() {
241 *     try (Scanner sc = new Scanner(System.in)) {
242 *         if (!sc.hasNext()) {
243 *             throw new IllegalStateException("Empty input"); // violation, not caught
244 *         }
245 *         return sc.next();
246 *     }
247 * }
248 *
249 * &#47;**
250 *  * Lambda expressions are ignored as we do not know when the exception will be thrown.
251 *  *
252 *  * &#64;param s a String to be printed at some point in the future
253 *  * &#64;return a Runnable to be executed when the string is to be printed
254 *  *&#47;
255 * public Runnable printLater(String s) {
256 *     return () -&gt; {
257 *         if (s == null) {
258 *             throw new NullPointerException(); // ok
259 *         }
260 *         System.out.println(s);
261 *     };
262 * }
263 * </pre>
264 * <p>
265 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
266 * </p>
267 * <p>
268 * Violation Message Keys:
269 * </p>
270 * <ul>
271 * <li>
272 * {@code javadoc.classInfo}
273 * </li>
274 * <li>
275 * {@code javadoc.duplicateTag}
276 * </li>
277 * <li>
278 * {@code javadoc.expectedTag}
279 * </li>
280 * <li>
281 * {@code javadoc.invalidInheritDoc}
282 * </li>
283 * <li>
284 * {@code javadoc.return.expected}
285 * </li>
286 * <li>
287 * {@code javadoc.unusedTag}
288 * </li>
289 * <li>
290 * {@code javadoc.unusedTagGeneral}
291 * </li>
292 * </ul>
293 *
294 * @since 3.0
295 */
296@FileStatefulCheck
297public class JavadocMethodCheck extends AbstractCheck {
298
299    /**
300     * A key is pointing to the warning message text in "messages.properties"
301     * file.
302     */
303    public static final String MSG_CLASS_INFO = "javadoc.classInfo";
304
305    /**
306     * A key is pointing to the warning message text in "messages.properties"
307     * file.
308     */
309    public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
310
311    /**
312     * A key is pointing to the warning message text in "messages.properties"
313     * file.
314     */
315    public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
316
317    /**
318     * A key is pointing to the warning message text in "messages.properties"
319     * file.
320     */
321    public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
322
323    /**
324     * A key is pointing to the warning message text in "messages.properties"
325     * file.
326     */
327    public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
328
329    /**
330     * A key is pointing to the warning message text in "messages.properties"
331     * file.
332     */
333    public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
334
335    /**
336     * A key is pointing to the warning message text in "messages.properties"
337     * file.
338     */
339    public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
340
341    /** Compiled regexp to match Javadoc tags that take an argument. */
342    private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern(
343            "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
344    /** Compiled regexp to match Javadoc tags with argument but with missing description. */
345    private static final Pattern MATCH_JAVADOC_ARG_MISSING_DESCRIPTION =
346        CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+"
347            + "(\\S[^*]*)(?:(\\s+|\\*\\/))?");
348
349    /** Compiled regexp to look for a continuation of the comment. */
350    private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
351            CommonUtil.createPattern("(\\*\\/|@|[^\\s\\*])");
352
353    /** Multiline finished at end of comment. */
354    private static final String END_JAVADOC = "*/";
355    /** Multiline finished at next Javadoc. */
356    private static final String NEXT_TAG = "@";
357
358    /** Compiled regexp to match Javadoc tags with no argument. */
359    private static final Pattern MATCH_JAVADOC_NOARG =
360            CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S");
361    /** Compiled regexp to match first part of multilineJavadoc tags. */
362    private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
363            CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$");
364    /** Compiled regexp to match Javadoc tags with no argument and {}. */
365    private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
366            CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
367
368    /** Name of current class. */
369    private String currentClassName;
370
371    /** Specify the visibility scope where Javadoc comments are checked. */
372    private Scope scope = Scope.PRIVATE;
373
374    /** Specify the visibility scope where Javadoc comments are not checked. */
375    private Scope excludeScope;
376
377    /**
378     * Control whether to validate {@code throws} tags.
379     */
380    private boolean validateThrows;
381
382    /**
383     * Control whether to ignore violations when a method has parameters but does
384     * not have matching {@code param} tags in the javadoc.
385     */
386    private boolean allowMissingParamTags;
387
388    /**
389     * Control whether to ignore violations when a method returns non-void type
390     * and does not have a {@code return} tag in the javadoc.
391     */
392    private boolean allowMissingReturnTag;
393
394    /** Specify the list of annotations that allow missed documentation. */
395    private List<String> allowedAnnotations = Collections.singletonList("Override");
396
397    /**
398     * Setter to control whether to validate {@code throws} tags.
399     *
400     * @param value user's value.
401     */
402    public void setValidateThrows(boolean value) {
403        validateThrows = value;
404    }
405
406    /**
407     * Setter to specify the list of annotations that allow missed documentation.
408     *
409     * @param userAnnotations user's value.
410     */
411    public void setAllowedAnnotations(String... userAnnotations) {
412        allowedAnnotations = Arrays.asList(userAnnotations);
413    }
414
415    /**
416     * Setter to specify the visibility scope where Javadoc comments are checked.
417     *
418     * @param scope a scope.
419     */
420    public void setScope(Scope scope) {
421        this.scope = scope;
422    }
423
424    /**
425     * Setter to specify the visibility scope where Javadoc comments are not checked.
426     *
427     * @param excludeScope a scope.
428     */
429    public void setExcludeScope(Scope excludeScope) {
430        this.excludeScope = excludeScope;
431    }
432
433    /**
434     * Setter to control whether to ignore violations when a method has parameters
435     * but does not have matching {@code param} tags in the javadoc.
436     *
437     * @param flag a {@code Boolean} value
438     */
439    public void setAllowMissingParamTags(boolean flag) {
440        allowMissingParamTags = flag;
441    }
442
443    /**
444     * Setter to control whether to ignore violations when a method returns non-void type
445     * and does not have a {@code return} tag in the javadoc.
446     *
447     * @param flag a {@code Boolean} value
448     */
449    public void setAllowMissingReturnTag(boolean flag) {
450        allowMissingReturnTag = flag;
451    }
452
453    @Override
454    public final int[] getRequiredTokens() {
455        return new int[] {
456            TokenTypes.CLASS_DEF,
457            TokenTypes.INTERFACE_DEF,
458            TokenTypes.ENUM_DEF,
459        };
460    }
461
462    @Override
463    public int[] getDefaultTokens() {
464        return getAcceptableTokens();
465    }
466
467    @Override
468    public int[] getAcceptableTokens() {
469        return new int[] {
470            TokenTypes.CLASS_DEF,
471            TokenTypes.ENUM_DEF,
472            TokenTypes.INTERFACE_DEF,
473            TokenTypes.METHOD_DEF,
474            TokenTypes.CTOR_DEF,
475            TokenTypes.ANNOTATION_FIELD_DEF,
476        };
477    }
478
479    @Override
480    public void beginTree(DetailAST rootAST) {
481        currentClassName = "";
482    }
483
484    @Override
485    public final void visitToken(DetailAST ast) {
486        if (ast.getType() == TokenTypes.CLASS_DEF
487                 || ast.getType() == TokenTypes.INTERFACE_DEF
488                 || ast.getType() == TokenTypes.ENUM_DEF) {
489            processClass(ast);
490        }
491        else {
492            processAST(ast);
493        }
494    }
495
496    @Override
497    public final void leaveToken(DetailAST ast) {
498        if (ast.getType() == TokenTypes.CLASS_DEF
499            || ast.getType() == TokenTypes.INTERFACE_DEF
500            || ast.getType() == TokenTypes.ENUM_DEF) {
501            // perhaps it was inner class
502            final int dotIdx = currentClassName.lastIndexOf('$');
503            currentClassName = currentClassName.substring(0, dotIdx);
504        }
505    }
506
507    /**
508     * Called to process an AST when visiting it.
509     *
510     * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or
511     *             IMPORT tokens.
512     */
513    private void processAST(DetailAST ast) {
514        final Scope theScope = calculateScope(ast);
515        if (shouldCheck(ast, theScope)) {
516            final FileContents contents = getFileContents();
517            final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
518
519            if (textBlock != null) {
520                checkComment(ast, textBlock);
521            }
522        }
523    }
524
525    /**
526     * Whether we should check this node.
527     *
528     * @param ast a given node.
529     * @param nodeScope the scope of the node.
530     * @return whether we should check a given node.
531     */
532    private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
533        final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
534
535        return (excludeScope == null
536                || nodeScope != excludeScope
537                && surroundingScope != excludeScope)
538            && nodeScope.isIn(scope)
539            && surroundingScope.isIn(scope);
540    }
541
542    /**
543     * Checks the Javadoc for a method.
544     *
545     * @param ast the token for the method
546     * @param comment the Javadoc comment
547     */
548    private void checkComment(DetailAST ast, TextBlock comment) {
549        final List<JavadocTag> tags = getMethodTags(comment);
550
551        if (!hasShortCircuitTag(ast, tags)) {
552            if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
553                checkReturnTag(tags, ast.getLineNo(), true);
554            }
555            else {
556                final Iterator<JavadocTag> it = tags.iterator();
557                // Check for inheritDoc
558                boolean hasInheritDocTag = false;
559                while (!hasInheritDocTag && it.hasNext()) {
560                    hasInheritDocTag = it.next().isInheritDocTag();
561                }
562                final boolean reportExpectedTags = !hasInheritDocTag
563                    && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
564
565                checkParamTags(tags, ast, reportExpectedTags);
566                final List<ExceptionInfo> throwed =
567                        combineExceptionInfo(getThrows(ast), getThrowed(ast));
568                checkThrowsTags(tags, throwed, reportExpectedTags);
569                if (CheckUtil.isNonVoidMethod(ast)) {
570                    checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
571                }
572            }
573
574            // Dump out all unused tags
575            tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
576                .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
577        }
578    }
579
580    /**
581     * Validates whether the Javadoc has a short circuit tag. Currently this is
582     * the inheritTag. Any violations are logged.
583     *
584     * @param ast the construct being checked
585     * @param tags the list of Javadoc tags associated with the construct
586     * @return true if the construct has a short circuit tag.
587     */
588    private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
589        boolean result = true;
590        // Check if it contains {@inheritDoc} tag
591        if (tags.size() == 1
592                && tags.get(0).isInheritDocTag()) {
593            // Invalid if private, a constructor, or a static method
594            if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
595                log(ast, MSG_INVALID_INHERIT_DOC);
596            }
597        }
598        else {
599            result = false;
600        }
601        return result;
602    }
603
604    /**
605     * Returns the scope for the method/constructor at the specified AST. If
606     * the method is in an interface or annotation block, the scope is assumed
607     * to be public.
608     *
609     * @param ast the token of the method/constructor
610     * @return the scope of the method/constructor
611     */
612    private static Scope calculateScope(final DetailAST ast) {
613        final Scope scope;
614
615        if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
616            scope = Scope.PUBLIC;
617        }
618        else {
619            final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
620            scope = ScopeUtil.getScopeFromMods(mods);
621        }
622        return scope;
623    }
624
625    /**
626     * Returns the tags in a javadoc comment. Only finds throws, exception,
627     * param, return and see tags.
628     *
629     * @param comment the Javadoc comment
630     * @return the tags found
631     */
632    private static List<JavadocTag> getMethodTags(TextBlock comment) {
633        final String[] lines = comment.getText();
634        final List<JavadocTag> tags = new ArrayList<>();
635        int currentLine = comment.getStartLineNo() - 1;
636        final int startColumnNumber = comment.getStartColNo();
637
638        for (int i = 0; i < lines.length; i++) {
639            currentLine++;
640            final Matcher javadocArgMatcher =
641                MATCH_JAVADOC_ARG.matcher(lines[i]);
642            final Matcher javadocArgMissingDescriptionMatcher =
643                MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]);
644            final Matcher javadocNoargMatcher =
645                MATCH_JAVADOC_NOARG.matcher(lines[i]);
646            final Matcher noargCurlyMatcher =
647                MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
648            final Matcher noargMultilineStart =
649                MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
650
651            if (javadocArgMatcher.find()) {
652                final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
653                tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
654                        javadocArgMatcher.group(2)));
655            }
656            else if (javadocArgMissingDescriptionMatcher.find()) {
657                final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i,
658                    startColumnNumber);
659                tags.add(new JavadocTag(currentLine, col,
660                    javadocArgMissingDescriptionMatcher.group(1),
661                    javadocArgMissingDescriptionMatcher.group(2)));
662            }
663            else if (javadocNoargMatcher.find()) {
664                final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
665                tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
666            }
667            else if (noargCurlyMatcher.find()) {
668                final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
669                tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
670            }
671            else if (noargMultilineStart.find()) {
672                tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
673            }
674        }
675        return tags;
676    }
677
678    /**
679     * Calculates column number using Javadoc tag matcher.
680     *
681     * @param javadocTagMatcher found javadoc tag matcher
682     * @param lineNumber line number of Javadoc tag in comment
683     * @param startColumnNumber column number of Javadoc comment beginning
684     * @return column number
685     */
686    private static int calculateTagColumn(Matcher javadocTagMatcher,
687            int lineNumber, int startColumnNumber) {
688        int col = javadocTagMatcher.start(1) - 1;
689        if (lineNumber == 0) {
690            col += startColumnNumber;
691        }
692        return col;
693    }
694
695    /**
696     * Gets multiline Javadoc tags with no arguments.
697     *
698     * @param noargMultilineStart javadoc tag Matcher
699     * @param lines comment text lines
700     * @param lineIndex line number that contains the javadoc tag
701     * @param tagLine javadoc tag line number in file
702     * @return javadoc tags with no arguments
703     */
704    private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
705            final String[] lines, final int lineIndex, final int tagLine) {
706        int remIndex = lineIndex;
707        Matcher multilineCont;
708
709        do {
710            remIndex++;
711            multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
712        } while (!multilineCont.find());
713
714        final List<JavadocTag> tags = new ArrayList<>();
715        final String lFin = multilineCont.group(1);
716        if (!lFin.equals(NEXT_TAG)
717            && !lFin.equals(END_JAVADOC)) {
718            final String param1 = noargMultilineStart.group(1);
719            final int col = noargMultilineStart.start(1) - 1;
720
721            tags.add(new JavadocTag(tagLine, col, param1));
722        }
723
724        return tags;
725    }
726
727    /**
728     * Computes the parameter nodes for a method.
729     *
730     * @param ast the method node.
731     * @return the list of parameter nodes for ast.
732     */
733    private static List<DetailAST> getParameters(DetailAST ast) {
734        final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
735        final List<DetailAST> returnValue = new ArrayList<>();
736
737        DetailAST child = params.getFirstChild();
738        while (child != null) {
739            if (child.getType() == TokenTypes.PARAMETER_DEF) {
740                final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
741                if (ident != null) {
742                    returnValue.add(ident);
743                }
744            }
745            child = child.getNextSibling();
746        }
747        return returnValue;
748    }
749
750    /**
751     * Computes the exception nodes for a method.
752     *
753     * @param ast the method node.
754     * @return the list of exception nodes for ast.
755     */
756    private static List<ExceptionInfo> getThrows(DetailAST ast) {
757        final List<ExceptionInfo> returnValue = new ArrayList<>();
758        final DetailAST throwsAST = ast
759                .findFirstToken(TokenTypes.LITERAL_THROWS);
760        if (throwsAST != null) {
761            DetailAST child = throwsAST.getFirstChild();
762            while (child != null) {
763                if (child.getType() == TokenTypes.IDENT
764                        || child.getType() == TokenTypes.DOT) {
765                    returnValue.add(getExceptionInfo(child));
766                }
767                child = child.getNextSibling();
768            }
769        }
770        return returnValue;
771    }
772
773    /**
774     * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'.
775     *
776     * @param methodAst method DetailAST object where to find exceptions
777     * @return list of ExceptionInfo
778     */
779    private static List<ExceptionInfo> getThrowed(DetailAST methodAst) {
780        final List<ExceptionInfo> returnValue = new ArrayList<>();
781        final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST);
782        if (blockAst != null) {
783            final List<DetailAST> throwLiterals = findTokensInAstByType(blockAst,
784                    TokenTypes.LITERAL_THROW);
785            for (DetailAST throwAst : throwLiterals) {
786                if (!isInIgnoreBlock(blockAst, throwAst)) {
787                    final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
788                    if (newAst.getType() == TokenTypes.LITERAL_NEW) {
789                        final DetailAST child = newAst.getFirstChild();
790                        returnValue.add(getExceptionInfo(child));
791                    }
792                }
793            }
794        }
795        return returnValue;
796    }
797
798    /**
799     * Get ExceptionInfo instance.
800     *
801     * @param ast DetailAST object where to find exceptions node;
802     * @return ExceptionInfo
803     */
804    private static ExceptionInfo getExceptionInfo(DetailAST ast) {
805        final FullIdent ident = FullIdent.createFullIdent(ast);
806        final DetailAST firstClassNameNode = getFirstClassNameNode(ast);
807        return new ExceptionInfo(firstClassNameNode,
808                new ClassInfo(new Token(ident)));
809    }
810
811    /**
812     * Get node where class name of exception starts.
813     *
814     * @param ast DetailAST object where to find exceptions node;
815     * @return exception node where class name starts
816     */
817    private static DetailAST getFirstClassNameNode(DetailAST ast) {
818        DetailAST startNode = ast;
819        while (startNode.getType() == TokenTypes.DOT) {
820            startNode = startNode.getFirstChild();
821        }
822        return startNode;
823    }
824
825    /**
826     * Checks if a 'throw' usage is contained within a block that should be ignored.
827     * Such blocks consist of try (with catch) blocks, local classes, anonymous classes,
828     * and lambda expressions. Note that a try block without catch is not considered.
829     *
830     * @param methodBodyAst DetailAST node representing the method body
831     * @param throwAst DetailAST node representing the 'throw' literal
832     * @return true if throwAst is inside a block that should be ignored
833     */
834    private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) {
835        DetailAST ancestor = throwAst.getParent();
836        while (ancestor != methodBodyAst) {
837            if (ancestor.getType() == TokenTypes.LITERAL_TRY
838                    && ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null
839                    || ancestor.getType() == TokenTypes.LAMBDA
840                    || ancestor.getType() == TokenTypes.OBJBLOCK) {
841                // throw is inside a try block, and there is a catch block,
842                // or throw is inside a lambda expression/anonymous class/local class
843                break;
844            }
845            if (ancestor.getType() == TokenTypes.LITERAL_CATCH
846                    || ancestor.getType() == TokenTypes.LITERAL_FINALLY) {
847                // if the throw is inside a catch or finally block,
848                // skip the immediate ancestor (try token)
849                ancestor = ancestor.getParent();
850            }
851            ancestor = ancestor.getParent();
852        }
853        return ancestor != methodBodyAst;
854    }
855
856    /**
857     * Combine ExceptionInfo lists together by matching names.
858     *
859     * @param list1 list of ExceptionInfo
860     * @param list2 list of ExceptionInfo
861     * @return combined list of ExceptionInfo
862     */
863    private static List<ExceptionInfo> combineExceptionInfo(List<ExceptionInfo> list1,
864                                                     List<ExceptionInfo> list2) {
865        final List<ExceptionInfo> result = new ArrayList<>(list1);
866        for (ExceptionInfo exceptionInfo : list2) {
867            if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) {
868                result.add(exceptionInfo);
869            }
870        }
871        return result;
872    }
873
874    /**
875     * Finds node of specified type among root children, siblings, siblings children
876     * on any deep level.
877     *
878     * @param root    DetailAST
879     * @param astType value of TokenType
880     * @return {@link List} of {@link DetailAST} nodes which matches the predicate.
881     */
882    public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
883        final List<DetailAST> result = new ArrayList<>();
884        DetailAST curNode = root;
885        while (curNode != null) {
886            DetailAST toVisit = curNode.getFirstChild();
887            while (curNode != null && toVisit == null) {
888                toVisit = curNode.getNextSibling();
889                curNode = curNode.getParent();
890                if (curNode == root) {
891                    toVisit = null;
892                    break;
893                }
894            }
895            curNode = toVisit;
896            if (curNode != null && curNode.getType() == astType) {
897                result.add(curNode);
898            }
899        }
900        return result;
901    }
902
903    /**
904     * Checks a set of tags for matching parameters.
905     *
906     * @param tags the tags to check
907     * @param parent the node which takes the parameters
908     * @param reportExpectedTags whether we should report if do not find
909     *            expected tag
910     */
911    private void checkParamTags(final List<JavadocTag> tags,
912            final DetailAST parent, boolean reportExpectedTags) {
913        final List<DetailAST> params = getParameters(parent);
914        final List<DetailAST> typeParams = CheckUtil
915                .getTypeParameters(parent);
916
917        // Loop over the tags, checking to see they exist in the params.
918        final ListIterator<JavadocTag> tagIt = tags.listIterator();
919        while (tagIt.hasNext()) {
920            final JavadocTag tag = tagIt.next();
921
922            if (!tag.isParamTag()) {
923                continue;
924            }
925
926            tagIt.remove();
927
928            final String arg1 = tag.getFirstArg();
929            boolean found = removeMatchingParam(params, arg1);
930
931            if (CommonUtil.startsWithChar(arg1, '<') && CommonUtil.endsWithChar(arg1, '>')) {
932                found = searchMatchingTypeParameter(typeParams,
933                        arg1.substring(1, arg1.length() - 1));
934            }
935
936            // Handle extra JavadocTag
937            if (!found) {
938                log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
939                        "@param", arg1);
940            }
941        }
942
943        // Now dump out all type parameters/parameters without tags :- unless
944        // the user has chosen to suppress these problems
945        if (!allowMissingParamTags && reportExpectedTags) {
946            for (DetailAST param : params) {
947                log(param, MSG_EXPECTED_TAG,
948                    JavadocTagInfo.PARAM.getText(), param.getText());
949            }
950
951            for (DetailAST typeParam : typeParams) {
952                log(typeParam, MSG_EXPECTED_TAG,
953                    JavadocTagInfo.PARAM.getText(),
954                    "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
955                    + ">");
956            }
957        }
958    }
959
960    /**
961     * Returns true if required type found in type parameters.
962     *
963     * @param typeParams
964     *            list of type parameters
965     * @param requiredTypeName
966     *            name of required type
967     * @return true if required type found in type parameters.
968     */
969    private static boolean searchMatchingTypeParameter(List<DetailAST> typeParams,
970            String requiredTypeName) {
971        // Loop looking for matching type param
972        final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
973        boolean found = false;
974        while (typeParamsIt.hasNext()) {
975            final DetailAST typeParam = typeParamsIt.next();
976            if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
977                    .equals(requiredTypeName)) {
978                found = true;
979                typeParamsIt.remove();
980                break;
981            }
982        }
983        return found;
984    }
985
986    /**
987     * Remove parameter from params collection by name.
988     *
989     * @param params collection of DetailAST parameters
990     * @param paramName name of parameter
991     * @return true if parameter found and removed
992     */
993    private static boolean removeMatchingParam(List<DetailAST> params, String paramName) {
994        boolean found = false;
995        final Iterator<DetailAST> paramIt = params.iterator();
996        while (paramIt.hasNext()) {
997            final DetailAST param = paramIt.next();
998            if (param.getText().equals(paramName)) {
999                found = true;
1000                paramIt.remove();
1001                break;
1002            }
1003        }
1004        return found;
1005    }
1006
1007    /**
1008     * Checks for only one return tag. All return tags will be removed from the
1009     * supplied list.
1010     *
1011     * @param tags the tags to check
1012     * @param lineNo the line number of the expected tag
1013     * @param reportExpectedTags whether we should report if do not find
1014     *            expected tag
1015     */
1016    private void checkReturnTag(List<JavadocTag> tags, int lineNo,
1017        boolean reportExpectedTags) {
1018        // Loop over tags finding return tags. After the first one, report an
1019        // violation.
1020        boolean found = false;
1021        final ListIterator<JavadocTag> it = tags.listIterator();
1022        while (it.hasNext()) {
1023            final JavadocTag javadocTag = it.next();
1024            if (javadocTag.isReturnTag()) {
1025                if (found) {
1026                    log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
1027                            MSG_DUPLICATE_TAG,
1028                            JavadocTagInfo.RETURN.getText());
1029                }
1030                found = true;
1031                it.remove();
1032            }
1033        }
1034
1035        // Handle there being no @return tags :- unless
1036        // the user has chosen to suppress these problems
1037        if (!found && !allowMissingReturnTag && reportExpectedTags) {
1038            log(lineNo, MSG_RETURN_EXPECTED);
1039        }
1040    }
1041
1042    /**
1043     * Checks a set of tags for matching throws.
1044     *
1045     * @param tags the tags to check
1046     * @param throwsList the throws to check
1047     * @param reportExpectedTags whether we should report if do not find
1048     *            expected tag
1049     */
1050    private void checkThrowsTags(List<JavadocTag> tags,
1051            List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
1052        // Loop over the tags, checking to see they exist in the throws.
1053        // The foundThrows used for performance only
1054        final Set<String> foundThrows = new HashSet<>();
1055        final ListIterator<JavadocTag> tagIt = tags.listIterator();
1056        while (tagIt.hasNext()) {
1057            final JavadocTag tag = tagIt.next();
1058
1059            if (!tag.isThrowsTag()) {
1060                continue;
1061            }
1062            tagIt.remove();
1063
1064            // Loop looking for matching throw
1065            final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
1066                    .getColumnNo());
1067            final ClassInfo documentedClassInfo = new ClassInfo(token);
1068            processThrows(throwsList, documentedClassInfo, foundThrows);
1069        }
1070        // Now dump out all throws without tags :- unless
1071        // the user has chosen to suppress these problems
1072        if (validateThrows && reportExpectedTags) {
1073            throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
1074                .forEach(exceptionInfo -> {
1075                    final Token token = exceptionInfo.getName();
1076                    log(exceptionInfo.getAst(),
1077                        MSG_EXPECTED_TAG,
1078                        JavadocTagInfo.THROWS.getText(), token.getText());
1079                });
1080        }
1081    }
1082
1083    /**
1084     * Verifies that documented exception is in throws.
1085     *
1086     * @param throwsList list of throws
1087     * @param documentedClassInfo documented exception class info
1088     * @param foundThrows previously found throws
1089     */
1090    private static void processThrows(List<ExceptionInfo> throwsList,
1091                                      ClassInfo documentedClassInfo, Set<String> foundThrows) {
1092        ExceptionInfo foundException = null;
1093
1094        // First look for matches on the exception name
1095        for (ExceptionInfo exceptionInfo : throwsList) {
1096            if (isClassNamesSame(exceptionInfo.getName().getText(),
1097                    documentedClassInfo.getName().getText())) {
1098                foundException = exceptionInfo;
1099                break;
1100            }
1101        }
1102
1103        if (foundException != null) {
1104            foundException.setFound();
1105            foundThrows.add(documentedClassInfo.getName().getText());
1106        }
1107    }
1108
1109    /**
1110     * Check that ExceptionInfo objects are same by name.
1111     *
1112     * @param info1 ExceptionInfo object
1113     * @param info2 ExceptionInfo object
1114     * @return true is ExceptionInfo object have the same name
1115     */
1116    private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
1117        return isClassNamesSame(info1.getName().getText(),
1118                                    info2.getName().getText());
1119    }
1120
1121    /**
1122     * Check that class names are same by short name of class. If some class name is fully
1123     * qualified it is cut to short name.
1124     *
1125     * @param class1 class name
1126     * @param class2 class name
1127     * @return true is ExceptionInfo object have the same name
1128     */
1129    private static boolean isClassNamesSame(String class1, String class2) {
1130        boolean result = false;
1131        if (class1.equals(class2)) {
1132            result = true;
1133        }
1134        else {
1135            final String separator = ".";
1136            if (class1.contains(separator) || class2.contains(separator)) {
1137                final String class1ShortName = class1
1138                        .substring(class1.lastIndexOf('.') + 1);
1139                final String class2ShortName = class2
1140                        .substring(class2.lastIndexOf('.') + 1);
1141                result = class1ShortName.equals(class2ShortName);
1142            }
1143        }
1144        return result;
1145    }
1146
1147    /**
1148     * Processes class definition.
1149     *
1150     * @param ast class definition to process.
1151     */
1152    private void processClass(DetailAST ast) {
1153        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
1154        String innerClass = ident.getText();
1155
1156        innerClass = "$" + innerClass;
1157        currentClassName += innerClass;
1158    }
1159
1160    /**
1161     * Contains class's {@code Token}.
1162     */
1163    private static class ClassInfo {
1164
1165        /** {@code FullIdent} associated with this class. */
1166        private final Token name;
1167
1168        /**
1169         * Creates new instance of class information object.
1170         *
1171         * @param className token which represents class name.
1172         * @throws IllegalArgumentException when className is nulls
1173         */
1174        protected ClassInfo(final Token className) {
1175            name = className;
1176        }
1177
1178        /**
1179         * Gets class name.
1180         *
1181         * @return class name
1182         */
1183        public final Token getName() {
1184            return name;
1185        }
1186
1187    }
1188
1189    /**
1190     * Represents text element with location in the text.
1191     */
1192    private static class Token {
1193
1194        /** Token's column number. */
1195        private final int columnNo;
1196        /** Token's line number. */
1197        private final int lineNo;
1198        /** Token's text. */
1199        private final String text;
1200
1201        /**
1202         * Creates token.
1203         *
1204         * @param text token's text
1205         * @param lineNo token's line number
1206         * @param columnNo token's column number
1207         */
1208        /* package */ Token(String text, int lineNo, int columnNo) {
1209            this.text = text;
1210            this.lineNo = lineNo;
1211            this.columnNo = columnNo;
1212        }
1213
1214        /**
1215         * Converts FullIdent to Token.
1216         *
1217         * @param fullIdent full ident to convert.
1218         */
1219        /* package */ Token(FullIdent fullIdent) {
1220            text = fullIdent.getText();
1221            lineNo = fullIdent.getLineNo();
1222            columnNo = fullIdent.getColumnNo();
1223        }
1224
1225        /**
1226         * Gets text of the token.
1227         *
1228         * @return text of the token
1229         */
1230        public String getText() {
1231            return text;
1232        }
1233
1234        @Override
1235        public String toString() {
1236            return "Token[" + text + "(" + lineNo
1237                + "x" + columnNo + ")]";
1238        }
1239
1240    }
1241
1242    /** Stores useful information about declared exception. */
1243    private static class ExceptionInfo {
1244
1245        /** AST node representing this exception. */
1246        private final DetailAST ast;
1247
1248        /** Class information associated with this exception. */
1249        private final ClassInfo classInfo;
1250        /** Does the exception have throws tag associated with. */
1251        private boolean found;
1252
1253        /**
1254         * Creates new instance for {@code FullIdent}.
1255         *
1256         * @param ast AST node representing this exception
1257         * @param classInfo class info
1258         */
1259        /* package */ ExceptionInfo(DetailAST ast, ClassInfo classInfo) {
1260            this.ast = ast;
1261            this.classInfo = classInfo;
1262        }
1263
1264        /**
1265         * Gets the AST node representing this exception.
1266         *
1267         * @return the AST node representing this exception
1268         */
1269        private DetailAST getAst() {
1270            return ast;
1271        }
1272
1273        /** Mark that the exception has associated throws tag. */
1274        private void setFound() {
1275            found = true;
1276        }
1277
1278        /**
1279         * Checks that the exception has throws tag associated with it.
1280         *
1281         * @return whether the exception has throws tag associated with
1282         */
1283        private boolean isFound() {
1284            return found;
1285        }
1286
1287        /**
1288         * Gets exception name.
1289         *
1290         * @return exception's name
1291         */
1292        private Token getName() {
1293            return classInfo.getName();
1294        }
1295
1296    }
1297
1298}