Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
JavadocMethodCheck
100%
333/333
100%
194/194
0
JavadocMethodCheck$ExceptionInfo
100%
9/9
N/A
0
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 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  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.javadoc;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.Arrays;
 24  
 import java.util.Collections;
 25  
 import java.util.HashSet;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 import java.util.ListIterator;
 29  
 import java.util.Set;
 30  
 import java.util.regex.Matcher;
 31  
 import java.util.regex.Pattern;
 32  
 
 33  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 34  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 35  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 36  
 import com.puppycrawl.tools.checkstyle.api.Scope;
 37  
 import com.puppycrawl.tools.checkstyle.api.TextBlock;
 38  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 39  
 import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
 40  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 41  
 import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
 42  
 
 43  
 /**
 44  
  * Checks the Javadoc of a method or constructor.
 45  
  *
 46  
  * @author Oliver Burn
 47  
  * @author Rick Giles
 48  
  * @author o_sukhodoslky
 49  
  *
 50  
  * @noinspection deprecation
 51  
  */
 52  58
 public class JavadocMethodCheck extends AbstractTypeAwareCheck {
 53  
 
 54  
     /**
 55  
      * A key is pointing to the warning message text in "messages.properties"
 56  
      * file.
 57  
      */
 58  
     public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
 59  
 
 60  
     /**
 61  
      * A key is pointing to the warning message text in "messages.properties"
 62  
      * file.
 63  
      */
 64  
     public static final String MSG_CLASS_INFO = "javadoc.classInfo";
 65  
 
 66  
     /**
 67  
      * A key is pointing to the warning message text in "messages.properties"
 68  
      * file.
 69  
      */
 70  
     public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
 71  
 
 72  
     /**
 73  
      * A key is pointing to the warning message text in "messages.properties"
 74  
      * file.
 75  
      */
 76  
     public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
 77  
 
 78  
     /**
 79  
      * A key is pointing to the warning message text in "messages.properties"
 80  
      * file.
 81  
      */
 82  
     public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
 83  
 
 84  
     /**
 85  
      * A key is pointing to the warning message text in "messages.properties"
 86  
      * file.
 87  
      */
 88  
     public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
 89  
 
 90  
     /**
 91  
      * A key is pointing to the warning message text in "messages.properties"
 92  
      * file.
 93  
      */
 94  
     public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
 95  
 
 96  
     /**
 97  
      * A key is pointing to the warning message text in "messages.properties"
 98  
      * file.
 99  
      */
 100  
     public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
 101  
 
 102  
     /** Compiled regexp to match Javadoc tags that take an argument. */
 103  2
     private static final Pattern MATCH_JAVADOC_ARG =
 104  2
             CommonUtils.createPattern("@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
 105  
 
 106  
     /** Compiled regexp to match first part of multilineJavadoc tags. */
 107  2
     private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START =
 108  2
             CommonUtils.createPattern("@(throws|exception|param)\\s+(\\S+)\\s*$");
 109  
 
 110  
     /** Compiled regexp to look for a continuation of the comment. */
 111  2
     private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
 112  2
             CommonUtils.createPattern("(\\*/|@|[^\\s\\*])");
 113  
 
 114  
     /** Multiline finished at end of comment. */
 115  
     private static final String END_JAVADOC = "*/";
 116  
     /** Multiline finished at next Javadoc. */
 117  
     private static final String NEXT_TAG = "@";
 118  
 
 119  
     /** Compiled regexp to match Javadoc tags with no argument. */
 120  2
     private static final Pattern MATCH_JAVADOC_NOARG =
 121  2
             CommonUtils.createPattern("@(return|see)\\s+\\S");
 122  
     /** Compiled regexp to match first part of multilineJavadoc tags. */
 123  2
     private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
 124  2
             CommonUtils.createPattern("@(return|see)\\s*$");
 125  
     /** Compiled regexp to match Javadoc tags with no argument and {}. */
 126  4
     private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
 127  2
             CommonUtils.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
 128  
 
 129  
     /** Default value of minimal amount of lines in method to allow no documentation.*/
 130  
     private static final int DEFAULT_MIN_LINE_COUNT = -1;
 131  
 
 132  
     /** The visibility scope where Javadoc comments are checked. */
 133  58
     private Scope scope = Scope.PRIVATE;
 134  
 
 135  
     /** The visibility scope where Javadoc comments shouldn't be checked. */
 136  
     private Scope excludeScope;
 137  
 
 138  
     /** Minimal amount of lines in method to allow no documentation.*/
 139  58
     private int minLineCount = DEFAULT_MIN_LINE_COUNT;
 140  
 
 141  
     /**
 142  
      * Controls whether to allow documented exceptions that are not declared if
 143  
      * they are a subclass of java.lang.RuntimeException.
 144  
      */
 145  
     // -@cs[AbbreviationAsWordInName] We can not change it as,
 146  
     // check's property is part of API (used in configurations).
 147  
     private boolean allowUndeclaredRTE;
 148  
 
 149  
     /**
 150  
      * Allows validating throws tags.
 151  
      */
 152  
     private boolean validateThrows;
 153  
 
 154  
     /**
 155  
      * Controls whether to allow documented exceptions that are subclass of one
 156  
      * of declared exception. Defaults to false (backward compatibility).
 157  
      */
 158  
     private boolean allowThrowsTagsForSubclasses;
 159  
 
 160  
     /**
 161  
      * Controls whether to ignore errors when a method has parameters but does
 162  
      * not have matching param tags in the javadoc. Defaults to false.
 163  
      */
 164  
     private boolean allowMissingParamTags;
 165  
 
 166  
     /**
 167  
      * Controls whether to ignore errors when a method declares that it throws
 168  
      * exceptions but does not have matching throws tags in the javadoc.
 169  
      * Defaults to false.
 170  
      */
 171  
     private boolean allowMissingThrowsTags;
 172  
 
 173  
     /**
 174  
      * Controls whether to ignore errors when a method returns non-void type
 175  
      * but does not have a return tag in the javadoc. Defaults to false.
 176  
      */
 177  
     private boolean allowMissingReturnTag;
 178  
 
 179  
     /**
 180  
      * Controls whether to ignore errors when there is no javadoc. Defaults to
 181  
      * false.
 182  
      */
 183  
     private boolean allowMissingJavadoc;
 184  
 
 185  
     /**
 186  
      * Controls whether to allow missing Javadoc on accessor methods for
 187  
      * properties (setters and getters).
 188  
      */
 189  
     private boolean allowMissingPropertyJavadoc;
 190  
 
 191  
     /** List of annotations that could allow missed documentation. */
 192  58
     private List<String> allowedAnnotations = Collections.singletonList("Override");
 193  
 
 194  
     /** Method names that match this pattern do not require javadoc blocks. */
 195  
     private Pattern ignoreMethodNamesRegex;
 196  
 
 197  
     /**
 198  
      * Set regex for matching method names to ignore.
 199  
      * @param pattern a pattern.
 200  
      */
 201  
     public void setIgnoreMethodNamesRegex(Pattern pattern) {
 202  3
         ignoreMethodNamesRegex = pattern;
 203  3
     }
 204  
 
 205  
     /**
 206  
      * Sets minimal amount of lines in method to allow no documentation.
 207  
      * @param value user's value.
 208  
      */
 209  
     public void setMinLineCount(int value) {
 210  3
         minLineCount = value;
 211  3
     }
 212  
 
 213  
     /**
 214  
      * Allow validating throws tag.
 215  
      * @param value user's value.
 216  
      */
 217  
     public void setValidateThrows(boolean value) {
 218  6
         validateThrows = value;
 219  6
     }
 220  
 
 221  
     /**
 222  
      * Sets list of annotations.
 223  
      * @param userAnnotations user's value.
 224  
      */
 225  
     public void setAllowedAnnotations(String... userAnnotations) {
 226  6
         allowedAnnotations = Arrays.asList(userAnnotations);
 227  6
     }
 228  
 
 229  
     /**
 230  
      * Set the scope.
 231  
      *
 232  
      * @param scope a scope.
 233  
      */
 234  
     public void setScope(Scope scope) {
 235  11
         this.scope = scope;
 236  11
     }
 237  
 
 238  
     /**
 239  
      * Set the excludeScope.
 240  
      *
 241  
      * @param excludeScope a scope.
 242  
      */
 243  
     public void setExcludeScope(Scope excludeScope) {
 244  2
         this.excludeScope = excludeScope;
 245  2
     }
 246  
 
 247  
     /**
 248  
      * Controls whether to allow documented exceptions that are not declared if
 249  
      * they are a subclass of java.lang.RuntimeException.
 250  
      *
 251  
      * @param flag a {@code Boolean} value
 252  
      */
 253  
     // -@cs[AbbreviationAsWordInName] We can not change it as,
 254  
     // check's property is part of API (used in configurations).
 255  
     public void setAllowUndeclaredRTE(boolean flag) {
 256  10
         allowUndeclaredRTE = flag;
 257  10
     }
 258  
 
 259  
     /**
 260  
      * Controls whether to allow documented exception that are subclass of one
 261  
      * of declared exceptions.
 262  
      *
 263  
      * @param flag a {@code Boolean} value
 264  
      */
 265  
     public void setAllowThrowsTagsForSubclasses(boolean flag) {
 266  6
         allowThrowsTagsForSubclasses = flag;
 267  6
     }
 268  
 
 269  
     /**
 270  
      * Controls whether to allow a method which has parameters to omit matching
 271  
      * param tags in the javadoc. Defaults to false.
 272  
      *
 273  
      * @param flag a {@code Boolean} value
 274  
      */
 275  
     public void setAllowMissingParamTags(boolean flag) {
 276  3
         allowMissingParamTags = flag;
 277  3
     }
 278  
 
 279  
     /**
 280  
      * Controls whether to allow a method which declares that it throws
 281  
      * exceptions to omit matching throws tags in the javadoc. Defaults to
 282  
      * false.
 283  
      *
 284  
      * @param flag a {@code Boolean} value
 285  
      */
 286  
     public void setAllowMissingThrowsTags(boolean flag) {
 287  2
         allowMissingThrowsTags = flag;
 288  2
     }
 289  
 
 290  
     /**
 291  
      * Controls whether to allow a method which returns non-void type to omit
 292  
      * the return tag in the javadoc. Defaults to false.
 293  
      *
 294  
      * @param flag a {@code Boolean} value
 295  
      */
 296  
     public void setAllowMissingReturnTag(boolean flag) {
 297  2
         allowMissingReturnTag = flag;
 298  2
     }
 299  
 
 300  
     /**
 301  
      * Controls whether to ignore errors when there is no javadoc. Defaults to
 302  
      * false.
 303  
      *
 304  
      * @param flag a {@code Boolean} value
 305  
      */
 306  
     public void setAllowMissingJavadoc(boolean flag) {
 307  1
         allowMissingJavadoc = flag;
 308  1
     }
 309  
 
 310  
     /**
 311  
      * Controls whether to ignore errors when there is no javadoc for a
 312  
      * property accessor (setter/getter methods). Defaults to false.
 313  
      *
 314  
      * @param flag a {@code Boolean} value
 315  
      */
 316  
     public void setAllowMissingPropertyJavadoc(final boolean flag) {
 317  1
         allowMissingPropertyJavadoc = flag;
 318  1
     }
 319  
 
 320  
     @Override
 321  
     public int[] getDefaultTokens() {
 322  98
         return getAcceptableTokens();
 323  
     }
 324  
 
 325  
     @Override
 326  
     public int[] getAcceptableTokens() {
 327  105
         return new int[] {
 328  
             TokenTypes.PACKAGE_DEF,
 329  
             TokenTypes.IMPORT,
 330  
             TokenTypes.CLASS_DEF,
 331  
             TokenTypes.ENUM_DEF,
 332  
             TokenTypes.INTERFACE_DEF,
 333  
             TokenTypes.METHOD_DEF,
 334  
             TokenTypes.CTOR_DEF,
 335  
             TokenTypes.ANNOTATION_FIELD_DEF,
 336  
         };
 337  
     }
 338  
 
 339  
     @Override
 340  
     public boolean isCommentNodesRequired() {
 341  414
         return true;
 342  
     }
 343  
 
 344  
     @Override
 345  
     protected final void processAST(DetailAST ast) {
 346  466
         final Scope theScope = calculateScope(ast);
 347  466
         if (shouldCheck(ast, theScope)) {
 348  379
             final FileContents contents = getFileContents();
 349  379
             final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
 350  
 
 351  379
             if (textBlock == null) {
 352  189
                 if (!isMissingJavadocAllowed(ast)) {
 353  132
                     log(ast, MSG_JAVADOC_MISSING);
 354  
                 }
 355  
             }
 356  
             else {
 357  190
                 checkComment(ast, textBlock);
 358  
             }
 359  
         }
 360  465
     }
 361  
 
 362  
     /**
 363  
      * Some javadoc.
 364  
      * @param methodDef Some javadoc.
 365  
      * @return Some javadoc.
 366  
      */
 367  
     private boolean hasAllowedAnnotations(DetailAST methodDef) {
 368  295
         boolean result = false;
 369  295
         final DetailAST modifiersNode = methodDef.findFirstToken(TokenTypes.MODIFIERS);
 370  295
         DetailAST annotationNode = modifiersNode.findFirstToken(TokenTypes.ANNOTATION);
 371  298
         while (annotationNode != null && annotationNode.getType() == TokenTypes.ANNOTATION) {
 372  19
             DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT);
 373  19
             if (identNode == null) {
 374  2
                 identNode = annotationNode.findFirstToken(TokenTypes.DOT)
 375  2
                     .findFirstToken(TokenTypes.IDENT);
 376  
             }
 377  19
             if (allowedAnnotations.contains(identNode.getText())) {
 378  16
                 result = true;
 379  16
                 break;
 380  
             }
 381  3
             annotationNode = annotationNode.getNextSibling();
 382  3
         }
 383  295
         return result;
 384  
     }
 385  
 
 386  
     /**
 387  
      * Some javadoc.
 388  
      * @param methodDef Some javadoc.
 389  
      * @return Some javadoc.
 390  
      */
 391  
     private static int getMethodsNumberOfLine(DetailAST methodDef) {
 392  
         final int numberOfLines;
 393  143
         final DetailAST lcurly = methodDef.getLastChild();
 394  143
         final DetailAST rcurly = lcurly.getLastChild();
 395  
 
 396  143
         if (lcurly.getFirstChild() == rcurly) {
 397  108
             numberOfLines = 1;
 398  
         }
 399  
         else {
 400  35
             numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
 401  
         }
 402  143
         return numberOfLines;
 403  
     }
 404  
 
 405  
     @Override
 406  
     protected final void logLoadError(Token ident) {
 407  6
         logLoadErrorImpl(ident.getLineNo(), ident.getColumnNo(),
 408  
             MSG_CLASS_INFO,
 409  3
             JavadocTagInfo.THROWS.getText(), ident.getText());
 410  2
     }
 411  
 
 412  
     /**
 413  
      * The JavadocMethodCheck is about to report a missing Javadoc.
 414  
      * This hook can be used by derived classes to allow a missing javadoc
 415  
      * in some situations.  The default implementation checks
 416  
      * {@code allowMissingJavadoc} and
 417  
      * {@code allowMissingPropertyJavadoc} properties, do not forget
 418  
      * to call {@code super.isMissingJavadocAllowed(ast)} in case
 419  
      * you want to keep this logic.
 420  
      * @param ast the tree node for the method or constructor.
 421  
      * @return True if this method or constructor doesn't need Javadoc.
 422  
      */
 423  
     private boolean isMissingJavadocAllowed(final DetailAST ast) {
 424  378
         return allowMissingJavadoc
 425  
             || allowMissingPropertyJavadoc
 426  17
                 && (CheckUtils.isSetterMethod(ast) || CheckUtils.isGetterMethod(ast))
 427  149
             || matchesSkipRegex(ast)
 428  146
             || isContentsAllowMissingJavadoc(ast);
 429  
     }
 430  
 
 431  
     /**
 432  
      * Checks if the Javadoc can be missing if the method or constructor is
 433  
      * below the minimum line count or has a special annotation.
 434  
      *
 435  
      * @param ast the tree node for the method or constructor.
 436  
      * @return True if this method or constructor doesn't need Javadoc.
 437  
      */
 438  
     private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
 439  292
         return (ast.getType() == TokenTypes.METHOD_DEF || ast.getType() == TokenTypes.CTOR_DEF)
 440  143
                 && (getMethodsNumberOfLine(ast) <= minLineCount || hasAllowedAnnotations(ast));
 441  
     }
 442  
 
 443  
     /**
 444  
      * Checks if the given method name matches the regex. In that case
 445  
      * we skip enforcement of javadoc for this method
 446  
      * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
 447  
      * @return true if given method name matches the regex.
 448  
      */
 449  
     private boolean matchesSkipRegex(DetailAST methodDef) {
 450  149
         boolean result = false;
 451  149
         if (ignoreMethodNamesRegex != null) {
 452  6
             final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
 453  6
             final String methodName = ident.getText();
 454  
 
 455  6
             final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
 456  6
             if (matcher.matches()) {
 457  3
                 result = true;
 458  
             }
 459  
         }
 460  149
         return result;
 461  
     }
 462  
 
 463  
     /**
 464  
      * Whether we should check this node.
 465  
      *
 466  
      * @param ast a given node.
 467  
      * @param nodeScope the scope of the node.
 468  
      * @return whether we should check a given node.
 469  
      */
 470  
     private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
 471  466
         final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
 472  
 
 473  932
         return (excludeScope == null
 474  
                 || nodeScope != excludeScope
 475  
                 && surroundingScope != excludeScope)
 476  454
             && nodeScope.isIn(scope)
 477  410
             && surroundingScope.isIn(scope);
 478  
     }
 479  
 
 480  
     /**
 481  
      * Checks the Javadoc for a method.
 482  
      *
 483  
      * @param ast the token for the method
 484  
      * @param comment the Javadoc comment
 485  
      */
 486  
     private void checkComment(DetailAST ast, TextBlock comment) {
 487  190
         final List<JavadocTag> tags = getMethodTags(comment);
 488  
 
 489  190
         if (!hasShortCircuitTag(ast, tags)) {
 490  173
             if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
 491  4
                 checkReturnTag(tags, ast.getLineNo(), true);
 492  
             }
 493  
             else {
 494  169
                 final Iterator<JavadocTag> it = tags.iterator();
 495  
                 // Check for inheritDoc
 496  169
                 boolean hasInheritDocTag = false;
 497  378
                 while (!hasInheritDocTag && it.hasNext()) {
 498  209
                     hasInheritDocTag = it.next().isInheritDocTag();
 499  
                 }
 500  169
                 final boolean reportExpectedTags = !hasInheritDocTag && !hasAllowedAnnotations(ast);
 501  
 
 502  169
                 checkParamTags(tags, ast, reportExpectedTags);
 503  169
                 checkThrowsTags(tags, getThrows(ast), reportExpectedTags);
 504  168
                 if (CheckUtils.isNonVoidMethod(ast)) {
 505  46
                     checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
 506  
                 }
 507  
             }
 508  
 
 509  
             // Dump out all unused tags
 510  198
             tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
 511  175
                 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
 512  
         }
 513  189
     }
 514  
 
 515  
     /**
 516  
      * Validates whether the Javadoc has a short circuit tag. Currently this is
 517  
      * the inheritTag. Any errors are logged.
 518  
      *
 519  
      * @param ast the construct being checked
 520  
      * @param tags the list of Javadoc tags associated with the construct
 521  
      * @return true if the construct has a short circuit tag.
 522  
      */
 523  
     private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
 524  190
         boolean result = true;
 525  
         // Check if it contains {@inheritDoc} tag
 526  190
         if (tags.size() == 1
 527  91
                 && tags.get(0).isInheritDocTag()) {
 528  
             // Invalid if private, a constructor, or a static method
 529  17
             if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
 530  6
                 log(ast, MSG_INVALID_INHERIT_DOC);
 531  
             }
 532  
         }
 533  
         else {
 534  173
             result = false;
 535  
         }
 536  190
         return result;
 537  
     }
 538  
 
 539  
     /**
 540  
      * Returns the scope for the method/constructor at the specified AST. If
 541  
      * the method is in an interface or annotation block, the scope is assumed
 542  
      * to be public.
 543  
      *
 544  
      * @param ast the token of the method/constructor
 545  
      * @return the scope of the method/constructor
 546  
      */
 547  
     private static Scope calculateScope(final DetailAST ast) {
 548  
         final Scope scope;
 549  
 
 550  466
         if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
 551  26
             scope = Scope.PUBLIC;
 552  
         }
 553  
         else {
 554  440
             final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
 555  440
             scope = ScopeUtils.getScopeFromMods(mods);
 556  
         }
 557  466
         return scope;
 558  
     }
 559  
 
 560  
     /**
 561  
      * Returns the tags in a javadoc comment. Only finds throws, exception,
 562  
      * param, return and see tags.
 563  
      *
 564  
      * @param comment the Javadoc comment
 565  
      * @return the tags found
 566  
      */
 567  
     private static List<JavadocTag> getMethodTags(TextBlock comment) {
 568  190
         final String[] lines = comment.getText();
 569  190
         final List<JavadocTag> tags = new ArrayList<>();
 570  190
         int currentLine = comment.getStartLineNo() - 1;
 571  190
         final int startColumnNumber = comment.getStartColNo();
 572  
 
 573  923
         for (int i = 0; i < lines.length; i++) {
 574  733
             currentLine++;
 575  733
             final Matcher javadocArgMatcher =
 576  733
                 MATCH_JAVADOC_ARG.matcher(lines[i]);
 577  733
             final Matcher javadocNoargMatcher =
 578  733
                 MATCH_JAVADOC_NOARG.matcher(lines[i]);
 579  733
             final Matcher noargCurlyMatcher =
 580  733
                 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
 581  733
             final Matcher argMultilineStart =
 582  733
                 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]);
 583  733
             final Matcher noargMultilineStart =
 584  733
                 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
 585  
 
 586  733
             if (javadocArgMatcher.find()) {
 587  149
                 final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
 588  298
                 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
 589  149
                         javadocArgMatcher.group(2)));
 590  149
             }
 591  584
             else if (javadocNoargMatcher.find()) {
 592  47
                 final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
 593  47
                 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
 594  47
             }
 595  537
             else if (noargCurlyMatcher.find()) {
 596  28
                 final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
 597  28
                 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
 598  28
             }
 599  509
             else if (argMultilineStart.find()) {
 600  22
                 final int col = calculateTagColumn(argMultilineStart, i, startColumnNumber);
 601  22
                 tags.addAll(getMultilineArgTags(argMultilineStart, col, lines, i, currentLine));
 602  22
             }
 603  487
             else if (noargMultilineStart.find()) {
 604  9
                 tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
 605  
             }
 606  
         }
 607  190
         return tags;
 608  
     }
 609  
 
 610  
     /**
 611  
      * Calculates column number using Javadoc tag matcher.
 612  
      * @param javadocTagMatcher found javadoc tag matcher
 613  
      * @param lineNumber line number of Javadoc tag in comment
 614  
      * @param startColumnNumber column number of Javadoc comment beginning
 615  
      * @return column number
 616  
      */
 617  
     private static int calculateTagColumn(Matcher javadocTagMatcher,
 618  
             int lineNumber, int startColumnNumber) {
 619  246
         int col = javadocTagMatcher.start(1) - 1;
 620  246
         if (lineNumber == 0) {
 621  35
             col += startColumnNumber;
 622  
         }
 623  246
         return col;
 624  
     }
 625  
 
 626  
     /**
 627  
      * Gets multiline Javadoc tags with arguments.
 628  
      * @param argMultilineStart javadoc tag Matcher
 629  
      * @param column column number of Javadoc tag
 630  
      * @param lines comment text lines
 631  
      * @param lineIndex line number that contains the javadoc tag
 632  
      * @param tagLine javadoc tag line number in file
 633  
      * @return javadoc tags with arguments
 634  
      */
 635  
     private static List<JavadocTag> getMultilineArgTags(final Matcher argMultilineStart,
 636  
             final int column, final String[] lines, final int lineIndex, final int tagLine) {
 637  22
         final List<JavadocTag> tags = new ArrayList<>();
 638  22
         final String param1 = argMultilineStart.group(1);
 639  22
         final String param2 = argMultilineStart.group(2);
 640  22
         int remIndex = lineIndex + 1;
 641  59
         while (remIndex < lines.length) {
 642  37
             final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
 643  37
             if (multilineCont.find()) {
 644  22
                 remIndex = lines.length;
 645  22
                 final String lFin = multilineCont.group(1);
 646  22
                 if (!lFin.equals(NEXT_TAG)
 647  16
                     && !lFin.equals(END_JAVADOC)) {
 648  13
                     tags.add(new JavadocTag(tagLine, column, param1, param2));
 649  
                 }
 650  
             }
 651  37
             remIndex++;
 652  37
         }
 653  22
         return tags;
 654  
     }
 655  
 
 656  
     /**
 657  
      * Gets multiline Javadoc tags with no arguments.
 658  
      * @param noargMultilineStart javadoc tag Matcher
 659  
      * @param lines comment text lines
 660  
      * @param lineIndex line number that contains the javadoc tag
 661  
      * @param tagLine javadoc tag line number in file
 662  
      * @return javadoc tags with no arguments
 663  
      */
 664  
     private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
 665  
             final String[] lines, final int lineIndex, final int tagLine) {
 666  9
         final String param1 = noargMultilineStart.group(1);
 667  9
         final int col = noargMultilineStart.start(1) - 1;
 668  9
         final List<JavadocTag> tags = new ArrayList<>();
 669  9
         int remIndex = lineIndex + 1;
 670  20
         while (remIndex < lines.length) {
 671  11
             final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT
 672  11
                     .matcher(lines[remIndex]);
 673  11
             if (multilineCont.find()) {
 674  9
                 remIndex = lines.length;
 675  9
                 final String lFin = multilineCont.group(1);
 676  9
                 if (!lFin.equals(NEXT_TAG)
 677  7
                     && !lFin.equals(END_JAVADOC)) {
 678  3
                     tags.add(new JavadocTag(tagLine, col, param1));
 679  
                 }
 680  
             }
 681  11
             remIndex++;
 682  11
         }
 683  
 
 684  9
         return tags;
 685  
     }
 686  
 
 687  
     /**
 688  
      * Computes the parameter nodes for a method.
 689  
      *
 690  
      * @param ast the method node.
 691  
      * @return the list of parameter nodes for ast.
 692  
      */
 693  
     private static List<DetailAST> getParameters(DetailAST ast) {
 694  169
         final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
 695  169
         final List<DetailAST> returnValue = new ArrayList<>();
 696  
 
 697  169
         DetailAST child = params.getFirstChild();
 698  269
         while (child != null) {
 699  100
             if (child.getType() == TokenTypes.PARAMETER_DEF) {
 700  81
                 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
 701  81
                 if (ident != null) {
 702  80
                     returnValue.add(ident);
 703  
                 }
 704  
             }
 705  100
             child = child.getNextSibling();
 706  
         }
 707  169
         return returnValue;
 708  
     }
 709  
 
 710  
     /**
 711  
      * Computes the exception nodes for a method.
 712  
      *
 713  
      * @param ast the method node.
 714  
      * @return the list of exception nodes for ast.
 715  
      */
 716  
     private List<ExceptionInfo> getThrows(DetailAST ast) {
 717  169
         final List<ExceptionInfo> returnValue = new ArrayList<>();
 718  169
         final DetailAST throwsAST = ast
 719  169
                 .findFirstToken(TokenTypes.LITERAL_THROWS);
 720  169
         if (throwsAST != null) {
 721  76
             DetailAST child = throwsAST.getFirstChild();
 722  184
             while (child != null) {
 723  108
                 if (child.getType() == TokenTypes.IDENT
 724  32
                         || child.getType() == TokenTypes.DOT) {
 725  92
                     final FullIdent ident = FullIdent.createFullIdent(child);
 726  92
                     final ExceptionInfo exceptionInfo = new ExceptionInfo(
 727  92
                             createClassInfo(new Token(ident), getCurrentClassName()));
 728  92
                     returnValue.add(exceptionInfo);
 729  
                 }
 730  108
                 child = child.getNextSibling();
 731  
             }
 732  
         }
 733  169
         return returnValue;
 734  
     }
 735  
 
 736  
     /**
 737  
      * Checks a set of tags for matching parameters.
 738  
      *
 739  
      * @param tags the tags to check
 740  
      * @param parent the node which takes the parameters
 741  
      * @param reportExpectedTags whether we should report if do not find
 742  
      *            expected tag
 743  
      */
 744  
     private void checkParamTags(final List<JavadocTag> tags,
 745  
             final DetailAST parent, boolean reportExpectedTags) {
 746  169
         final List<DetailAST> params = getParameters(parent);
 747  169
         final List<DetailAST> typeParams = CheckUtils
 748  169
                 .getTypeParameters(parent);
 749  
 
 750  
         // Loop over the tags, checking to see they exist in the params.
 751  169
         final ListIterator<JavadocTag> tagIt = tags.listIterator();
 752  388
         while (tagIt.hasNext()) {
 753  219
             final JavadocTag tag = tagIt.next();
 754  
 
 755  219
             if (!tag.isParamTag()) {
 756  153
                 continue;
 757  
             }
 758  
 
 759  66
             tagIt.remove();
 760  
 
 761  66
             final String arg1 = tag.getFirstArg();
 762  66
             boolean found = removeMatchingParam(params, arg1);
 763  
 
 764  66
             if (CommonUtils.startsWithChar(arg1, '<') && CommonUtils.endsWithChar(arg1, '>')) {
 765  8
                 found = searchMatchingTypeParameter(typeParams,
 766  4
                         arg1.substring(1, arg1.length() - 1));
 767  
 
 768  
             }
 769  
 
 770  
             // Handle extra JavadocTag
 771  66
             if (!found) {
 772  18
                 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
 773  
                         "@param", arg1);
 774  
             }
 775  66
         }
 776  
 
 777  
         // Now dump out all type parameters/parameters without tags :- unless
 778  
         // the user has chosen to suppress these problems
 779  169
         if (!allowMissingParamTags && reportExpectedTags) {
 780  144
             for (DetailAST param : params) {
 781  46
                 log(param, MSG_EXPECTED_TAG,
 782  23
                     JavadocTagInfo.PARAM.getText(), param.getText());
 783  23
             }
 784  
 
 785  144
             for (DetailAST typeParam : typeParams) {
 786  10
                 log(typeParam, MSG_EXPECTED_TAG,
 787  5
                     JavadocTagInfo.PARAM.getText(),
 788  5
                     "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
 789  
                     + ">");
 790  5
             }
 791  
         }
 792  169
     }
 793  
 
 794  
     /**
 795  
      * Returns true if required type found in type parameters.
 796  
      * @param typeParams
 797  
      *            list of type parameters
 798  
      * @param requiredTypeName
 799  
      *            name of required type
 800  
      * @return true if required type found in type parameters.
 801  
      */
 802  
     private static boolean searchMatchingTypeParameter(List<DetailAST> typeParams,
 803  
             String requiredTypeName) {
 804  
         // Loop looking for matching type param
 805  4
         final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
 806  4
         boolean found = false;
 807  5
         while (typeParamsIt.hasNext()) {
 808  4
             final DetailAST typeParam = typeParamsIt.next();
 809  4
             if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
 810  4
                     .equals(requiredTypeName)) {
 811  3
                 found = true;
 812  3
                 typeParamsIt.remove();
 813  3
                 break;
 814  
             }
 815  1
         }
 816  4
         return found;
 817  
     }
 818  
 
 819  
     /**
 820  
      * Remove parameter from params collection by name.
 821  
      * @param params collection of DetailAST parameters
 822  
      * @param paramName name of parameter
 823  
      * @return true if parameter found and removed
 824  
      */
 825  
     private static boolean removeMatchingParam(List<DetailAST> params, String paramName) {
 826  66
         boolean found = false;
 827  66
         final Iterator<DetailAST> paramIt = params.iterator();
 828  78
         while (paramIt.hasNext()) {
 829  57
             final DetailAST param = paramIt.next();
 830  57
             if (param.getText().equals(paramName)) {
 831  45
                 found = true;
 832  45
                 paramIt.remove();
 833  45
                 break;
 834  
             }
 835  12
         }
 836  66
         return found;
 837  
     }
 838  
 
 839  
     /**
 840  
      * Checks for only one return tag. All return tags will be removed from the
 841  
      * supplied list.
 842  
      *
 843  
      * @param tags the tags to check
 844  
      * @param lineNo the line number of the expected tag
 845  
      * @param reportExpectedTags whether we should report if do not find
 846  
      *            expected tag
 847  
      */
 848  
     private void checkReturnTag(List<JavadocTag> tags, int lineNo,
 849  
         boolean reportExpectedTags) {
 850  
         // Loop over tags finding return tags. After the first one, report an
 851  
         // error.
 852  50
         boolean found = false;
 853  50
         final ListIterator<JavadocTag> it = tags.listIterator();
 854  96
         while (it.hasNext()) {
 855  46
             final JavadocTag javadocTag = it.next();
 856  46
             if (javadocTag.isReturnTag()) {
 857  35
                 if (found) {
 858  6
                     log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
 859  
                             MSG_DUPLICATE_TAG,
 860  3
                             JavadocTagInfo.RETURN.getText());
 861  
                 }
 862  35
                 found = true;
 863  35
                 it.remove();
 864  
             }
 865  46
         }
 866  
 
 867  
         // Handle there being no @return tags :- unless
 868  
         // the user has chosen to suppress these problems
 869  50
         if (!found && !allowMissingReturnTag && reportExpectedTags) {
 870  10
             log(lineNo, MSG_RETURN_EXPECTED);
 871  
         }
 872  50
     }
 873  
 
 874  
     /**
 875  
      * Checks a set of tags for matching throws.
 876  
      *
 877  
      * @param tags the tags to check
 878  
      * @param throwsList the throws to check
 879  
      * @param reportExpectedTags whether we should report if do not find
 880  
      *            expected tag
 881  
      */
 882  
     private void checkThrowsTags(List<JavadocTag> tags,
 883  
             List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
 884  
         // Loop over the tags, checking to see they exist in the throws.
 885  
         // The foundThrows used for performance only
 886  169
         final Set<String> foundThrows = new HashSet<>();
 887  169
         final ListIterator<JavadocTag> tagIt = tags.listIterator();
 888  321
         while (tagIt.hasNext()) {
 889  153
             final JavadocTag tag = tagIt.next();
 890  
 
 891  153
             if (!tag.isThrowsTag()) {
 892  57
                 continue;
 893  
             }
 894  96
             tagIt.remove();
 895  
 
 896  
             // Loop looking for matching throw
 897  96
             final String documentedEx = tag.getFirstArg();
 898  96
             final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
 899  96
                     .getColumnNo());
 900  192
             final AbstractClassInfo documentedClassInfo = createClassInfo(token,
 901  96
                     getCurrentClassName());
 902  96
             final boolean found = foundThrows.contains(documentedEx)
 903  93
                     || isInThrows(throwsList, documentedClassInfo, foundThrows);
 904  
 
 905  
             // Handle extra JavadocTag.
 906  96
             if (!found) {
 907  21
                 boolean reqd = true;
 908  21
                 if (allowUndeclaredRTE) {
 909  9
                     reqd = !isUnchecked(documentedClassInfo.getClazz());
 910  
                 }
 911  
 
 912  20
                 if (reqd && validateThrows) {
 913  26
                     log(tag.getLineNo(), tag.getColumnNo(),
 914  
                         MSG_UNUSED_TAG,
 915  13
                         JavadocTagInfo.THROWS.getText(), tag.getFirstArg());
 916  
 
 917  
                 }
 918  
             }
 919  95
         }
 920  
         // Now dump out all throws without tags :- unless
 921  
         // the user has chosen to suppress these problems
 922  168
         if (!allowMissingThrowsTags && reportExpectedTags) {
 923  228
             throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
 924  143
                 .forEach(exceptionInfo -> {
 925  25
                     final Token token = exceptionInfo.getName();
 926  50
                     log(token.getLineNo(), token.getColumnNo(),
 927  
                         MSG_EXPECTED_TAG,
 928  25
                         JavadocTagInfo.THROWS.getText(), token.getText());
 929  25
                 });
 930  
         }
 931  168
     }
 932  
 
 933  
     /**
 934  
      * Verifies that documented exception is in throws.
 935  
      *
 936  
      * @param throwsList list of throws
 937  
      * @param documentedClassInfo documented exception class info
 938  
      * @param foundThrows previously found throws
 939  
      * @return true if documented exception is in throws.
 940  
      */
 941  
     private boolean isInThrows(List<ExceptionInfo> throwsList,
 942  
             AbstractClassInfo documentedClassInfo, Set<String> foundThrows) {
 943  93
         boolean found = false;
 944  93
         ExceptionInfo foundException = null;
 945  
 
 946  
         // First look for matches on the exception name
 947  93
         for (ExceptionInfo exceptionInfo : throwsList) {
 948  214
             if (exceptionInfo.getName().getText().equals(
 949  107
                     documentedClassInfo.getName().getText())) {
 950  55
                 found = true;
 951  55
                 foundException = exceptionInfo;
 952  55
                 break;
 953  
             }
 954  52
         }
 955  
 
 956  
         // Now match on the exception type
 957  93
         final ListIterator<ExceptionInfo> exceptionInfoIt = throwsList.listIterator();
 958  134
         while (!found && exceptionInfoIt.hasNext()) {
 959  41
             final ExceptionInfo exceptionInfo = exceptionInfoIt.next();
 960  
 
 961  41
             if (documentedClassInfo.getClazz() == exceptionInfo.getClazz()) {
 962  12
                 found = true;
 963  12
                 foundException = exceptionInfo;
 964  
             }
 965  29
             else if (allowThrowsTagsForSubclasses) {
 966  13
                 found = isSubclass(documentedClassInfo.getClazz(), exceptionInfo.getClazz());
 967  
             }
 968  41
         }
 969  
 
 970  93
         if (foundException != null) {
 971  67
             foundException.setFound();
 972  67
             foundThrows.add(documentedClassInfo.getName().getText());
 973  
         }
 974  
 
 975  93
         return found;
 976  
     }
 977  
 
 978  
     /** Stores useful information about declared exception. */
 979  338
     private static class ExceptionInfo {
 980  
         /** Class information associated with this exception. */
 981  
         private final AbstractClassInfo classInfo;
 982  
         /** Does the exception have throws tag associated with. */
 983  
         private boolean found;
 984  
 
 985  
         /**
 986  
          * Creates new instance for {@code FullIdent}.
 987  
          *
 988  
          * @param classInfo class info
 989  
          */
 990  92
         ExceptionInfo(AbstractClassInfo classInfo) {
 991  92
             this.classInfo = classInfo;
 992  92
         }
 993  
 
 994  
         /** Mark that the exception has associated throws tag. */
 995  
         private void setFound() {
 996  67
             found = true;
 997  67
         }
 998  
 
 999  
         /**
 1000  
          * Checks that the exception has throws tag associated with it.
 1001  
          * @return whether the exception has throws tag associated with
 1002  
          */
 1003  
         private boolean isFound() {
 1004  85
             return found;
 1005  
         }
 1006  
 
 1007  
         /**
 1008  
          * Gets exception name.
 1009  
          * @return exception's name
 1010  
          */
 1011  
         private Token getName() {
 1012  132
             return classInfo.getName();
 1013  
         }
 1014  
 
 1015  
         /**
 1016  
          * Gets exception class.
 1017  
          * @return class for this exception
 1018  
          */
 1019  
         private Class<?> getClazz() {
 1020  54
             return classInfo.getClazz();
 1021  
         }
 1022  
     }
 1023  
 }