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