001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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        if ((ast.getType() == TokenTypes.METHOD_DEF || ast.getType() == TokenTypes.CTOR_DEF)
350            && getMethodsNumberOfLine(ast) <= minLineCount
351            || hasAllowedAnnotations(ast)) {
352            return;
353        }
354        final Scope theScope = calculateScope(ast);
355        if (shouldCheck(ast, theScope)) {
356            final FileContents contents = getFileContents();
357            final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
358
359            if (textBlock == null) {
360                if (!isMissingJavadocAllowed(ast)) {
361                    log(ast, MSG_JAVADOC_MISSING);
362                }
363            }
364            else {
365                checkComment(ast, textBlock);
366            }
367        }
368    }
369
370    /**
371     * Some javadoc.
372     * @param methodDef Some javadoc.
373     * @return Some javadoc.
374     */
375    private boolean hasAllowedAnnotations(DetailAST methodDef) {
376        final DetailAST modifiersNode = methodDef.findFirstToken(TokenTypes.MODIFIERS);
377        DetailAST annotationNode = modifiersNode.findFirstToken(TokenTypes.ANNOTATION);
378        while (annotationNode != null && annotationNode.getType() == TokenTypes.ANNOTATION) {
379            DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT);
380            if (identNode == null) {
381                identNode = annotationNode.findFirstToken(TokenTypes.DOT)
382                    .findFirstToken(TokenTypes.IDENT);
383            }
384            if (allowedAnnotations.contains(identNode.getText())) {
385                return true;
386            }
387            annotationNode = annotationNode.getNextSibling();
388        }
389        return false;
390    }
391
392    /**
393     * Some javadoc.
394     * @param methodDef Some javadoc.
395     * @return Some javadoc.
396     */
397    private static int getMethodsNumberOfLine(DetailAST methodDef) {
398        int numberOfLines;
399        final DetailAST lcurly = methodDef.getLastChild();
400        final DetailAST rcurly = lcurly.getLastChild();
401
402        if (lcurly.getFirstChild() == rcurly) {
403            numberOfLines = 1;
404        }
405        else {
406            numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
407        }
408        return numberOfLines;
409    }
410
411    @Override
412    protected final void logLoadError(Token ident) {
413        logLoadErrorImpl(ident.getLineNo(), ident.getColumnNo(),
414            MSG_CLASS_INFO,
415            JavadocTagInfo.THROWS.getText(), ident.getText());
416    }
417
418    /**
419     * The JavadocMethodCheck is about to report a missing Javadoc.
420     * This hook can be used by derived classes to allow a missing javadoc
421     * in some situations.  The default implementation checks
422     * {@code allowMissingJavadoc} and
423     * {@code allowMissingPropertyJavadoc} properties, do not forget
424     * to call {@code super.isMissingJavadocAllowed(ast)} in case
425     * you want to keep this logic.
426     * @param ast the tree node for the method or constructor.
427     * @return True if this method or constructor doesn't need Javadoc.
428     */
429    protected boolean isMissingJavadocAllowed(final DetailAST ast) {
430        return allowMissingJavadoc
431            || allowMissingPropertyJavadoc
432                && (CheckUtils.isSetterMethod(ast) || CheckUtils.isGetterMethod(ast))
433            || matchesSkipRegex(ast);
434    }
435
436    /**
437     * Checks if the given method name matches the regex. In that case
438     * we skip enforcement of javadoc for this method
439     * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
440     * @return true if given method name matches the regex.
441     */
442    private boolean matchesSkipRegex(DetailAST methodDef) {
443        if (ignoreMethodNamesRegex != null) {
444            final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
445            final String methodName = ident.getText();
446
447            final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
448            if (matcher.matches()) {
449                return true;
450            }
451        }
452        return false;
453    }
454
455    /**
456     * Whether we should check this node.
457     *
458     * @param ast a given node.
459     * @param nodeScope the scope of the node.
460     * @return whether we should check a given node.
461     */
462    private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
463        final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
464
465        return nodeScope.isIn(scope)
466                && surroundingScope.isIn(scope)
467                && (excludeScope == null || nodeScope != excludeScope
468                    && surroundingScope != excludeScope);
469    }
470
471    /**
472     * Checks the Javadoc for a method.
473     *
474     * @param ast the token for the method
475     * @param comment the Javadoc comment
476     */
477    private void checkComment(DetailAST ast, TextBlock comment) {
478        final List<JavadocTag> tags = getMethodTags(comment);
479
480        if (hasShortCircuitTag(ast, tags)) {
481            return;
482        }
483
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 (it.hasNext() && !hasInheritDocTag) {
492                hasInheritDocTag = it.next().isInheritDocTag();
493            }
494
495            checkParamTags(tags, ast, !hasInheritDocTag);
496            checkThrowsTags(tags, getThrows(ast), !hasInheritDocTag);
497            if (CheckUtils.isNonVoidMethod(ast)) {
498                checkReturnTag(tags, ast.getLineNo(), !hasInheritDocTag);
499            }
500        }
501
502        // Dump out all unused tags
503        for (JavadocTag javadocTag : tags) {
504            if (!javadocTag.isSeeOrInheritDocTag()) {
505                log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL);
506            }
507        }
508    }
509
510    /**
511     * Validates whether the Javadoc has a short circuit tag. Currently this is
512     * the inheritTag. Any errors are logged.
513     *
514     * @param ast the construct being checked
515     * @param tags the list of Javadoc tags associated with the construct
516     * @return true if the construct has a short circuit tag.
517     */
518    private boolean hasShortCircuitTag(final DetailAST ast,
519            final List<JavadocTag> tags) {
520        // Check if it contains {@inheritDoc} tag
521        if (tags.size() != 1
522                || !tags.get(0).isInheritDocTag()) {
523            return false;
524        }
525
526        // Invalid if private, a constructor, or a static method
527        if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
528            log(ast, MSG_INVALID_INHERIT_DOC);
529        }
530
531        return true;
532    }
533
534    /**
535     * Returns the scope for the method/constructor at the specified AST. If
536     * the method is in an interface or annotation block, the scope is assumed
537     * to be public.
538     *
539     * @param ast the token of the method/constructor
540     * @return the scope of the method/constructor
541     */
542    private static Scope calculateScope(final DetailAST ast) {
543        final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
544        final Scope declaredScope = ScopeUtils.getScopeFromMods(mods);
545
546        if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
547            return Scope.PUBLIC;
548        }
549        else {
550            return declaredScope;
551        }
552    }
553
554    /**
555     * Returns the tags in a javadoc comment. Only finds throws, exception,
556     * param, return and see tags.
557     *
558     * @param comment the Javadoc comment
559     * @return the tags found
560     */
561    private static List<JavadocTag> getMethodTags(TextBlock comment) {
562        final String[] lines = comment.getText();
563        final List<JavadocTag> tags = Lists.newArrayList();
564        int currentLine = comment.getStartLineNo() - 1;
565        final int startColumnNumber = comment.getStartColNo();
566
567        for (int i = 0; i < lines.length; i++) {
568            currentLine++;
569            final Matcher javadocArgMatcher =
570                MATCH_JAVADOC_ARG.matcher(lines[i]);
571            final Matcher javadocNoargMatcher =
572                MATCH_JAVADOC_NOARG.matcher(lines[i]);
573            final Matcher noargCurlyMatcher =
574                MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
575            final Matcher argMultilineStart =
576                MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]);
577            final Matcher noargMultilineStart =
578                MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
579
580            if (javadocArgMatcher.find()) {
581                final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
582                tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
583                        javadocArgMatcher.group(2)));
584            }
585            else if (javadocNoargMatcher.find()) {
586                final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
587                tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
588            }
589            else if (noargCurlyMatcher.find()) {
590                final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
591                tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
592            }
593            else if (argMultilineStart.find()) {
594                final int col = calculateTagColumn(argMultilineStart, i, startColumnNumber);
595                tags.addAll(getMultilineArgTags(argMultilineStart, col, lines, i, currentLine));
596            }
597            else if (noargMultilineStart.find()) {
598                tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
599            }
600        }
601        return tags;
602    }
603
604    /**
605     * Calculates column number using Javadoc tag matcher.
606     * @param javadocTagMatcher found javadoc tag matcher
607     * @param lineNumber line number of Javadoc tag in comment
608     * @param startColumnNumber column number of Javadoc comment beginning
609     * @return column number
610     */
611    private static int calculateTagColumn(Matcher javadocTagMatcher,
612            int lineNumber, int startColumnNumber) {
613        int col = javadocTagMatcher.start(1) - 1;
614        if (lineNumber == 0) {
615            col += startColumnNumber;
616        }
617        return col;
618    }
619
620    /**
621     * Gets multiline Javadoc tags with arguments.
622     * @param argMultilineStart javadoc tag Matcher
623     * @param column column number of Javadoc tag
624     * @param lines comment text lines
625     * @param lineIndex line number that contains the javadoc tag
626     * @param tagLine javadoc tag line number in file
627     * @return javadoc tags with arguments
628     */
629    private static List<JavadocTag> getMultilineArgTags(final Matcher argMultilineStart,
630            final int column, final String[] lines, final int lineIndex, final int tagLine) {
631        final List<JavadocTag> tags = new ArrayList<>();
632        final String param1 = argMultilineStart.group(1);
633        final String param2 = argMultilineStart.group(2);
634        int remIndex = lineIndex + 1;
635        while (remIndex < lines.length) {
636            final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
637            if (multilineCont.find()) {
638                remIndex = lines.length;
639                final String lFin = multilineCont.group(1);
640                if (!lFin.equals(NEXT_TAG)
641                    && !lFin.equals(END_JAVADOC)) {
642                    tags.add(new JavadocTag(tagLine, column, param1, param2));
643                }
644            }
645            remIndex++;
646        }
647        return tags;
648    }
649
650    /**
651     * Gets multiline Javadoc tags with no arguments.
652     * @param noargMultilineStart javadoc tag Matcher
653     * @param lines comment text lines
654     * @param lineIndex line number that contains the javadoc tag
655     * @param tagLine javadoc tag line number in file
656     * @return javadoc tags with no arguments
657     */
658    private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
659            final String[] lines, final int lineIndex, final int tagLine) {
660        final String param1 = noargMultilineStart.group(1);
661        final int col = noargMultilineStart.start(1) - 1;
662        final List<JavadocTag> tags = new ArrayList<>();
663        int remIndex = lineIndex + 1;
664        while (remIndex < lines.length) {
665            final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT
666                    .matcher(lines[remIndex]);
667            if (multilineCont.find()) {
668                remIndex = lines.length;
669                final String lFin = multilineCont.group(1);
670                if (!lFin.equals(NEXT_TAG)
671                    && !lFin.equals(END_JAVADOC)) {
672                    tags.add(new JavadocTag(tagLine, col, param1));
673                }
674            }
675            remIndex++;
676        }
677
678        return tags;
679    }
680
681    /**
682     * Computes the parameter nodes for a method.
683     *
684     * @param ast the method node.
685     * @return the list of parameter nodes for ast.
686     */
687    private static List<DetailAST> getParameters(DetailAST ast) {
688        final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
689        final List<DetailAST> returnValue = Lists.newArrayList();
690
691        DetailAST child = params.getFirstChild();
692        while (child != null) {
693            if (child.getType() == TokenTypes.PARAMETER_DEF) {
694                final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
695                returnValue.add(ident);
696            }
697            child = child.getNextSibling();
698        }
699        return returnValue;
700    }
701
702    /**
703     * Computes the exception nodes for a method.
704     *
705     * @param ast the method node.
706     * @return the list of exception nodes for ast.
707     */
708    private List<ExceptionInfo> getThrows(DetailAST ast) {
709        final List<ExceptionInfo> returnValue = Lists.newArrayList();
710        final DetailAST throwsAST = ast
711                .findFirstToken(TokenTypes.LITERAL_THROWS);
712        if (throwsAST != null) {
713            DetailAST child = throwsAST.getFirstChild();
714            while (child != null) {
715                if (child.getType() == TokenTypes.IDENT
716                        || child.getType() == TokenTypes.DOT) {
717                    final FullIdent ident = FullIdent.createFullIdent(child);
718                    final ExceptionInfo exceptionInfo = new ExceptionInfo(
719                            createClassInfo(new Token(ident), getCurrentClassName()));
720                    returnValue.add(exceptionInfo);
721                }
722                child = child.getNextSibling();
723            }
724        }
725        return returnValue;
726    }
727
728    /**
729     * Checks a set of tags for matching parameters.
730     *
731     * @param tags the tags to check
732     * @param parent the node which takes the parameters
733     * @param reportExpectedTags whether we should report if do not find
734     *            expected tag
735     */
736    private void checkParamTags(final List<JavadocTag> tags,
737            final DetailAST parent, boolean reportExpectedTags) {
738        final List<DetailAST> params = getParameters(parent);
739        final List<DetailAST> typeParams = CheckUtils
740                .getTypeParameters(parent);
741
742        // Loop over the tags, checking to see they exist in the params.
743        final ListIterator<JavadocTag> tagIt = tags.listIterator();
744        while (tagIt.hasNext()) {
745            final JavadocTag tag = tagIt.next();
746
747            if (!tag.isParamTag()) {
748                continue;
749            }
750
751            tagIt.remove();
752
753            final String arg1 = tag.getFirstArg();
754            boolean found = removeMatchingParam(params, arg1);
755
756            if (CommonUtils.startsWithChar(arg1, '<') && CommonUtils.endsWithChar(arg1, '>')) {
757                found = searchMatchingTypeParameter(typeParams,
758                        arg1.substring(1, arg1.length() - 1));
759
760            }
761
762            // Handle extra JavadocTag
763            if (!found) {
764                log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
765                        "@param", arg1);
766            }
767        }
768
769        // Now dump out all type parameters/parameters without tags :- unless
770        // the user has chosen to suppress these problems
771        if (!allowMissingParamTags && reportExpectedTags) {
772            for (DetailAST param : params) {
773                log(param, MSG_EXPECTED_TAG,
774                    JavadocTagInfo.PARAM.getText(), param.getText());
775            }
776
777            for (DetailAST typeParam : typeParams) {
778                log(typeParam, MSG_EXPECTED_TAG,
779                    JavadocTagInfo.PARAM.getText(),
780                    "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
781                    + ">");
782            }
783        }
784    }
785
786    /**
787     * Returns true if required type found in type parameters.
788     * @param typeParams
789     *            list of type parameters
790     * @param requiredTypeName
791     *            name of required type
792     * @return true if required type found in type parameters.
793     */
794    private static boolean searchMatchingTypeParameter(List<DetailAST> typeParams,
795            String requiredTypeName) {
796        // Loop looking for matching type param
797        final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
798        boolean found = false;
799        while (typeParamsIt.hasNext()) {
800            final DetailAST typeParam = typeParamsIt.next();
801            if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
802                    .equals(requiredTypeName)) {
803                found = true;
804                typeParamsIt.remove();
805                break;
806            }
807        }
808        return found;
809    }
810
811    /**
812     * Remove parameter from params collection by name.
813     * @param params collection of DetailAST parameters
814     * @param paramName name of parameter
815     * @return true if parameter found and removed
816     */
817    private static boolean removeMatchingParam(List<DetailAST> params, String paramName) {
818        boolean found = false;
819        final Iterator<DetailAST> paramIt = params.iterator();
820        while (paramIt.hasNext()) {
821            final DetailAST param = paramIt.next();
822            if (param.getText().equals(paramName)) {
823                found = true;
824                paramIt.remove();
825                break;
826            }
827        }
828        return found;
829    }
830
831    /**
832     * Checks for only one return tag. All return tags will be removed from the
833     * supplied list.
834     *
835     * @param tags the tags to check
836     * @param lineNo the line number of the expected tag
837     * @param reportExpectedTags whether we should report if do not find
838     *            expected tag
839     */
840    private void checkReturnTag(List<JavadocTag> tags, int lineNo,
841        boolean reportExpectedTags) {
842        // Loop over tags finding return tags. After the first one, report an
843        // error.
844        boolean found = false;
845        final ListIterator<JavadocTag> it = tags.listIterator();
846        while (it.hasNext()) {
847            final JavadocTag javadocTag = it.next();
848            if (javadocTag.isReturnTag()) {
849                if (found) {
850                    log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
851                            MSG_DUPLICATE_TAG,
852                            JavadocTagInfo.RETURN.getText());
853                }
854                found = true;
855                it.remove();
856            }
857        }
858
859        // Handle there being no @return tags :- unless
860        // the user has chosen to suppress these problems
861        if (!found && !allowMissingReturnTag && reportExpectedTags) {
862            log(lineNo, MSG_RETURN_EXPECTED);
863        }
864    }
865
866    /**
867     * Checks a set of tags for matching throws.
868     *
869     * @param tags the tags to check
870     * @param throwsList the throws to check
871     * @param reportExpectedTags whether we should report if do not find
872     *            expected tag
873     */
874    private void checkThrowsTags(List<JavadocTag> tags,
875            List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
876        // Loop over the tags, checking to see they exist in the throws.
877        // The foundThrows used for performance only
878        final Set<String> foundThrows = Sets.newHashSet();
879        final ListIterator<JavadocTag> tagIt = tags.listIterator();
880        while (tagIt.hasNext()) {
881            final JavadocTag tag = tagIt.next();
882
883            if (!tag.isThrowsTag()) {
884                continue;
885            }
886            tagIt.remove();
887
888            // Loop looking for matching throw
889            final String documentedEx = tag.getFirstArg();
890            final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
891                    .getColumnNo());
892            final AbstractClassInfo documentedClassInfo = createClassInfo(token,
893                    getCurrentClassName());
894            final boolean found = foundThrows.contains(documentedEx)
895                    || isInThrows(throwsList, documentedClassInfo, foundThrows);
896
897            // Handle extra JavadocTag.
898            if (!found) {
899                boolean reqd = true;
900                if (allowUndeclaredRTE) {
901                    reqd = !isUnchecked(documentedClassInfo.getClazz());
902                }
903
904                if (reqd && validateThrows) {
905                    log(tag.getLineNo(), tag.getColumnNo(),
906                        MSG_UNUSED_TAG,
907                        JavadocTagInfo.THROWS.getText(), tag.getFirstArg());
908
909                }
910            }
911        }
912        // Now dump out all throws without tags :- unless
913        // the user has chosen to suppress these problems
914        if (!allowMissingThrowsTags && reportExpectedTags) {
915            for (ExceptionInfo ei : throwsList) {
916                if (!ei.isFound()) {
917                    final Token token = ei.getName();
918                    log(token.getLineNo(), token.getColumnNo(),
919                            MSG_EXPECTED_TAG,
920                            JavadocTagInfo.THROWS.getText(), token.getText());
921                }
922            }
923        }
924    }
925
926    /**
927     * Verifies that documented exception is in throws.
928     *
929     * @param throwsList list of throws
930     * @param documentedClassInfo documented exception class info
931     * @param foundThrows previously found throws
932     * @return true if documented exception is in throws.
933     */
934    private boolean isInThrows(List<ExceptionInfo> throwsList,
935            AbstractClassInfo documentedClassInfo, Set<String> foundThrows) {
936        boolean found = false;
937        ExceptionInfo foundException = null;
938
939        // First look for matches on the exception name
940        final ListIterator<ExceptionInfo> throwIt = throwsList.listIterator();
941        while (!found && throwIt.hasNext()) {
942            final ExceptionInfo exceptionInfo = throwIt.next();
943
944            if (exceptionInfo.getName().getText().equals(
945                    documentedClassInfo.getName().getText())) {
946                found = true;
947                foundException = exceptionInfo;
948            }
949        }
950
951        // Now match on the exception type
952        final ListIterator<ExceptionInfo> exceptionInfoIt = throwsList.listIterator();
953        while (!found && exceptionInfoIt.hasNext()) {
954            final ExceptionInfo exceptionInfo = exceptionInfoIt.next();
955
956            if (documentedClassInfo.getClazz() == exceptionInfo.getClazz()) {
957                found = true;
958                foundException = exceptionInfo;
959            }
960            else if (allowThrowsTagsForSubclasses) {
961                found = isSubclass(documentedClassInfo.getClazz(), exceptionInfo.getClazz());
962            }
963        }
964
965        if (foundException != null) {
966            foundException.setFound();
967            foundThrows.add(documentedClassInfo.getName().getText());
968        }
969
970        return found;
971    }
972
973    /** Stores useful information about declared exception. */
974    private static class ExceptionInfo {
975        /** Does the exception have throws tag associated with. */
976        private boolean found;
977        /** Class information associated with this exception. */
978        private final AbstractClassInfo classInfo;
979
980        /**
981         * Creates new instance for {@code FullIdent}.
982         *
983         * @param classInfo class info
984         */
985        ExceptionInfo(AbstractClassInfo classInfo) {
986            this.classInfo = classInfo;
987        }
988
989        /** Mark that the exception has associated throws tag. */
990        private void setFound() {
991            found = true;
992        }
993
994        /**
995         * Checks that the exception has throws tag associated with it.
996         * @return whether the exception has throws tag associated with
997         */
998        private boolean isFound() {
999            return found;
1000        }
1001
1002        /**
1003         * Gets exception name.
1004         * @return exception's name
1005         */
1006        private Token getName() {
1007            return classInfo.getName();
1008        }
1009
1010        /**
1011         * Gets exception class.
1012         * @return class for this exception
1013         */
1014        private Class<?> getClazz() {
1015            return classInfo.getClazz();
1016        }
1017    }
1018}