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