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