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