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