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