Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
JavadocMethodCheck
95%
279/292
89%
212/236
4.879
JavadocMethodCheck$ExceptionInfo
100%
8/8
N/A
4.879
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 package com.puppycrawl.tools.checkstyle.checks.javadoc;
 20  
 
 21  
 import antlr.collections.AST;
 22  
 import com.google.common.collect.Lists;
 23  
 import com.google.common.collect.Sets;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 26  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 27  
 import com.puppycrawl.tools.checkstyle.api.JavadocTagInfo;
 28  
 import com.puppycrawl.tools.checkstyle.api.Scope;
 29  
 import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
 30  
 import com.puppycrawl.tools.checkstyle.api.TextBlock;
 31  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 32  
 import com.puppycrawl.tools.checkstyle.api.Utils;
 33  
 import com.puppycrawl.tools.checkstyle.checks.AbstractTypeAwareCheck;
 34  
 import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
 35  
 import java.util.Iterator;
 36  
 import java.util.List;
 37  
 import java.util.ListIterator;
 38  
 import java.util.Set;
 39  
 import java.util.regex.Matcher;
 40  
 import java.util.regex.Pattern;
 41  
 
 42  
 /**
 43  
  * Checks the Javadoc of a method or constructor.
 44  
  *
 45  
  * @author Oliver Burn
 46  
  * @author Rick Giles
 47  
  * @author o_sukhodoslky
 48  
  */
 49  104
 public class JavadocMethodCheck extends AbstractTypeAwareCheck
 50  
 {
 51  
     /** compiled regexp to match Javadoc tags that take an argument * */
 52  1
     private static final Pattern MATCH_JAVADOC_ARG =
 53  
         Utils.createPattern("@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
 54  
 
 55  
     /** compiled regexp to match first part of multilineJavadoc tags * */
 56  1
     private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START =
 57  
         Utils.createPattern("@(throws|exception|param)\\s+(\\S+)\\s*$");
 58  
 
 59  
     /** compiled regexp to look for a continuation of the comment * */
 60  1
     private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
 61  
         Utils.createPattern("(\\*/|@|[^\\s\\*])");
 62  
 
 63  
     /** Multiline finished at end of comment * */
 64  
     private static final String END_JAVADOC = "*/";
 65  
     /** Multiline finished at next Javadoc * */
 66  
     private static final String NEXT_TAG = "@";
 67  
 
 68  
     /** compiled regexp to match Javadoc tags with no argument * */
 69  1
     private static final Pattern MATCH_JAVADOC_NOARG =
 70  
         Utils.createPattern("@(return|see)\\s+\\S");
 71  
     /** compiled regexp to match first part of multilineJavadoc tags * */
 72  1
     private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
 73  
         Utils.createPattern("@(return|see)\\s*$");
 74  
     /** compiled regexp to match Javadoc tags with no argument and {} * */
 75  1
     private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
 76  
         Utils.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
 77  
 
 78  
     /** Maximum children allowed * */
 79  
     private static final int MAX_CHILDREN = 7;
 80  
 
 81  
     /** Maximum children allowed * */
 82  
     private static final int BODY_SIZE = 3;
 83  
 
 84  
     /** the visibility scope where Javadoc comments are checked * */
 85  25
     private Scope mScope = Scope.PRIVATE;
 86  
 
 87  
     /** the visibility scope where Javadoc comments shouldn't be checked * */
 88  
     private Scope mExcludeScope;
 89  
 
 90  
     /**
 91  
      * controls whether to allow documented exceptions that are not declared if
 92  
      * they are a subclass of java.lang.RuntimeException.
 93  
      */
 94  
     private boolean mAllowUndeclaredRTE;
 95  
 
 96  
     /**
 97  
      * controls whether to allow documented exceptions that are subclass of one
 98  
      * of declared exception. Defaults to false (backward compatibility).
 99  
      */
 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  7
         mScope = Scope.getInstance(aFrom);
 141  7
     }
 142  
 
 143  
     /**
 144  
      * Set the excludeScope.
 145  
      *
 146  
      * @param aScope a <code>String</code> value
 147  
      */
 148  
     public void setExcludeScope(String aScope)
 149  
     {
 150  1
         mExcludeScope = Scope.getInstance(aScope);
 151  1
     }
 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  5
         mAllowUndeclaredRTE = aFlag;
 162  5
     }
 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  5
         mAllowThrowsTagsForSubclasses = aFlag;
 173  5
     }
 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  0
         mAllowMissingParamTags = aFlag;
 184  0
     }
 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  0
         mAllowMissingThrowsTags = aFlag;
 196  0
     }
 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  0
         mAllowMissingReturnTag = aFlag;
 207  0
     }
 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  1
         mAllowMissingJavadoc = aFlag;
 218  1
     }
 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  1
         mAllowMissingPropertyJavadoc = aFlag;
 229  1
     }
 230  
 
 231  
     @Override
 232  
     public int[] getDefaultTokens()
 233  
     {
 234  25
         return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
 235  
                           TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF,
 236  
                           TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF,
 237  
                           TokenTypes.ANNOTATION_FIELD_DEF,
 238  
         };
 239  
     }
 240  
 
 241  
     @Override
 242  
     public int[] getAcceptableTokens()
 243  
     {
 244  0
         return new int[] {TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF,
 245  
                           TokenTypes.ANNOTATION_FIELD_DEF,
 246  
         };
 247  
     }
 248  
 
 249  
     @Override
 250  
     protected final void processAST(DetailAST aAST)
 251  
     {
 252  390
         final Scope theScope = calculateScope(aAST);
 253  390
         if (shouldCheck(aAST, theScope)) {
 254  312
             final FileContents contents = getFileContents();
 255  312
             final TextBlock cmt = contents.getJavadocBefore(aAST.getLineNo());
 256  
 
 257  312
             if (cmt == null) {
 258  158
                 if (!isMissingJavadocAllowed(aAST)) {
 259  117
                     log(aAST, "javadoc.missing");
 260  
                 }
 261  
             }
 262  
             else {
 263  154
                 checkComment(aAST, cmt);
 264  
             }
 265  
         }
 266  390
     }
 267  
 
 268  
     @Override
 269  
     protected final void logLoadError(Token aIdent)
 270  
     {
 271  0
         logLoadErrorImpl(aIdent.getLineNo(), aIdent.getColumnNo(),
 272  
             "javadoc.classInfo",
 273  
             JavadocTagInfo.THROWS.getText(), aIdent.getText());
 274  0
     }
 275  
 
 276  
     /**
 277  
      * The JavadocMethodCheck is about to report a missing Javadoc.
 278  
      * This hook can be used by derived classes to allow a missing javadoc
 279  
      * in some situations.  The default implementation checks
 280  
      * <code>allowMissingJavadoc</code> and
 281  
      * <code>allowMissingPropertyJavadoc</code> properties, do not forget
 282  
      * to call <code>super.isMissingJavadocAllowed(aAST)</code> in case
 283  
      * you want to keep this logic.
 284  
      * @param aAST the tree node for the method or constructor.
 285  
      * @return True if this method or constructor doesn't need Javadoc.
 286  
      */
 287  
     protected boolean isMissingJavadocAllowed(final DetailAST aAST)
 288  
     {
 289  158
         return mAllowMissingJavadoc || isOverrideMethod(aAST)
 290  
             || (mAllowMissingPropertyJavadoc
 291  
                 && (isSetterMethod(aAST) || isGetterMethod(aAST)));
 292  
     }
 293  
 
 294  
     /**
 295  
      * Whether we should check this node.
 296  
      *
 297  
      * @param aAST a given node.
 298  
      * @param aScope the scope of the node.
 299  
      * @return whether we should check a given node.
 300  
      */
 301  
     private boolean shouldCheck(final DetailAST aAST, final Scope aScope)
 302  
     {
 303  390
         final Scope surroundingScope = ScopeUtils.getSurroundingScope(aAST);
 304  
 
 305  390
         return aScope.isIn(mScope)
 306  
                 && surroundingScope.isIn(mScope)
 307  
                 && ((mExcludeScope == null) || !aScope.isIn(mExcludeScope)
 308  
                     || !surroundingScope.isIn(mExcludeScope));
 309  
     }
 310  
 
 311  
     /**
 312  
      * Checks the Javadoc for a method.
 313  
      *
 314  
      * @param aAST the token for the method
 315  
      * @param aComment the Javadoc comment
 316  
      */
 317  
     private void checkComment(DetailAST aAST, TextBlock aComment)
 318  
     {
 319  154
         final List<JavadocTag> tags = getMethodTags(aComment);
 320  
 
 321  154
         if (hasShortCircuitTag(aAST, tags)) {
 322  17
             return;
 323  
         }
 324  
 
 325  137
         Iterator<JavadocTag> it = tags.iterator();
 326  137
         if (aAST.getType() != TokenTypes.ANNOTATION_FIELD_DEF) {
 327  
             // Check for inheritDoc
 328  134
             boolean hasInheritDocTag = false;
 329  301
             while (it.hasNext() && !hasInheritDocTag) {
 330  167
                 hasInheritDocTag |= (it.next()).isInheritDocTag();
 331  
             }
 332  
 
 333  134
             checkParamTags(tags, aAST, !hasInheritDocTag);
 334  134
             checkThrowsTags(tags, getThrows(aAST), !hasInheritDocTag);
 335  134
             if (isFunction(aAST)) {
 336  25
                 checkReturnTag(tags, aAST.getLineNo(), !hasInheritDocTag);
 337  
             }
 338  
         }
 339  
 
 340  
         // Dump out all unused tags
 341  137
         it = tags.iterator();
 342  164
         while (it.hasNext()) {
 343  27
             final JavadocTag jt = it.next();
 344  27
             if (!jt.isSeeOrInheritDocTag()) {
 345  6
                 log(jt.getLineNo(), "javadoc.unusedTagGeneral");
 346  
             }
 347  27
         }
 348  137
     }
 349  
 
 350  
     /**
 351  
      * Validates whether the Javadoc has a short circuit tag. Currently this is
 352  
      * the inheritTag. Any errors are logged.
 353  
      *
 354  
      * @param aAST the construct being checked
 355  
      * @param aTags the list of Javadoc tags associated with the construct
 356  
      * @return true if the construct has a short circuit tag.
 357  
      */
 358  
     private boolean hasShortCircuitTag(final DetailAST aAST,
 359  
             final List<JavadocTag> aTags)
 360  
     {
 361  
         // Check if it contains {@inheritDoc} tag
 362  154
         if ((aTags.size() != 1)
 363  
                 || !(aTags.get(0)).isInheritDocTag())
 364  
         {
 365  137
             return false;
 366  
         }
 367  
 
 368  
         // Invalid if private, a constructor, or a static method
 369  17
         if (!JavadocTagInfo.INHERIT_DOC.isValidOn(aAST)) {
 370  6
             log(aAST, "javadoc.invalidInheritDoc");
 371  
         }
 372  
 
 373  17
         return true;
 374  
     }
 375  
 
 376  
     /**
 377  
      * Returns the scope for the method/constructor at the specified AST. If
 378  
      * the method is in an interface or annotation block, the scope is assumed
 379  
      * to be public.
 380  
      *
 381  
      * @param aAST the token of the method/constructor
 382  
      * @return the scope of the method/constructor
 383  
      */
 384  
     private Scope calculateScope(final DetailAST aAST)
 385  
     {
 386  390
         final DetailAST mods = aAST.findFirstToken(TokenTypes.MODIFIERS);
 387  390
         final Scope declaredScope = ScopeUtils.getScopeFromMods(mods);
 388  390
         return ScopeUtils.inInterfaceOrAnnotationBlock(aAST) ? Scope.PUBLIC
 389  
                 : declaredScope;
 390  
     }
 391  
 
 392  
     /**
 393  
      * Returns the tags in a javadoc comment. Only finds throws, exception,
 394  
      * param, return and see tags.
 395  
      *
 396  
      * @return the tags found
 397  
      * @param aComment the Javadoc comment
 398  
      */
 399  
     private List<JavadocTag> getMethodTags(TextBlock aComment)
 400  
     {
 401  154
         final String[] lines = aComment.getText();
 402  154
         final List<JavadocTag> tags = Lists.newArrayList();
 403  154
         int currentLine = aComment.getStartLineNo() - 1;
 404  
 
 405  710
         for (int i = 0; i < lines.length; i++) {
 406  556
             currentLine++;
 407  556
             final Matcher javadocArgMatcher =
 408  
                 MATCH_JAVADOC_ARG.matcher(lines[i]);
 409  556
             final Matcher javadocNoargMatcher =
 410  
                 MATCH_JAVADOC_NOARG.matcher(lines[i]);
 411  556
             final Matcher noargCurlyMatcher =
 412  
                 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
 413  556
             final Matcher argMultilineStart =
 414  
                 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]);
 415  556
             final Matcher noargMultilineStart =
 416  
                 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
 417  
 
 418  556
             if (javadocArgMatcher.find()) {
 419  118
                 int col = javadocArgMatcher.start(1) - 1;
 420  118
                 if (i == 0) {
 421  12
                     col += aComment.getStartColNo();
 422  
                 }
 423  118
                 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher
 424  
                         .group(1), javadocArgMatcher.group(2)));
 425  118
             }
 426  438
             else if (javadocNoargMatcher.find()) {
 427  37
                 int col = javadocNoargMatcher.start(1) - 1;
 428  37
                 if (i == 0) {
 429  3
                     col += aComment.getStartColNo();
 430  
                 }
 431  37
                 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher
 432  
                         .group(1)));
 433  37
             }
 434  401
             else if (noargCurlyMatcher.find()) {
 435  26
                 int col = noargCurlyMatcher.start(1) - 1;
 436  26
                 if (i == 0) {
 437  17
                     col += aComment.getStartColNo();
 438  
                 }
 439  26
                 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher
 440  
                         .group(1)));
 441  26
             }
 442  375
             else if (argMultilineStart.find()) {
 443  18
                 final String p1 = argMultilineStart.group(1);
 444  18
                 final String p2 = argMultilineStart.group(2);
 445  18
                 int col = argMultilineStart.start(1) - 1;
 446  18
                 if (i == 0) {
 447  3
                     col += aComment.getStartColNo();
 448  
                 }
 449  
 
 450  
                 // Look for the rest of the comment if all we saw was
 451  
                 // the tag and the name. Stop when we see '*/' (end of
 452  
                 // Javadoc), '@' (start of next tag), or anything that's
 453  
                 // not whitespace or '*' characters.
 454  18
                 int remIndex = i + 1;
 455  51
                 while (remIndex < lines.length) {
 456  33
                     final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT
 457  
                             .matcher(lines[remIndex]);
 458  33
                     if (multilineCont.find()) {
 459  18
                         remIndex = lines.length;
 460  18
                         final String lFin = multilineCont.group(1);
 461  18
                         if (!lFin.equals(NEXT_TAG)
 462  
                             && !lFin.equals(END_JAVADOC))
 463  
                         {
 464  9
                             tags.add(new JavadocTag(currentLine, col, p1, p2));
 465  
                         }
 466  
                     }
 467  33
                     remIndex++;
 468  33
                 }
 469  18
             }
 470  357
             else if (noargMultilineStart.find()) {
 471  3
                 final String p1 = noargMultilineStart.group(1);
 472  3
                 int col = noargMultilineStart.start(1) - 1;
 473  3
                 if (i == 0) {
 474  0
                     col += aComment.getStartColNo();
 475  
                 }
 476  
 
 477  
                 // Look for the rest of the comment if all we saw was
 478  
                 // the tag and the name. Stop when we see '*/' (end of
 479  
                 // Javadoc), '@' (start of next tag), or anything that's
 480  
                 // not whitespace or '*' characters.
 481  3
                 int remIndex = i + 1;
 482  6
                 while (remIndex < lines.length) {
 483  3
                     final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT
 484  
                             .matcher(lines[remIndex]);
 485  3
                     if (multilineCont.find()) {
 486  3
                         remIndex = lines.length;
 487  3
                         final String lFin = multilineCont.group(1);
 488  3
                         if (!lFin.equals(NEXT_TAG)
 489  
                             && !lFin.equals(END_JAVADOC))
 490  
                         {
 491  3
                             tags.add(new JavadocTag(currentLine, col, p1));
 492  
                         }
 493  
                     }
 494  3
                     remIndex++;
 495  3
                 }
 496  
             }
 497  
         }
 498  154
         return tags;
 499  
     }
 500  
 
 501  
     /**
 502  
      * Computes the parameter nodes for a method.
 503  
      *
 504  
      * @param aAST the method node.
 505  
      * @return the list of parameter nodes for aAST.
 506  
      */
 507  
     private List<DetailAST> getParameters(DetailAST aAST)
 508  
     {
 509  134
         final DetailAST params = aAST.findFirstToken(TokenTypes.PARAMETERS);
 510  134
         final List<DetailAST> retVal = Lists.newArrayList();
 511  
 
 512  134
         DetailAST child = params.getFirstChild();
 513  212
         while (child != null) {
 514  78
             if (child.getType() == TokenTypes.PARAMETER_DEF) {
 515  60
                 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
 516  60
                 retVal.add(ident);
 517  
             }
 518  78
             child = child.getNextSibling();
 519  
         }
 520  134
         return retVal;
 521  
     }
 522  
 
 523  
     /**
 524  
      * Computes the exception nodes for a method.
 525  
      *
 526  
      * @param aAST the method node.
 527  
      * @return the list of exception nodes for aAST.
 528  
      */
 529  
     private List<ExceptionInfo> getThrows(DetailAST aAST)
 530  
     {
 531  134
         final List<ExceptionInfo> retVal = Lists.newArrayList();
 532  134
         final DetailAST throwsAST = aAST
 533  
                 .findFirstToken(TokenTypes.LITERAL_THROWS);
 534  134
         if (throwsAST != null) {
 535  63
             DetailAST child = throwsAST.getFirstChild();
 536  158
             while (child != null) {
 537  95
                 if ((child.getType() == TokenTypes.IDENT)
 538  
                         || (child.getType() == TokenTypes.DOT))
 539  
                 {
 540  79
                     final FullIdent fi = FullIdent.createFullIdent(child);
 541  79
                     final ExceptionInfo ei = new ExceptionInfo(new Token(fi),
 542  
                             getCurrentClassName());
 543  79
                     retVal.add(ei);
 544  
                 }
 545  95
                 child = child.getNextSibling();
 546  
             }
 547  
         }
 548  134
         return retVal;
 549  
     }
 550  
 
 551  
     /**
 552  
      * Checks a set of tags for matching parameters.
 553  
      *
 554  
      * @param aTags the tags to check
 555  
      * @param aParent the node which takes the parameters
 556  
      * @param aReportExpectedTags whether we should report if do not find
 557  
      *            expected tag
 558  
      */
 559  
     private void checkParamTags(final List<JavadocTag> aTags,
 560  
             final DetailAST aParent, boolean aReportExpectedTags)
 561  
     {
 562  134
         final List<DetailAST> params = getParameters(aParent);
 563  134
         final List<DetailAST> typeParams = CheckUtils
 564  
                 .getTypeParameters(aParent);
 565  
 
 566  
         // Loop over the tags, checking to see they exist in the params.
 567  134
         final ListIterator<JavadocTag> tagIt = aTags.listIterator();
 568  307
         while (tagIt.hasNext()) {
 569  173
             final JavadocTag tag = tagIt.next();
 570  
 
 571  173
             if (!tag.isParamTag()) {
 572  128
                 continue;
 573  
             }
 574  
 
 575  45
             tagIt.remove();
 576  
 
 577  45
             boolean found = false;
 578  
 
 579  
             // Loop looking for matching param
 580  45
             final Iterator<DetailAST> paramIt = params.iterator();
 581  57
             while (paramIt.hasNext()) {
 582  41
                 final DetailAST param = paramIt.next();
 583  41
                 if (param.getText().equals(tag.getArg1())) {
 584  29
                     found = true;
 585  29
                     paramIt.remove();
 586  29
                     break;
 587  
                 }
 588  12
             }
 589  
 
 590  45
             if (tag.getArg1().startsWith("<") && tag.getArg1().endsWith(">")) {
 591  
                 // Loop looking for matching type param
 592  4
                 final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
 593  5
                 while (typeParamsIt.hasNext()) {
 594  4
                     final DetailAST typeParam = typeParamsIt.next();
 595  4
                     if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
 596  
                             .equals(
 597  
                                     tag.getArg1().substring(1,
 598  
                                             tag.getArg1().length() - 1)))
 599  
                     {
 600  3
                         found = true;
 601  3
                         typeParamsIt.remove();
 602  3
                         break;
 603  
                     }
 604  1
                 }
 605  
 
 606  
             }
 607  
 
 608  
             // Handle extra JavadocTag
 609  45
             if (!found) {
 610  13
                 log(tag.getLineNo(), tag.getColumnNo(), "javadoc.unusedTag",
 611  
                         "@param", tag.getArg1());
 612  
             }
 613  45
         }
 614  
 
 615  
         // Now dump out all type parameters/parameters without tags :- unless
 616  
         // the user has chosen to suppress these problems
 617  134
         if (!mAllowMissingParamTags && aReportExpectedTags) {
 618  125
             for (DetailAST param : params) {
 619  22
                 log(param, "javadoc.expectedTag",
 620  
                     JavadocTagInfo.PARAM.getText(), param.getText());
 621  
             }
 622  
 
 623  125
             for (DetailAST typeParam : typeParams) {
 624  4
                 log(typeParam, "javadoc.expectedTag",
 625  
                     JavadocTagInfo.PARAM.getText(),
 626  
                     "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
 627  
                     + ">");
 628  
             }
 629  
         }
 630  134
     }
 631  
 
 632  
     /**
 633  
      * Checks whether a method is a function.
 634  
      *
 635  
      * @param aAST the method node.
 636  
      * @return whether the method is a function.
 637  
      */
 638  
     private boolean isFunction(DetailAST aAST)
 639  
     {
 640  134
         boolean retVal = false;
 641  134
         if (aAST.getType() == TokenTypes.METHOD_DEF) {
 642  121
             final DetailAST typeAST = aAST.findFirstToken(TokenTypes.TYPE);
 643  121
             if ((typeAST != null)
 644  
                 && (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null))
 645  
             {
 646  25
                 retVal = true;
 647  
             }
 648  
         }
 649  134
         return retVal;
 650  
     }
 651  
 
 652  
     /**
 653  
      * Checks for only one return tag. All return tags will be removed from the
 654  
      * supplied list.
 655  
      *
 656  
      * @param aTags the tags to check
 657  
      * @param aLineNo the line number of the expected tag
 658  
      * @param aReportExpectedTags whether we should report if do not find
 659  
      *            expected tag
 660  
      */
 661  
     private void checkReturnTag(List<JavadocTag> aTags, int aLineNo,
 662  
         boolean aReportExpectedTags)
 663  
     {
 664  
         // Loop over tags finding return tags. After the first one, report an
 665  
         // error.
 666  25
         boolean found = false;
 667  25
         final ListIterator<JavadocTag> it = aTags.listIterator();
 668  56
         while (it.hasNext()) {
 669  31
             final JavadocTag jt = it.next();
 670  31
             if (jt.isReturnTag()) {
 671  22
                 if (found) {
 672  3
                     log(jt.getLineNo(), jt.getColumnNo(),
 673  
                         "javadoc.duplicateTag",
 674  
                         JavadocTagInfo.RETURN.getText());
 675  
                 }
 676  22
                 found = true;
 677  22
                 it.remove();
 678  
             }
 679  31
         }
 680  
 
 681  
         // Handle there being no @return tags :- unless
 682  
         // the user has chosen to suppress these problems
 683  25
         if (!found && !mAllowMissingReturnTag && aReportExpectedTags) {
 684  6
             log(aLineNo, "javadoc.return.expected");
 685  
         }
 686  25
     }
 687  
 
 688  
     /**
 689  
      * Checks a set of tags for matching throws.
 690  
      *
 691  
      * @param aTags the tags to check
 692  
      * @param aThrows the throws to check
 693  
      * @param aReportExpectedTags whether we should report if do not find
 694  
      *            expected tag
 695  
      */
 696  
     private void checkThrowsTags(List<JavadocTag> aTags,
 697  
             List<ExceptionInfo> aThrows, boolean aReportExpectedTags)
 698  
     {
 699  
         // Loop over the tags, checking to see they exist in the throws.
 700  
         // The foundThrows used for performance only
 701  134
         final Set<String> foundThrows = Sets.newHashSet();
 702  134
         final ListIterator<JavadocTag> tagIt = aTags.listIterator();
 703  262
         while (tagIt.hasNext()) {
 704  128
             final JavadocTag tag = tagIt.next();
 705  
 
 706  128
             if (!tag.isThrowsTag()) {
 707  46
                 continue;
 708  
             }
 709  
 
 710  82
             tagIt.remove();
 711  
 
 712  
             // Loop looking for matching throw
 713  82
             final String documentedEx = tag.getArg1();
 714  82
             final Token token = new Token(tag.getArg1(), tag.getLineNo(), tag
 715  
                     .getColumnNo());
 716  82
             final ClassInfo documentedCI = createClassInfo(token,
 717  
                     getCurrentClassName());
 718  82
             boolean found = foundThrows.contains(documentedEx);
 719  
 
 720  
             // First look for matches on the exception name
 721  82
             ListIterator<ExceptionInfo> throwIt = aThrows.listIterator();
 722  180
             while (!found && throwIt.hasNext()) {
 723  98
                 final ExceptionInfo ei = throwIt.next();
 724  
 
 725  98
                 if (ei.getName().getText().equals(
 726  
                         documentedCI.getName().getText()))
 727  
                 {
 728  44
                     found = true;
 729  44
                     ei.setFound();
 730  44
                     foundThrows.add(documentedEx);
 731  
                 }
 732  98
             }
 733  
 
 734  
             // Now match on the exception type
 735  82
             throwIt = aThrows.listIterator();
 736  125
             while (!found && throwIt.hasNext()) {
 737  43
                 final ExceptionInfo ei = throwIt.next();
 738  
 
 739  43
                 if (documentedCI.getClazz() == ei.getClazz()) {
 740  14
                     found = true;
 741  14
                     ei.setFound();
 742  14
                     foundThrows.add(documentedEx);
 743  
                 }
 744  29
                 else if (mAllowThrowsTagsForSubclasses) {
 745  13
                     found = isSubclass(documentedCI.getClazz(), ei.getClazz());
 746  
                 }
 747  43
             }
 748  
 
 749  
             // Handle extra JavadocTag.
 750  82
             if (!found) {
 751  16
                 boolean reqd = true;
 752  16
                 if (mAllowUndeclaredRTE) {
 753  6
                     reqd = !isUnchecked(documentedCI.getClazz());
 754  
                 }
 755  
 
 756  16
                 if (reqd) {
 757  13
                     log(tag.getLineNo(), tag.getColumnNo(),
 758  
                         "javadoc.unusedTag",
 759  
                         JavadocTagInfo.THROWS.getText(), tag.getArg1());
 760  
 
 761  
                 }
 762  
             }
 763  82
         }
 764  
 
 765  
         // Now dump out all throws without tags :- unless
 766  
         // the user has chosen to suppress these problems
 767  134
         if (!mAllowMissingThrowsTags && aReportExpectedTags) {
 768  125
             for (ExceptionInfo ei : aThrows) {
 769  79
                 if (!ei.isFound()) {
 770  24
                     final Token fi = ei.getName();
 771  24
                     log(fi.getLineNo(), fi.getColumnNo(),
 772  
                         "javadoc.expectedTag",
 773  
                         JavadocTagInfo.THROWS.getText(), fi.getText());
 774  79
                 }
 775  
             }
 776  
         }
 777  134
     }
 778  
 
 779  
     /**
 780  
      * Returns whether an AST represents a setter method.
 781  
      * @param aAST the AST to check with
 782  
      * @return whether the AST represents a setter method
 783  
      */
 784  
     private boolean isSetterMethod(final DetailAST aAST)
 785  
     {
 786  
         // Check have a method with exactly 7 children which are all that
 787  
         // is allowed in a proper setter method which does not throw any
 788  
         // exceptions.
 789  9
         if ((aAST.getType() != TokenTypes.METHOD_DEF)
 790  
                 || (aAST.getChildCount() != MAX_CHILDREN))
 791  
         {
 792  1
             return false;
 793  
         }
 794  
 
 795  
         // Should I handle only being in a class????
 796  
 
 797  
         // Check the name matches format setX...
 798  8
         final DetailAST type = aAST.findFirstToken(TokenTypes.TYPE);
 799  8
         final String name = type.getNextSibling().getText();
 800  8
         if (!name.matches("^set[A-Z].*")) { // Depends on JDK 1.4
 801  5
             return false;
 802  
         }
 803  
 
 804  
         // Check the return type is void
 805  3
         if (type.getChildCount(TokenTypes.LITERAL_VOID) == 0) {
 806  0
             return false;
 807  
         }
 808  
 
 809  
         // Check that is had only one parameter
 810  3
         final DetailAST params = aAST.findFirstToken(TokenTypes.PARAMETERS);
 811  3
         if ((params == null)
 812  
                 || (params.getChildCount(TokenTypes.PARAMETER_DEF) != 1))
 813  
         {
 814  1
             return false;
 815  
         }
 816  
 
 817  
         // Now verify that the body consists of:
 818  
         // SLIST -> EXPR -> ASSIGN
 819  
         // SEMI
 820  
         // RCURLY
 821  2
         final DetailAST slist = aAST.findFirstToken(TokenTypes.SLIST);
 822  2
         if ((slist == null) || (slist.getChildCount() != BODY_SIZE)) {
 823  1
             return false;
 824  
         }
 825  
 
 826  1
         final AST expr = slist.getFirstChild();
 827  1
         if ((expr.getType() != TokenTypes.EXPR)
 828  
                 || (expr.getFirstChild().getType() != TokenTypes.ASSIGN))
 829  
         {
 830  0
             return false;
 831  
         }
 832  
 
 833  1
         return true;
 834  
     }
 835  
 
 836  
     /**
 837  
      * Returns whether an AST represents a getter method.
 838  
      * @param aAST the AST to check with
 839  
      * @return whether the AST represents a getter method
 840  
      */
 841  
     private boolean isGetterMethod(final DetailAST aAST)
 842  
     {
 843  
         // Check have a method with exactly 7 children which are all that
 844  
         // is allowed in a proper getter method which does not throw any
 845  
         // exceptions.
 846  8
         if ((aAST.getType() != TokenTypes.METHOD_DEF)
 847  
                 || (aAST.getChildCount() != MAX_CHILDREN))
 848  
         {
 849  1
             return false;
 850  
         }
 851  
 
 852  
         // Check the name matches format of getX or isX. Technically I should
 853  
         // check that the format isX is only used with a boolean type.
 854  7
         final DetailAST type = aAST.findFirstToken(TokenTypes.TYPE);
 855  7
         final String name = type.getNextSibling().getText();
 856  7
         if (!name.matches("^(is|get)[A-Z].*")) { // Depends on JDK 1.4
 857  2
             return false;
 858  
         }
 859  
 
 860  
         // Check the return type is void
 861  5
         if (type.getChildCount(TokenTypes.LITERAL_VOID) > 0) {
 862  1
             return false;
 863  
         }
 864  
 
 865  
         // Check that is had only one parameter
 866  4
         final DetailAST params = aAST.findFirstToken(TokenTypes.PARAMETERS);
 867  4
         if ((params == null)
 868  
                 || (params.getChildCount(TokenTypes.PARAMETER_DEF) > 0))
 869  
         {
 870  1
             return false;
 871  
         }
 872  
 
 873  
         // Now verify that the body consists of:
 874  
         // SLIST -> RETURN
 875  
         // RCURLY
 876  3
         final DetailAST slist = aAST.findFirstToken(TokenTypes.SLIST);
 877  3
         if ((slist == null) || (slist.getChildCount() != 2)) {
 878  1
             return false;
 879  
         }
 880  
 
 881  2
         final AST expr = slist.getFirstChild();
 882  2
         if ((expr.getType() != TokenTypes.LITERAL_RETURN)
 883  
                 || (expr.getFirstChild().getType() != TokenTypes.EXPR))
 884  
         {
 885  0
             return false;
 886  
         }
 887  
 
 888  2
         return true;
 889  
     }
 890  
 
 891  
     /**
 892  
      * Returns is a method has the "@Override" annotation.
 893  
      * @param aAST the AST to check with
 894  
      * @return whether the AST represents a method that has the annotation.
 895  
      */
 896  
     private boolean isOverrideMethod(DetailAST aAST)
 897  
     {
 898  
         // Need it to be a method, cannot have an override on anything else.
 899  
         // Must also have MODIFIERS token to hold the @Override
 900  122
         if ((TokenTypes.METHOD_DEF != aAST.getType())
 901  
             || (TokenTypes.MODIFIERS != aAST.getFirstChild().getType()))
 902  
         {
 903  10
             return false;
 904  
         }
 905  
 
 906  
         // Now loop over all nodes while they are annotations looking for
 907  
         // an "@Override".
 908  112
         DetailAST node = aAST.getFirstChild().getFirstChild();
 909  114
         while ((null != node) && (TokenTypes.ANNOTATION == node.getType())) {
 910  4
             if ((node.getFirstChild().getType() == TokenTypes.AT)
 911  
                 && (node.getFirstChild().getNextSibling().getType()
 912  
                     == TokenTypes.IDENT)
 913  
                 && ("Override".equals(
 914  
                         node.getFirstChild().getNextSibling().getText())))
 915  
             {
 916  2
                 return true;
 917  
             }
 918  2
             node = node.getNextSibling();
 919  
         }
 920  110
         return false;
 921  
     }
 922  
 
 923  
     /** Stores useful information about declared exception. */
 924  25
     private class ExceptionInfo
 925  
     {
 926  
         /** does the exception have throws tag associated with. */
 927  
         private boolean mFound;
 928  
         /** class information associated with this exception. */
 929  
         private final ClassInfo mClassInfo;
 930  
 
 931  
         /**
 932  
          * Creates new instance for <code>FullIdent</code>.
 933  
          *
 934  
          * @param aIdent the exception
 935  
          * @param aCurrentClass name of current class.
 936  
          */
 937  
         ExceptionInfo(Token aIdent, String aCurrentClass)
 938  79
         {
 939  79
             mClassInfo = createClassInfo(aIdent, aCurrentClass);
 940  79
         }
 941  
 
 942  
         /** Mark that the exception has associated throws tag */
 943  
         final void setFound()
 944  
         {
 945  58
             mFound = true;
 946  58
         }
 947  
 
 948  
         /** @return whether the exception has throws tag associated with */
 949  
         final boolean isFound()
 950  
         {
 951  79
             return mFound;
 952  
         }
 953  
 
 954  
         /** @return exception's name */
 955  
         final Token getName()
 956  
         {
 957  122
             return mClassInfo.getName();
 958  
         }
 959  
 
 960  
         /** @return class for this exception */
 961  
         final Class<?> getClazz()
 962  
         {
 963  56
             return mClassInfo.getClazz();
 964  
         }
 965  
     }
 966  
 }