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