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