001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.api.DetailAST;
034import com.puppycrawl.tools.checkstyle.api.FileContents;
035import com.puppycrawl.tools.checkstyle.api.FullIdent;
036import com.puppycrawl.tools.checkstyle.api.Scope;
037import com.puppycrawl.tools.checkstyle.api.TextBlock;
038import com.puppycrawl.tools.checkstyle.api.TokenTypes;
039import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
040import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
041import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
042
043/**
044 * Checks the Javadoc of a method or constructor.
045 *
046 * @author Oliver Burn
047 * @author Rick Giles
048 * @author o_sukhodoslky
049 *
050 * @noinspection deprecation
051 */
052public class JavadocMethodCheck extends AbstractTypeAwareCheck {
053
054    /**
055     * A key is pointing to the warning message text in "messages.properties"
056     * file.
057     */
058    public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
059
060    /**
061     * A key is pointing to the warning message text in "messages.properties"
062     * file.
063     */
064    public static final String MSG_CLASS_INFO = "javadoc.classInfo";
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties"
068     * file.
069     */
070    public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
071
072    /**
073     * A key is pointing to the warning message text in "messages.properties"
074     * file.
075     */
076    public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
077
078    /**
079     * A key is pointing to the warning message text in "messages.properties"
080     * file.
081     */
082    public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
083
084    /**
085     * A key is pointing to the warning message text in "messages.properties"
086     * file.
087     */
088    public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
089
090    /**
091     * A key is pointing to the warning message text in "messages.properties"
092     * file.
093     */
094    public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
095
096    /**
097     * A key is pointing to the warning message text in "messages.properties"
098     * file.
099     */
100    public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
101
102    /** Compiled regexp to match Javadoc tags that take an argument. */
103    private static final Pattern MATCH_JAVADOC_ARG = CommonUtils.createPattern(
104            "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
105
106    /** Compiled regexp to match first part of multilineJavadoc tags. */
107    private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START = CommonUtils.createPattern(
108            "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s*$");
109
110    /** Compiled regexp to look for a continuation of the comment. */
111    private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
112            CommonUtils.createPattern("(\\*/|@|[^\\s\\*])");
113
114    /** Multiline finished at end of comment. */
115    private static final String END_JAVADOC = "*/";
116    /** Multiline finished at next Javadoc. */
117    private static final String NEXT_TAG = "@";
118
119    /** Compiled regexp to match Javadoc tags with no argument. */
120    private static final Pattern MATCH_JAVADOC_NOARG =
121            CommonUtils.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S");
122    /** Compiled regexp to match first part of multilineJavadoc tags. */
123    private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
124            CommonUtils.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$");
125    /** Compiled regexp to match Javadoc tags with no argument and {}. */
126    private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
127            CommonUtils.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
128
129    /** Default value of minimal amount of lines in method to allow no documentation.*/
130    private static final int DEFAULT_MIN_LINE_COUNT = -1;
131
132    /** The visibility scope where Javadoc comments are checked. */
133    private Scope scope = Scope.PRIVATE;
134
135    /** The visibility scope where Javadoc comments shouldn't be checked. */
136    private Scope excludeScope;
137
138    /** Minimal amount of lines in method to allow no documentation.*/
139    private int minLineCount = DEFAULT_MIN_LINE_COUNT;
140
141    /**
142     * Controls whether to allow documented exceptions that are not declared if
143     * they are a subclass of java.lang.RuntimeException.
144     */
145    // -@cs[AbbreviationAsWordInName] We can not change it as,
146    // check's property is part of API (used in configurations).
147    private boolean allowUndeclaredRTE;
148
149    /**
150     * Allows validating throws tags.
151     */
152    private boolean validateThrows;
153
154    /**
155     * Controls whether to allow documented exceptions that are subclass of one
156     * of declared exception. Defaults to false (backward compatibility).
157     */
158    private boolean allowThrowsTagsForSubclasses;
159
160    /**
161     * Controls whether to ignore errors when a method has parameters but does
162     * not have matching param tags in the javadoc. Defaults to false.
163     */
164    private boolean allowMissingParamTags;
165
166    /**
167     * Controls whether to ignore errors when a method declares that it throws
168     * exceptions but does not have matching throws tags in the javadoc.
169     * Defaults to false.
170     */
171    private boolean allowMissingThrowsTags;
172
173    /**
174     * Controls whether to ignore errors when a method returns non-void type
175     * but does not have a return tag in the javadoc. Defaults to false.
176     */
177    private boolean allowMissingReturnTag;
178
179    /**
180     * Controls whether to ignore errors when there is no javadoc. Defaults to
181     * false.
182     */
183    private boolean allowMissingJavadoc;
184
185    /**
186     * Controls whether to allow missing Javadoc on accessor methods for
187     * properties (setters and getters).
188     */
189    private boolean allowMissingPropertyJavadoc;
190
191    /** List of annotations that could allow missed documentation. */
192    private List<String> allowedAnnotations = Collections.singletonList("Override");
193
194    /** Method names that match this pattern do not require javadoc blocks. */
195    private Pattern ignoreMethodNamesRegex;
196
197    /**
198     * Set regex for matching method names to ignore.
199     * @param pattern a pattern.
200     */
201    public void setIgnoreMethodNamesRegex(Pattern pattern) {
202        ignoreMethodNamesRegex = pattern;
203    }
204
205    /**
206     * Sets minimal amount of lines in method to allow no documentation.
207     * @param value user's value.
208     */
209    public void setMinLineCount(int value) {
210        minLineCount = value;
211    }
212
213    /**
214     * Allow validating throws tag.
215     * @param value user's value.
216     */
217    public void setValidateThrows(boolean value) {
218        validateThrows = value;
219    }
220
221    /**
222     * Sets list of annotations.
223     * @param userAnnotations user's value.
224     */
225    public void setAllowedAnnotations(String... userAnnotations) {
226        allowedAnnotations = Arrays.asList(userAnnotations);
227    }
228
229    /**
230     * Set the scope.
231     *
232     * @param scope a scope.
233     */
234    public void setScope(Scope scope) {
235        this.scope = scope;
236    }
237
238    /**
239     * Set the excludeScope.
240     *
241     * @param excludeScope a scope.
242     */
243    public void setExcludeScope(Scope excludeScope) {
244        this.excludeScope = excludeScope;
245    }
246
247    /**
248     * Controls whether to allow documented exceptions that are not declared if
249     * they are a subclass of java.lang.RuntimeException.
250     *
251     * @param flag a {@code Boolean} value
252     */
253    // -@cs[AbbreviationAsWordInName] We can not change it as,
254    // check's property is part of API (used in configurations).
255    public void setAllowUndeclaredRTE(boolean flag) {
256        allowUndeclaredRTE = flag;
257    }
258
259    /**
260     * Controls whether to allow documented exception that are subclass of one
261     * of declared exceptions.
262     *
263     * @param flag a {@code Boolean} value
264     */
265    public void setAllowThrowsTagsForSubclasses(boolean flag) {
266        allowThrowsTagsForSubclasses = flag;
267    }
268
269    /**
270     * Controls whether to allow a method which has parameters to omit matching
271     * param tags in the javadoc. Defaults to false.
272     *
273     * @param flag a {@code Boolean} value
274     */
275    public void setAllowMissingParamTags(boolean flag) {
276        allowMissingParamTags = flag;
277    }
278
279    /**
280     * Controls whether to allow a method which declares that it throws
281     * exceptions to omit matching throws tags in the javadoc. Defaults to
282     * false.
283     *
284     * @param flag a {@code Boolean} value
285     */
286    public void setAllowMissingThrowsTags(boolean flag) {
287        allowMissingThrowsTags = flag;
288    }
289
290    /**
291     * Controls whether to allow a method which returns non-void type to omit
292     * the return tag in the javadoc. Defaults to false.
293     *
294     * @param flag a {@code Boolean} value
295     */
296    public void setAllowMissingReturnTag(boolean flag) {
297        allowMissingReturnTag = flag;
298    }
299
300    /**
301     * Controls whether to ignore errors when there is no javadoc. Defaults to
302     * false.
303     *
304     * @param flag a {@code Boolean} value
305     */
306    public void setAllowMissingJavadoc(boolean flag) {
307        allowMissingJavadoc = flag;
308    }
309
310    /**
311     * Controls whether to ignore errors when there is no javadoc for a
312     * property accessor (setter/getter methods). Defaults to false.
313     *
314     * @param flag a {@code Boolean} value
315     */
316    public void setAllowMissingPropertyJavadoc(final boolean flag) {
317        allowMissingPropertyJavadoc = flag;
318    }
319
320    @Override
321    public int[] getDefaultTokens() {
322        return getAcceptableTokens();
323    }
324
325    @Override
326    public int[] getAcceptableTokens() {
327        return new int[] {
328            TokenTypes.PACKAGE_DEF,
329            TokenTypes.IMPORT,
330            TokenTypes.CLASS_DEF,
331            TokenTypes.ENUM_DEF,
332            TokenTypes.INTERFACE_DEF,
333            TokenTypes.METHOD_DEF,
334            TokenTypes.CTOR_DEF,
335            TokenTypes.ANNOTATION_FIELD_DEF,
336        };
337    }
338
339    @Override
340    public boolean isCommentNodesRequired() {
341        return true;
342    }
343
344    @Override
345    protected final void processAST(DetailAST ast) {
346        final Scope theScope = calculateScope(ast);
347        if (shouldCheck(ast, theScope)) {
348            final FileContents contents = getFileContents();
349            final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
350
351            if (textBlock == null) {
352                if (!isMissingJavadocAllowed(ast)) {
353                    log(ast, MSG_JAVADOC_MISSING);
354                }
355            }
356            else {
357                checkComment(ast, textBlock);
358            }
359        }
360    }
361
362    /**
363     * Some javadoc.
364     * @param methodDef Some javadoc.
365     * @return Some javadoc.
366     */
367    private boolean hasAllowedAnnotations(DetailAST methodDef) {
368        boolean result = false;
369        final DetailAST modifiersNode = methodDef.findFirstToken(TokenTypes.MODIFIERS);
370        DetailAST annotationNode = modifiersNode.findFirstToken(TokenTypes.ANNOTATION);
371        while (annotationNode != null && annotationNode.getType() == TokenTypes.ANNOTATION) {
372            DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT);
373            if (identNode == null) {
374                identNode = annotationNode.findFirstToken(TokenTypes.DOT)
375                    .findFirstToken(TokenTypes.IDENT);
376            }
377            if (allowedAnnotations.contains(identNode.getText())) {
378                result = true;
379                break;
380            }
381            annotationNode = annotationNode.getNextSibling();
382        }
383        return result;
384    }
385
386    /**
387     * Some javadoc.
388     * @param methodDef Some javadoc.
389     * @return Some javadoc.
390     */
391    private static int getMethodsNumberOfLine(DetailAST methodDef) {
392        final int numberOfLines;
393        final DetailAST lcurly = methodDef.getLastChild();
394        final DetailAST rcurly = lcurly.getLastChild();
395
396        if (lcurly.getFirstChild() == rcurly) {
397            numberOfLines = 1;
398        }
399        else {
400            numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
401        }
402        return numberOfLines;
403    }
404
405    @Override
406    protected final void logLoadError(Token ident) {
407        logLoadErrorImpl(ident.getLineNo(), ident.getColumnNo(),
408            MSG_CLASS_INFO,
409            JavadocTagInfo.THROWS.getText(), ident.getText());
410    }
411
412    /**
413     * The JavadocMethodCheck is about to report a missing Javadoc.
414     * This hook can be used by derived classes to allow a missing javadoc
415     * in some situations.  The default implementation checks
416     * {@code allowMissingJavadoc} and
417     * {@code allowMissingPropertyJavadoc} properties, do not forget
418     * to call {@code super.isMissingJavadocAllowed(ast)} in case
419     * you want to keep this logic.
420     * @param ast the tree node for the method or constructor.
421     * @return True if this method or constructor doesn't need Javadoc.
422     */
423    private boolean isMissingJavadocAllowed(final DetailAST ast) {
424        return allowMissingJavadoc
425            || allowMissingPropertyJavadoc
426                && (CheckUtils.isSetterMethod(ast) || CheckUtils.isGetterMethod(ast))
427            || matchesSkipRegex(ast)
428            || isContentsAllowMissingJavadoc(ast);
429    }
430
431    /**
432     * Checks if the Javadoc can be missing if the method or constructor is
433     * below the minimum line count or has a special annotation.
434     *
435     * @param ast the tree node for the method or constructor.
436     * @return True if this method or constructor doesn't need Javadoc.
437     */
438    private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
439        return (ast.getType() == TokenTypes.METHOD_DEF || ast.getType() == TokenTypes.CTOR_DEF)
440                && (getMethodsNumberOfLine(ast) <= minLineCount || hasAllowedAnnotations(ast));
441    }
442
443    /**
444     * Checks if the given method name matches the regex. In that case
445     * we skip enforcement of javadoc for this method
446     * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
447     * @return true if given method name matches the regex.
448     */
449    private boolean matchesSkipRegex(DetailAST methodDef) {
450        boolean result = false;
451        if (ignoreMethodNamesRegex != null) {
452            final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
453            final String methodName = ident.getText();
454
455            final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
456            if (matcher.matches()) {
457                result = true;
458            }
459        }
460        return result;
461    }
462
463    /**
464     * Whether we should check this node.
465     *
466     * @param ast a given node.
467     * @param nodeScope the scope of the node.
468     * @return whether we should check a given node.
469     */
470    private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
471        final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
472
473        return (excludeScope == null
474                || nodeScope != excludeScope
475                && surroundingScope != excludeScope)
476            && nodeScope.isIn(scope)
477            && surroundingScope.isIn(scope);
478    }
479
480    /**
481     * Checks the Javadoc for a method.
482     *
483     * @param ast the token for the method
484     * @param comment the Javadoc comment
485     */
486    private void checkComment(DetailAST ast, TextBlock comment) {
487        final List<JavadocTag> tags = getMethodTags(comment);
488
489        if (!hasShortCircuitTag(ast, tags)) {
490            if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
491                checkReturnTag(tags, ast.getLineNo(), true);
492            }
493            else {
494                final Iterator<JavadocTag> it = tags.iterator();
495                // Check for inheritDoc
496                boolean hasInheritDocTag = false;
497                while (!hasInheritDocTag && it.hasNext()) {
498                    hasInheritDocTag = it.next().isInheritDocTag();
499                }
500                final boolean reportExpectedTags = !hasInheritDocTag && !hasAllowedAnnotations(ast);
501
502                checkParamTags(tags, ast, reportExpectedTags);
503                checkThrowsTags(tags, getThrows(ast), reportExpectedTags);
504                if (CheckUtils.isNonVoidMethod(ast)) {
505                    checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
506                }
507            }
508
509            // Dump out all unused tags
510            tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
511                .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
512        }
513    }
514
515    /**
516     * Validates whether the Javadoc has a short circuit tag. Currently this is
517     * the inheritTag. Any errors are logged.
518     *
519     * @param ast the construct being checked
520     * @param tags the list of Javadoc tags associated with the construct
521     * @return true if the construct has a short circuit tag.
522     */
523    private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
524        boolean result = true;
525        // Check if it contains {@inheritDoc} tag
526        if (tags.size() == 1
527                && tags.get(0).isInheritDocTag()) {
528            // Invalid if private, a constructor, or a static method
529            if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
530                log(ast, MSG_INVALID_INHERIT_DOC);
531            }
532        }
533        else {
534            result = false;
535        }
536        return result;
537    }
538
539    /**
540     * Returns the scope for the method/constructor at the specified AST. If
541     * the method is in an interface or annotation block, the scope is assumed
542     * to be public.
543     *
544     * @param ast the token of the method/constructor
545     * @return the scope of the method/constructor
546     */
547    private static Scope calculateScope(final DetailAST ast) {
548        final Scope scope;
549
550        if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
551            scope = Scope.PUBLIC;
552        }
553        else {
554            final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
555            scope = ScopeUtils.getScopeFromMods(mods);
556        }
557        return scope;
558    }
559
560    /**
561     * Returns the tags in a javadoc comment. Only finds throws, exception,
562     * param, return and see tags.
563     *
564     * @param comment the Javadoc comment
565     * @return the tags found
566     */
567    private static List<JavadocTag> getMethodTags(TextBlock comment) {
568        final String[] lines = comment.getText();
569        final List<JavadocTag> tags = new ArrayList<>();
570        int currentLine = comment.getStartLineNo() - 1;
571        final int startColumnNumber = comment.getStartColNo();
572
573        for (int i = 0; i < lines.length; i++) {
574            currentLine++;
575            final Matcher javadocArgMatcher =
576                MATCH_JAVADOC_ARG.matcher(lines[i]);
577            final Matcher javadocNoargMatcher =
578                MATCH_JAVADOC_NOARG.matcher(lines[i]);
579            final Matcher noargCurlyMatcher =
580                MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
581            final Matcher argMultilineStart =
582                MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]);
583            final Matcher noargMultilineStart =
584                MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
585
586            if (javadocArgMatcher.find()) {
587                final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
588                tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
589                        javadocArgMatcher.group(2)));
590            }
591            else if (javadocNoargMatcher.find()) {
592                final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
593                tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
594            }
595            else if (noargCurlyMatcher.find()) {
596                final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
597                tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
598            }
599            else if (argMultilineStart.find()) {
600                final int col = calculateTagColumn(argMultilineStart, i, startColumnNumber);
601                tags.addAll(getMultilineArgTags(argMultilineStart, col, lines, i, currentLine));
602            }
603            else if (noargMultilineStart.find()) {
604                tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
605            }
606        }
607        return tags;
608    }
609
610    /**
611     * Calculates column number using Javadoc tag matcher.
612     * @param javadocTagMatcher found javadoc tag matcher
613     * @param lineNumber line number of Javadoc tag in comment
614     * @param startColumnNumber column number of Javadoc comment beginning
615     * @return column number
616     */
617    private static int calculateTagColumn(Matcher javadocTagMatcher,
618            int lineNumber, int startColumnNumber) {
619        int col = javadocTagMatcher.start(1) - 1;
620        if (lineNumber == 0) {
621            col += startColumnNumber;
622        }
623        return col;
624    }
625
626    /**
627     * Gets multiline Javadoc tags with arguments.
628     * @param argMultilineStart javadoc tag Matcher
629     * @param column column number of Javadoc tag
630     * @param lines comment text lines
631     * @param lineIndex line number that contains the javadoc tag
632     * @param tagLine javadoc tag line number in file
633     * @return javadoc tags with arguments
634     */
635    private static List<JavadocTag> getMultilineArgTags(final Matcher argMultilineStart,
636            final int column, final String[] lines, final int lineIndex, final int tagLine) {
637        final List<JavadocTag> tags = new ArrayList<>();
638        final String param1 = argMultilineStart.group(1);
639        final String param2 = argMultilineStart.group(2);
640        int remIndex = lineIndex + 1;
641        while (remIndex < lines.length) {
642            final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
643            if (multilineCont.find()) {
644                remIndex = lines.length;
645                final String lFin = multilineCont.group(1);
646                if (!lFin.equals(NEXT_TAG)
647                    && !lFin.equals(END_JAVADOC)) {
648                    tags.add(new JavadocTag(tagLine, column, param1, param2));
649                }
650            }
651            remIndex++;
652        }
653        return tags;
654    }
655
656    /**
657     * Gets multiline Javadoc tags with no arguments.
658     * @param noargMultilineStart javadoc tag Matcher
659     * @param lines comment text lines
660     * @param lineIndex line number that contains the javadoc tag
661     * @param tagLine javadoc tag line number in file
662     * @return javadoc tags with no arguments
663     */
664    private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
665            final String[] lines, final int lineIndex, final int tagLine) {
666        final String param1 = noargMultilineStart.group(1);
667        final int col = noargMultilineStart.start(1) - 1;
668        final List<JavadocTag> tags = new ArrayList<>();
669        int remIndex = lineIndex + 1;
670        while (remIndex < lines.length) {
671            final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT
672                    .matcher(lines[remIndex]);
673            if (multilineCont.find()) {
674                remIndex = lines.length;
675                final String lFin = multilineCont.group(1);
676                if (!lFin.equals(NEXT_TAG)
677                    && !lFin.equals(END_JAVADOC)) {
678                    tags.add(new JavadocTag(tagLine, col, param1));
679                }
680            }
681            remIndex++;
682        }
683
684        return tags;
685    }
686
687    /**
688     * Computes the parameter nodes for a method.
689     *
690     * @param ast the method node.
691     * @return the list of parameter nodes for ast.
692     */
693    private static List<DetailAST> getParameters(DetailAST ast) {
694        final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
695        final List<DetailAST> returnValue = new ArrayList<>();
696
697        DetailAST child = params.getFirstChild();
698        while (child != null) {
699            if (child.getType() == TokenTypes.PARAMETER_DEF) {
700                final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
701                if (ident != null) {
702                    returnValue.add(ident);
703                }
704            }
705            child = child.getNextSibling();
706        }
707        return returnValue;
708    }
709
710    /**
711     * Computes the exception nodes for a method.
712     *
713     * @param ast the method node.
714     * @return the list of exception nodes for ast.
715     */
716    private List<ExceptionInfo> getThrows(DetailAST ast) {
717        final List<ExceptionInfo> returnValue = new ArrayList<>();
718        final DetailAST throwsAST = ast
719                .findFirstToken(TokenTypes.LITERAL_THROWS);
720        if (throwsAST != null) {
721            DetailAST child = throwsAST.getFirstChild();
722            while (child != null) {
723                if (child.getType() == TokenTypes.IDENT
724                        || child.getType() == TokenTypes.DOT) {
725                    final FullIdent ident = FullIdent.createFullIdent(child);
726                    final ExceptionInfo exceptionInfo = new ExceptionInfo(
727                            createClassInfo(new Token(ident), getCurrentClassName()));
728                    returnValue.add(exceptionInfo);
729                }
730                child = child.getNextSibling();
731            }
732        }
733        return returnValue;
734    }
735
736    /**
737     * Checks a set of tags for matching parameters.
738     *
739     * @param tags the tags to check
740     * @param parent the node which takes the parameters
741     * @param reportExpectedTags whether we should report if do not find
742     *            expected tag
743     */
744    private void checkParamTags(final List<JavadocTag> tags,
745            final DetailAST parent, boolean reportExpectedTags) {
746        final List<DetailAST> params = getParameters(parent);
747        final List<DetailAST> typeParams = CheckUtils
748                .getTypeParameters(parent);
749
750        // Loop over the tags, checking to see they exist in the params.
751        final ListIterator<JavadocTag> tagIt = tags.listIterator();
752        while (tagIt.hasNext()) {
753            final JavadocTag tag = tagIt.next();
754
755            if (!tag.isParamTag()) {
756                continue;
757            }
758
759            tagIt.remove();
760
761            final String arg1 = tag.getFirstArg();
762            boolean found = removeMatchingParam(params, arg1);
763
764            if (CommonUtils.startsWithChar(arg1, '<') && CommonUtils.endsWithChar(arg1, '>')) {
765                found = searchMatchingTypeParameter(typeParams,
766                        arg1.substring(1, arg1.length() - 1));
767            }
768
769            // Handle extra JavadocTag
770            if (!found) {
771                log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
772                        "@param", arg1);
773            }
774        }
775
776        // Now dump out all type parameters/parameters without tags :- unless
777        // the user has chosen to suppress these problems
778        if (!allowMissingParamTags && reportExpectedTags) {
779            for (DetailAST param : params) {
780                log(param, MSG_EXPECTED_TAG,
781                    JavadocTagInfo.PARAM.getText(), param.getText());
782            }
783
784            for (DetailAST typeParam : typeParams) {
785                log(typeParam, MSG_EXPECTED_TAG,
786                    JavadocTagInfo.PARAM.getText(),
787                    "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
788                    + ">");
789            }
790        }
791    }
792
793    /**
794     * Returns true if required type found in type parameters.
795     * @param typeParams
796     *            list of type parameters
797     * @param requiredTypeName
798     *            name of required type
799     * @return true if required type found in type parameters.
800     */
801    private static boolean searchMatchingTypeParameter(List<DetailAST> typeParams,
802            String requiredTypeName) {
803        // Loop looking for matching type param
804        final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
805        boolean found = false;
806        while (typeParamsIt.hasNext()) {
807            final DetailAST typeParam = typeParamsIt.next();
808            if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
809                    .equals(requiredTypeName)) {
810                found = true;
811                typeParamsIt.remove();
812                break;
813            }
814        }
815        return found;
816    }
817
818    /**
819     * Remove parameter from params collection by name.
820     * @param params collection of DetailAST parameters
821     * @param paramName name of parameter
822     * @return true if parameter found and removed
823     */
824    private static boolean removeMatchingParam(List<DetailAST> params, String paramName) {
825        boolean found = false;
826        final Iterator<DetailAST> paramIt = params.iterator();
827        while (paramIt.hasNext()) {
828            final DetailAST param = paramIt.next();
829            if (param.getText().equals(paramName)) {
830                found = true;
831                paramIt.remove();
832                break;
833            }
834        }
835        return found;
836    }
837
838    /**
839     * Checks for only one return tag. All return tags will be removed from the
840     * supplied list.
841     *
842     * @param tags the tags to check
843     * @param lineNo the line number of the expected tag
844     * @param reportExpectedTags whether we should report if do not find
845     *            expected tag
846     */
847    private void checkReturnTag(List<JavadocTag> tags, int lineNo,
848        boolean reportExpectedTags) {
849        // Loop over tags finding return tags. After the first one, report an
850        // error.
851        boolean found = false;
852        final ListIterator<JavadocTag> it = tags.listIterator();
853        while (it.hasNext()) {
854            final JavadocTag javadocTag = it.next();
855            if (javadocTag.isReturnTag()) {
856                if (found) {
857                    log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
858                            MSG_DUPLICATE_TAG,
859                            JavadocTagInfo.RETURN.getText());
860                }
861                found = true;
862                it.remove();
863            }
864        }
865
866        // Handle there being no @return tags :- unless
867        // the user has chosen to suppress these problems
868        if (!found && !allowMissingReturnTag && reportExpectedTags) {
869            log(lineNo, MSG_RETURN_EXPECTED);
870        }
871    }
872
873    /**
874     * Checks a set of tags for matching throws.
875     *
876     * @param tags the tags to check
877     * @param throwsList the throws to check
878     * @param reportExpectedTags whether we should report if do not find
879     *            expected tag
880     */
881    private void checkThrowsTags(List<JavadocTag> tags,
882            List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
883        // Loop over the tags, checking to see they exist in the throws.
884        // The foundThrows used for performance only
885        final Set<String> foundThrows = new HashSet<>();
886        final ListIterator<JavadocTag> tagIt = tags.listIterator();
887        while (tagIt.hasNext()) {
888            final JavadocTag tag = tagIt.next();
889
890            if (!tag.isThrowsTag()) {
891                continue;
892            }
893            tagIt.remove();
894
895            // Loop looking for matching throw
896            final String documentedEx = tag.getFirstArg();
897            final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
898                    .getColumnNo());
899            final AbstractClassInfo documentedClassInfo = createClassInfo(token,
900                    getCurrentClassName());
901            final boolean found = foundThrows.contains(documentedEx)
902                    || isInThrows(throwsList, documentedClassInfo, foundThrows);
903
904            // Handle extra JavadocTag.
905            if (!found) {
906                boolean reqd = true;
907                if (allowUndeclaredRTE) {
908                    reqd = !isUnchecked(documentedClassInfo.getClazz());
909                }
910
911                if (reqd && validateThrows) {
912                    log(tag.getLineNo(), tag.getColumnNo(),
913                        MSG_UNUSED_TAG,
914                        JavadocTagInfo.THROWS.getText(), tag.getFirstArg());
915                }
916            }
917        }
918        // Now dump out all throws without tags :- unless
919        // the user has chosen to suppress these problems
920        if (!allowMissingThrowsTags && reportExpectedTags) {
921            throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
922                .forEach(exceptionInfo -> {
923                    final Token token = exceptionInfo.getName();
924                    log(token.getLineNo(), token.getColumnNo(),
925                        MSG_EXPECTED_TAG,
926                        JavadocTagInfo.THROWS.getText(), token.getText());
927                });
928        }
929    }
930
931    /**
932     * Verifies that documented exception is in throws.
933     *
934     * @param throwsList list of throws
935     * @param documentedClassInfo documented exception class info
936     * @param foundThrows previously found throws
937     * @return true if documented exception is in throws.
938     */
939    private boolean isInThrows(List<ExceptionInfo> throwsList,
940            AbstractClassInfo documentedClassInfo, Set<String> foundThrows) {
941        boolean found = false;
942        ExceptionInfo foundException = null;
943
944        // First look for matches on the exception name
945        for (ExceptionInfo exceptionInfo : throwsList) {
946            if (exceptionInfo.getName().getText().equals(
947                    documentedClassInfo.getName().getText())) {
948                found = true;
949                foundException = exceptionInfo;
950                break;
951            }
952        }
953
954        // Now match on the exception type
955        final ListIterator<ExceptionInfo> exceptionInfoIt = throwsList.listIterator();
956        while (!found && exceptionInfoIt.hasNext()) {
957            final ExceptionInfo exceptionInfo = exceptionInfoIt.next();
958
959            if (documentedClassInfo.getClazz() == exceptionInfo.getClazz()) {
960                found = true;
961                foundException = exceptionInfo;
962            }
963            else if (allowThrowsTagsForSubclasses) {
964                found = isSubclass(documentedClassInfo.getClazz(), exceptionInfo.getClazz());
965            }
966        }
967
968        if (foundException != null) {
969            foundException.setFound();
970            foundThrows.add(documentedClassInfo.getName().getText());
971        }
972
973        return found;
974    }
975
976    /** Stores useful information about declared exception. */
977    private static class ExceptionInfo {
978
979        /** Class information associated with this exception. */
980        private final AbstractClassInfo classInfo;
981        /** Does the exception have throws tag associated with. */
982        private boolean found;
983
984        /**
985         * Creates new instance for {@code FullIdent}.
986         *
987         * @param classInfo class info
988         */
989        ExceptionInfo(AbstractClassInfo classInfo) {
990            this.classInfo = classInfo;
991        }
992
993        /** Mark that the exception has associated throws tag. */
994        private void setFound() {
995            found = true;
996        }
997
998        /**
999         * Checks that the exception has throws tag associated with it.
1000         * @return whether the exception has throws tag associated with
1001         */
1002        private boolean isFound() {
1003            return found;
1004        }
1005
1006        /**
1007         * Gets exception name.
1008         * @return exception's name
1009         */
1010        private Token getName() {
1011            return classInfo.getName();
1012        }
1013
1014        /**
1015         * Gets exception class.
1016         * @return class for this exception
1017         */
1018        private Class<?> getClazz() {
1019            return classInfo.getClazz();
1020        }
1021
1022    }
1023
1024}