001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.List; 028import java.util.ListIterator; 029import java.util.Set; 030import java.util.regex.MatchResult; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033 034import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 035import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 036import com.puppycrawl.tools.checkstyle.api.DetailAST; 037import com.puppycrawl.tools.checkstyle.api.FileContents; 038import com.puppycrawl.tools.checkstyle.api.FullIdent; 039import com.puppycrawl.tools.checkstyle.api.TextBlock; 040import com.puppycrawl.tools.checkstyle.api.TokenTypes; 041import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 042import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 043import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 044import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 045 046/** 047 * <p> 048 * Checks the Javadoc of a method or constructor. 049 * </p> 050 * <p> 051 * Violates parameters and type parameters for which no param tags are present can 052 * be suppressed by defining property {@code allowMissingParamTags}. 053 * </p> 054 * <p> 055 * Violates methods which return non-void but for which no return tag is present can 056 * be suppressed by defining property {@code allowMissingReturnTag}. 057 * </p> 058 * <p> 059 * Violates exceptions which are declared to be thrown (by {@code throws} in the method 060 * signature or by {@code throw new} in the method body), but for which no throws tag is 061 * present by activation of property {@code validateThrows}. 062 * Note that {@code throw new} is not checked in the following places: 063 * </p> 064 * <ul> 065 * <li> 066 * Inside a try block (with catch). It is not possible to determine if the thrown 067 * exception can be caught by the catch block as there is no knowledge of the 068 * inheritance hierarchy, so the try block is ignored entirely. However, catch 069 * and finally blocks, as well as try blocks without catch, are still checked. 070 * </li> 071 * <li> 072 * Local classes, anonymous classes and lambda expressions. It is not known when the 073 * throw statements inside such classes are going to be evaluated, so they are ignored. 074 * </li> 075 * </ul> 076 * <p> 077 * ATTENTION: Checkstyle does not have information about hierarchy of exception types 078 * so usage of base class is considered as separate exception type. 079 * As workaround, you need to specify both types in javadoc (parent and exact type). 080 * </p> 081 * <p> 082 * Javadoc is not required on a method that is tagged with the {@code @Override} 083 * annotation. However, under Java 5 it is not possible to mark a method required 084 * for an interface (this was <i>corrected</i> under Java 6). Hence, Checkstyle 085 * supports using the convention of using a single {@code {@inheritDoc}} tag 086 * instead of all the other tags. 087 * </p> 088 * <p> 089 * Note that only inheritable items will allow the {@code {@inheritDoc}} 090 * tag to be used in place of comments. Static methods at all visibilities, 091 * private non-static methods and constructors are not inheritable. 092 * </p> 093 * <p> 094 * For example, if the following method is implementing a method required by 095 * an interface, then the Javadoc could be done as: 096 * </p> 097 * <pre> 098 * /** {@inheritDoc} */ 099 * public int checkReturnTag(final int aTagIndex, 100 * JavadocTag[] aTags, 101 * int aLineNo) 102 * </pre> 103 * <ul> 104 * <li> 105 * Property {@code allowedAnnotations} - Specify annotations that allow missed documentation. 106 * Type is {@code java.lang.String[]}. 107 * Default value is {@code Override}. 108 * </li> 109 * <li> 110 * Property {@code validateThrows} - Control whether to validate {@code throws} tags. 111 * Type is {@code boolean}. 112 * Default value is {@code false}. 113 * </li> 114 * <li> 115 * Property {@code accessModifiers} - Specify the access modifiers where Javadoc comments are 116 * checked. 117 * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}. 118 * Default value is {@code public, protected, package, private}. 119 * </li> 120 * <li> 121 * Property {@code allowMissingParamTags} - Control whether to ignore violations 122 * when a method has parameters but does not have matching {@code param} tags in the javadoc. 123 * Type is {@code boolean}. 124 * Default value is {@code false}. 125 * </li> 126 * <li> 127 * Property {@code allowMissingReturnTag} - Control whether to ignore violations 128 * when a method returns non-void type and does not have a {@code return} tag in the javadoc. 129 * Type is {@code boolean}. 130 * Default value is {@code false}. 131 * </li> 132 * <li> 133 * Property {@code tokens} - tokens to check 134 * Type is {@code java.lang.String[]}. 135 * Validation type is {@code tokenSet}. 136 * Default value is: 137 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 138 * METHOD_DEF</a>, 139 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 140 * CTOR_DEF</a>, 141 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 142 * ANNOTATION_FIELD_DEF</a>, 143 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 144 * COMPACT_CTOR_DEF</a>. 145 * </li> 146 * </ul> 147 * <p> 148 * To configure the default check: 149 * </p> 150 * <pre> 151 * <module name="JavadocMethod"/> 152 * </pre> 153 * <p>Example:</p> 154 * <pre> 155 * public class Test { 156 * 157 * /** 158 * * 159 * */ 160 * Test(int x) { // violation, param tag missing for x 161 * } 162 * 163 * /** 164 * * 165 * */ 166 * public int foo(int p1) { // violation, param tag missing for p1 167 * return p1; // violation, return tag missing 168 * } 169 * 170 * /** 171 * * 172 * * @param p1 The first number 173 * */ 174 * @Deprecated 175 * private int boo(int p1) { 176 * return p1; // violation, return tag missing 177 * } 178 * 179 * /** 180 * * 181 * */ 182 * void bar(int p1) { // violation, param tag missing for p1 183 * } // ok, no return tag for void method 184 * } 185 * </pre> 186 * <p> 187 * To configure the check for only {@code public} modifier, ignoring any missing param tags is: 188 * </p> 189 * <pre> 190 * <module name="JavadocMethod"> 191 * <property name="accessModifiers" value="public"/> 192 * <property name="allowMissingParamTags" value="true"/> 193 * </module> 194 * </pre> 195 * <p>Example:</p> 196 * <pre> 197 * public class Test { 198 * 199 * /** 200 * * 201 * */ 202 * Test(int x) { // ok, only public methods checked 203 * } 204 * 205 * /** 206 * * 207 * */ 208 * public int foo(int p1) { // ok, missing param tags allowed 209 * return p1; // violation, return tag missing 210 * } 211 * 212 * /** 213 * * 214 * * @param p1 The first number 215 * */ 216 * @Deprecated 217 * private int boo(int p1) { 218 * return p1; // ok, only public methods checked 219 * } 220 * 221 * /** 222 * * 223 * */ 224 * void bar(int p1) { // ok, missing param tags allowed 225 * } // ok, no return tag for void method 226 * } 227 * </pre> 228 * <p> 229 * To configure the check for methods which are in {@code private} and {@code package}, 230 * but not any other modifier: 231 * </p> 232 * <pre> 233 * <module name="JavadocMethod"> 234 * <property name="accessModifiers" value="private, package"/> 235 * </module> 236 * </pre> 237 * <p>Example:</p> 238 * <pre> 239 * class Test { 240 * 241 * /** 242 * * 243 * */ 244 * Test(int x) { // violation, param tag missing for x 245 * } 246 * 247 * /** 248 * * 249 * */ 250 * public int foo(int p1) { // ok, public methods not checked 251 * return p1; 252 * } 253 * 254 * /** 255 * * 256 * * @param p1 The first number 257 * */ 258 * @Deprecated 259 * private int boo(int p1) { 260 * return p1; // violation, return tag missing 261 * } 262 * 263 * /** 264 * * 265 * */ 266 * void bar(int p1) { // violation, param tag missing for p1 267 * } // ok, no return tag for void method 268 * } 269 * </pre> 270 * <p> 271 * To configure the check to ignore any missing return tags: 272 * </p> 273 * <pre> 274 * <module name="JavadocMethod"> 275 * <property name="allowMissingReturnTag" value="true"/> 276 * </module> 277 * </pre> 278 * <p>Example:</p> 279 * <pre> 280 * public class Test { 281 * 282 * /** 283 * * 284 * */ 285 * Test(int x) { // violation, param tag missing for x 286 * } 287 * 288 * /** 289 * * 290 * */ 291 * public int foo(int p1) { // violation, param tag missing for p1 292 * return p1; // ok, missing return tag allowed 293 * } 294 * 295 * /** 296 * * 297 * * @param p1 The first number 298 * */ 299 * @Deprecated 300 * private int boo(int p1) { 301 * return p1; // ok, missing return tag allowed 302 * } 303 * 304 * /** 305 * * 306 * */ 307 * void bar(int p1) { // violation, param tag missing for p1 308 * } // ok, no return tag for void method 309 * } 310 * </pre> 311 * <p> 312 * To configure the check to ignore Methods with annotation {@code Deprecated}: 313 * </p> 314 * <pre> 315 * <module name="JavadocMethod"> 316 * <property name="allowedAnnotations" value="Deprecated"/> 317 * </module> 318 * </pre> 319 * <p>Example:</p> 320 * <pre> 321 * public class Test { 322 * 323 * /** 324 * * 325 * */ 326 * Test(int x) { // violation, param tag missing for x 327 * } 328 * 329 * /** 330 * * 331 * */ 332 * public int foo(int p1) { // violation, param tag missing for p1 333 * return p1; // violation, return tag missing 334 * } 335 * 336 * /** 337 * * 338 * * @param p1 The first number 339 * */ 340 * @Deprecated 341 * private int boo(int p1) { 342 * return p1; // ok, Deprecated methods not checked 343 * } 344 * 345 * /** 346 * * 347 * */ 348 * void bar(int p1) { // violation, param tag missing for p1 349 * } // ok, no return tag for void method 350 * } 351 * </pre> 352 * <p> 353 * To configure the check only for tokens which are Constructor Definitions: 354 * </p> 355 * <pre> 356 * <module name="JavadocMethod"> 357 * <property name="tokens" value="CTOR_DEF"/> 358 * </module> 359 * </pre> 360 * <p>Example:</p> 361 * <pre> 362 * public class Test { 363 * 364 * /** 365 * * 366 * */ 367 * Test(int x) { // violation, param tag missing for x 368 * } 369 * 370 * /** 371 * * 372 * */ 373 * public int foo(int p1) { // ok, method not checked 374 * return p1; // ok, method not checked 375 * } 376 * 377 * /** 378 * * 379 * * @param p1 The first number 380 * */ 381 * @Deprecated 382 * private int boo(int p1) { 383 * return p1; // ok, method not checked 384 * } 385 * 386 * /** 387 * * 388 * */ 389 * void bar(int p1) { // ok, method not checked 390 * } 391 * } 392 * </pre> 393 * <p> 394 * To configure the check to validate {@code throws} tags, you can use following config. 395 * </p> 396 * <pre> 397 * <module name="JavadocMethod"> 398 * <property name="validateThrows" value="true"/> 399 * </module> 400 * </pre> 401 * <p>Example:</p> 402 * <pre> 403 * /** 404 * * Actual exception thrown is child class of class that is declared in throws. 405 * * It is limitation of checkstyle (as checkstyle does not know type hierarchy). 406 * * Javadoc is valid not declaring FileNotFoundException 407 * * BUT checkstyle can not distinguish relationship between exceptions. 408 * * @param file some file 409 * * @throws IOException if some problem 410 * */ 411 * public void doSomething8(File file) throws IOException { 412 * if (file == null) { 413 * throw new FileNotFoundException(); // violation 414 * } 415 * } 416 * 417 * /** 418 * * Exact throw type referencing in javadoc even first is parent of second type. 419 * * It is a limitation of checkstyle (as checkstyle does not know type hierarchy). 420 * * This javadoc is valid for checkstyle and for javadoc tool. 421 * * @param file some file 422 * * @throws IOException if some problem 423 * * @throws FileNotFoundException if file is not found 424 * */ 425 * public void doSomething9(File file) throws IOException { 426 * if (file == null) { 427 * throw new FileNotFoundException(); 428 * } 429 * } 430 * 431 * /** 432 * * Ignore try block, but keep catch and finally blocks. 433 * * 434 * * @param s String to parse 435 * * @return A positive integer 436 * */ 437 * public int parsePositiveInt(String s) { 438 * try { 439 * int value = Integer.parseInt(s); 440 * if (value <= 0) { 441 * throw new NumberFormatException(value + " is negative/zero"); // ok, try 442 * } 443 * return value; 444 * } catch (NumberFormatException ex) { 445 * throw new IllegalArgumentException("Invalid number", ex); // violation, catch 446 * } finally { 447 * throw new IllegalStateException("Should never reach here"); // violation, finally 448 * } 449 * } 450 * 451 * /** 452 * * Try block without catch is not ignored. 453 * * 454 * * @return a String from standard input, if there is one 455 * */ 456 * public String readLine() { 457 * try (Scanner sc = new Scanner(System.in)) { 458 * if (!sc.hasNext()) { 459 * throw new IllegalStateException("Empty input"); // violation, not caught 460 * } 461 * return sc.next(); 462 * } 463 * } 464 * 465 * /** 466 * * Lambda expressions are ignored as we do not know when the exception will be thrown. 467 * * 468 * * @param s a String to be printed at some point in the future 469 * * @return a Runnable to be executed when the string is to be printed 470 * */ 471 * public Runnable printLater(String s) { 472 * return () -> { 473 * if (s == null) { 474 * throw new NullPointerException(); // ok 475 * } 476 * System.out.println(s); 477 * }; 478 * } 479 * </pre> 480 * <p> 481 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 482 * </p> 483 * <p> 484 * Violation Message Keys: 485 * </p> 486 * <ul> 487 * <li> 488 * {@code javadoc.classInfo} 489 * </li> 490 * <li> 491 * {@code javadoc.duplicateTag} 492 * </li> 493 * <li> 494 * {@code javadoc.expectedTag} 495 * </li> 496 * <li> 497 * {@code javadoc.invalidInheritDoc} 498 * </li> 499 * <li> 500 * {@code javadoc.return.expected} 501 * </li> 502 * <li> 503 * {@code javadoc.unusedTag} 504 * </li> 505 * <li> 506 * {@code javadoc.unusedTagGeneral} 507 * </li> 508 * </ul> 509 * 510 * @since 3.0 511 */ 512@FileStatefulCheck 513public class JavadocMethodCheck extends AbstractCheck { 514 515 /** 516 * A key is pointing to the warning message text in "messages.properties" 517 * file. 518 */ 519 public static final String MSG_CLASS_INFO = "javadoc.classInfo"; 520 521 /** 522 * A key is pointing to the warning message text in "messages.properties" 523 * file. 524 */ 525 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral"; 526 527 /** 528 * A key is pointing to the warning message text in "messages.properties" 529 * file. 530 */ 531 public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc"; 532 533 /** 534 * A key is pointing to the warning message text in "messages.properties" 535 * file. 536 */ 537 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag"; 538 539 /** 540 * A key is pointing to the warning message text in "messages.properties" 541 * file. 542 */ 543 public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag"; 544 545 /** 546 * A key is pointing to the warning message text in "messages.properties" 547 * file. 548 */ 549 public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected"; 550 551 /** 552 * A key is pointing to the warning message text in "messages.properties" 553 * file. 554 */ 555 public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag"; 556 557 /** Compiled regexp to match Javadoc tags that take an argument. */ 558 private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern( 559 "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*"); 560 /** Compiled regexp to match Javadoc tags with argument but with missing description. */ 561 private static final Pattern MATCH_JAVADOC_ARG_MISSING_DESCRIPTION = 562 CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+" 563 + "(\\S[^*]*)(?:(\\s+|\\*\\/))?"); 564 565 /** Compiled regexp to look for a continuation of the comment. */ 566 private static final Pattern MATCH_JAVADOC_MULTILINE_CONT = 567 CommonUtil.createPattern("(\\*\\/|@|[^\\s\\*])"); 568 569 /** Multiline finished at end of comment. */ 570 private static final String END_JAVADOC = "*/"; 571 /** Multiline finished at next Javadoc. */ 572 private static final String NEXT_TAG = "@"; 573 574 /** Compiled regexp to match Javadoc tags with no argument. */ 575 private static final Pattern MATCH_JAVADOC_NOARG = 576 CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S"); 577 /** Compiled regexp to match first part of multilineJavadoc tags. */ 578 private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START = 579 CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$"); 580 /** Compiled regexp to match Javadoc tags with no argument and {}. */ 581 private static final Pattern MATCH_JAVADOC_NOARG_CURLY = 582 CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}"); 583 584 /** Name of current class. */ 585 private String currentClassName; 586 587 /** Specify the access modifiers where Javadoc comments are checked. */ 588 private AccessModifierOption[] accessModifiers = { 589 AccessModifierOption.PUBLIC, 590 AccessModifierOption.PROTECTED, 591 AccessModifierOption.PACKAGE, 592 AccessModifierOption.PRIVATE, 593 }; 594 595 /** 596 * Control whether to validate {@code throws} tags. 597 */ 598 private boolean validateThrows; 599 600 /** 601 * Control whether to ignore violations when a method has parameters but does 602 * not have matching {@code param} tags in the javadoc. 603 */ 604 private boolean allowMissingParamTags; 605 606 /** 607 * Control whether to ignore violations when a method returns non-void type 608 * and does not have a {@code return} tag in the javadoc. 609 */ 610 private boolean allowMissingReturnTag; 611 612 /** Specify annotations that allow missed documentation. */ 613 private Set<String> allowedAnnotations = Set.of("Override"); 614 615 /** 616 * Setter to control whether to validate {@code throws} tags. 617 * 618 * @param value user's value. 619 */ 620 public void setValidateThrows(boolean value) { 621 validateThrows = value; 622 } 623 624 /** 625 * Setter to specify annotations that allow missed documentation. 626 * 627 * @param userAnnotations user's value. 628 */ 629 public void setAllowedAnnotations(String... userAnnotations) { 630 allowedAnnotations = Set.of(userAnnotations); 631 } 632 633 /** 634 * Setter to specify the access modifiers where Javadoc comments are checked. 635 * 636 * @param accessModifiers access modifiers. 637 */ 638 public void setAccessModifiers(AccessModifierOption... accessModifiers) { 639 this.accessModifiers = 640 Arrays.copyOf(accessModifiers, accessModifiers.length); 641 } 642 643 /** 644 * Setter to control whether to ignore violations when a method has parameters 645 * but does not have matching {@code param} tags in the javadoc. 646 * 647 * @param flag a {@code Boolean} value 648 */ 649 public void setAllowMissingParamTags(boolean flag) { 650 allowMissingParamTags = flag; 651 } 652 653 /** 654 * Setter to control whether to ignore violations when a method returns non-void type 655 * and does not have a {@code return} tag in the javadoc. 656 * 657 * @param flag a {@code Boolean} value 658 */ 659 public void setAllowMissingReturnTag(boolean flag) { 660 allowMissingReturnTag = flag; 661 } 662 663 @Override 664 public final int[] getRequiredTokens() { 665 return new int[] { 666 TokenTypes.CLASS_DEF, 667 TokenTypes.INTERFACE_DEF, 668 TokenTypes.ENUM_DEF, 669 TokenTypes.RECORD_DEF, 670 }; 671 } 672 673 @Override 674 public int[] getDefaultTokens() { 675 return getAcceptableTokens(); 676 } 677 678 @Override 679 public int[] getAcceptableTokens() { 680 return new int[] { 681 TokenTypes.CLASS_DEF, 682 TokenTypes.ENUM_DEF, 683 TokenTypes.INTERFACE_DEF, 684 TokenTypes.METHOD_DEF, 685 TokenTypes.CTOR_DEF, 686 TokenTypes.ANNOTATION_FIELD_DEF, 687 TokenTypes.RECORD_DEF, 688 TokenTypes.COMPACT_CTOR_DEF, 689 }; 690 } 691 692 @Override 693 public void beginTree(DetailAST rootAST) { 694 currentClassName = ""; 695 } 696 697 @Override 698 public final void visitToken(DetailAST ast) { 699 if (ast.getType() == TokenTypes.CLASS_DEF 700 || ast.getType() == TokenTypes.INTERFACE_DEF 701 || ast.getType() == TokenTypes.ENUM_DEF 702 || ast.getType() == TokenTypes.RECORD_DEF) { 703 processClass(ast); 704 } 705 else { 706 processAST(ast); 707 } 708 } 709 710 @Override 711 public final void leaveToken(DetailAST ast) { 712 if (ast.getType() == TokenTypes.CLASS_DEF 713 || ast.getType() == TokenTypes.INTERFACE_DEF 714 || ast.getType() == TokenTypes.ENUM_DEF 715 || ast.getType() == TokenTypes.RECORD_DEF) { 716 // perhaps it was inner class 717 final int dotIdx = currentClassName.lastIndexOf('$'); 718 currentClassName = currentClassName.substring(0, dotIdx); 719 } 720 } 721 722 /** 723 * Called to process an AST when visiting it. 724 * 725 * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or 726 * IMPORT tokens. 727 */ 728 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 729 @SuppressWarnings("deprecation") 730 private void processAST(DetailAST ast) { 731 if (shouldCheck(ast)) { 732 final FileContents contents = getFileContents(); 733 final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo()); 734 735 if (textBlock != null) { 736 checkComment(ast, textBlock); 737 } 738 } 739 } 740 741 /** 742 * Whether we should check this node. 743 * 744 * @param ast a given node. 745 * @return whether we should check a given node. 746 */ 747 private boolean shouldCheck(final DetailAST ast) { 748 final AccessModifierOption surroundingAccessModifier = CheckUtil 749 .getSurroundingAccessModifier(ast); 750 final AccessModifierOption accessModifier = CheckUtil 751 .getAccessModifierFromModifiersToken(ast); 752 return surroundingAccessModifier != null 753 && Arrays.stream(accessModifiers) 754 .anyMatch(modifier -> modifier == surroundingAccessModifier) 755 && Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier); 756 } 757 758 /** 759 * Checks the Javadoc for a method. 760 * 761 * @param ast the token for the method 762 * @param comment the Javadoc comment 763 */ 764 private void checkComment(DetailAST ast, TextBlock comment) { 765 final List<JavadocTag> tags = getMethodTags(comment); 766 767 if (!hasShortCircuitTag(ast, tags)) { 768 if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) { 769 checkReturnTag(tags, ast.getLineNo(), true); 770 } 771 else { 772 final Iterator<JavadocTag> it = tags.iterator(); 773 // Check for inheritDoc 774 boolean hasInheritDocTag = false; 775 while (!hasInheritDocTag && it.hasNext()) { 776 hasInheritDocTag = it.next().isInheritDocTag(); 777 } 778 final boolean reportExpectedTags = !hasInheritDocTag 779 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); 780 781 // COMPACT_CTOR_DEF has no parameters 782 if (ast.getType() != TokenTypes.COMPACT_CTOR_DEF) { 783 checkParamTags(tags, ast, reportExpectedTags); 784 } 785 final List<ExceptionInfo> throwed = 786 combineExceptionInfo(getThrows(ast), getThrowed(ast)); 787 checkThrowsTags(tags, throwed, reportExpectedTags); 788 if (CheckUtil.isNonVoidMethod(ast)) { 789 checkReturnTag(tags, ast.getLineNo(), reportExpectedTags); 790 } 791 792 } 793 794 // Dump out all unused tags 795 tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag()) 796 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL)); 797 } 798 } 799 800 /** 801 * Validates whether the Javadoc has a short circuit tag. Currently, this is 802 * the inheritTag. Any violations are logged. 803 * 804 * @param ast the construct being checked 805 * @param tags the list of Javadoc tags associated with the construct 806 * @return true if the construct has a short circuit tag. 807 */ 808 private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) { 809 boolean result = true; 810 // Check if it contains {@inheritDoc} tag 811 if (tags.size() == 1 812 && tags.get(0).isInheritDocTag()) { 813 // Invalid if private, a constructor, or a static method 814 if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) { 815 log(ast, MSG_INVALID_INHERIT_DOC); 816 } 817 } 818 else { 819 result = false; 820 } 821 return result; 822 } 823 824 /** 825 * Returns the tags in a javadoc comment. Only finds throws, exception, 826 * param, return and see tags. 827 * 828 * @param comment the Javadoc comment 829 * @return the tags found 830 */ 831 private static List<JavadocTag> getMethodTags(TextBlock comment) { 832 final String[] lines = comment.getText(); 833 final List<JavadocTag> tags = new ArrayList<>(); 834 int currentLine = comment.getStartLineNo() - 1; 835 final int startColumnNumber = comment.getStartColNo(); 836 837 for (int i = 0; i < lines.length; i++) { 838 currentLine++; 839 final Matcher javadocArgMatcher = 840 MATCH_JAVADOC_ARG.matcher(lines[i]); 841 final Matcher javadocArgMissingDescriptionMatcher = 842 MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]); 843 final Matcher javadocNoargMatcher = 844 MATCH_JAVADOC_NOARG.matcher(lines[i]); 845 final Matcher noargCurlyMatcher = 846 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]); 847 final Matcher noargMultilineStart = 848 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]); 849 850 if (javadocArgMatcher.find()) { 851 final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber); 852 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1), 853 javadocArgMatcher.group(2))); 854 } 855 else if (javadocArgMissingDescriptionMatcher.find()) { 856 final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i, 857 startColumnNumber); 858 tags.add(new JavadocTag(currentLine, col, 859 javadocArgMissingDescriptionMatcher.group(1), 860 javadocArgMissingDescriptionMatcher.group(2))); 861 } 862 else if (javadocNoargMatcher.find()) { 863 final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber); 864 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1))); 865 } 866 else if (noargCurlyMatcher.find()) { 867 final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber); 868 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1))); 869 } 870 else if (noargMultilineStart.find()) { 871 tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine)); 872 } 873 } 874 return tags; 875 } 876 877 /** 878 * Calculates column number using Javadoc tag matcher. 879 * 880 * @param javadocTagMatchResult found javadoc tag match result 881 * @param lineNumber line number of Javadoc tag in comment 882 * @param startColumnNumber column number of Javadoc comment beginning 883 * @return column number 884 */ 885 private static int calculateTagColumn(MatchResult javadocTagMatchResult, 886 int lineNumber, int startColumnNumber) { 887 int col = javadocTagMatchResult.start(1) - 1; 888 if (lineNumber == 0) { 889 col += startColumnNumber; 890 } 891 return col; 892 } 893 894 /** 895 * Gets multiline Javadoc tags with no arguments. 896 * 897 * @param noargMultilineStart javadoc tag Matcher 898 * @param lines comment text lines 899 * @param lineIndex line number that contains the javadoc tag 900 * @param tagLine javadoc tag line number in file 901 * @return javadoc tags with no arguments 902 */ 903 private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart, 904 final String[] lines, final int lineIndex, final int tagLine) { 905 int remIndex = lineIndex; 906 Matcher multilineCont; 907 908 do { 909 remIndex++; 910 multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]); 911 } while (!multilineCont.find()); 912 913 final List<JavadocTag> tags = new ArrayList<>(); 914 final String lFin = multilineCont.group(1); 915 if (!NEXT_TAG.equals(lFin) 916 && !END_JAVADOC.equals(lFin)) { 917 final String param1 = noargMultilineStart.group(1); 918 final int col = noargMultilineStart.start(1) - 1; 919 920 tags.add(new JavadocTag(tagLine, col, param1)); 921 } 922 923 return tags; 924 } 925 926 /** 927 * Computes the parameter nodes for a method. 928 * 929 * @param ast the method node. 930 * @return the list of parameter nodes for ast. 931 */ 932 private static List<DetailAST> getParameters(DetailAST ast) { 933 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 934 final List<DetailAST> returnValue = new ArrayList<>(); 935 936 DetailAST child = params.getFirstChild(); 937 while (child != null) { 938 if (child.getType() == TokenTypes.PARAMETER_DEF) { 939 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT); 940 if (ident != null) { 941 returnValue.add(ident); 942 } 943 } 944 child = child.getNextSibling(); 945 } 946 return returnValue; 947 } 948 949 /** 950 * Computes the exception nodes for a method. 951 * 952 * @param ast the method node. 953 * @return the list of exception nodes for ast. 954 */ 955 private static List<ExceptionInfo> getThrows(DetailAST ast) { 956 final List<ExceptionInfo> returnValue = new ArrayList<>(); 957 final DetailAST throwsAST = ast 958 .findFirstToken(TokenTypes.LITERAL_THROWS); 959 if (throwsAST != null) { 960 DetailAST child = throwsAST.getFirstChild(); 961 while (child != null) { 962 if (child.getType() == TokenTypes.IDENT 963 || child.getType() == TokenTypes.DOT) { 964 returnValue.add(getExceptionInfo(child)); 965 } 966 child = child.getNextSibling(); 967 } 968 } 969 return returnValue; 970 } 971 972 /** 973 * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'. 974 * 975 * @param methodAst method DetailAST object where to find exceptions 976 * @return list of ExceptionInfo 977 */ 978 private static List<ExceptionInfo> getThrowed(DetailAST methodAst) { 979 final List<ExceptionInfo> returnValue = new ArrayList<>(); 980 final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST); 981 if (blockAst != null) { 982 final List<DetailAST> throwLiterals = findTokensInAstByType(blockAst, 983 TokenTypes.LITERAL_THROW); 984 for (DetailAST throwAst : throwLiterals) { 985 if (!isInIgnoreBlock(blockAst, throwAst)) { 986 final DetailAST newAst = throwAst.getFirstChild().getFirstChild(); 987 if (newAst.getType() == TokenTypes.LITERAL_NEW) { 988 final DetailAST child = newAst.getFirstChild(); 989 returnValue.add(getExceptionInfo(child)); 990 } 991 } 992 } 993 } 994 return returnValue; 995 } 996 997 /** 998 * Get ExceptionInfo instance. 999 * 1000 * @param ast DetailAST object where to find exceptions node; 1001 * @return ExceptionInfo 1002 */ 1003 private static ExceptionInfo getExceptionInfo(DetailAST ast) { 1004 final FullIdent ident = FullIdent.createFullIdent(ast); 1005 final DetailAST firstClassNameNode = getFirstClassNameNode(ast); 1006 return new ExceptionInfo(firstClassNameNode, 1007 new ClassInfo(new Token(ident))); 1008 } 1009 1010 /** 1011 * Get node where class name of exception starts. 1012 * 1013 * @param ast DetailAST object where to find exceptions node; 1014 * @return exception node where class name starts 1015 */ 1016 private static DetailAST getFirstClassNameNode(DetailAST ast) { 1017 DetailAST startNode = ast; 1018 while (startNode.getType() == TokenTypes.DOT) { 1019 startNode = startNode.getFirstChild(); 1020 } 1021 return startNode; 1022 } 1023 1024 /** 1025 * Checks if a 'throw' usage is contained within a block that should be ignored. 1026 * Such blocks consist of try (with catch) blocks, local classes, anonymous classes, 1027 * and lambda expressions. Note that a try block without catch is not considered. 1028 * 1029 * @param methodBodyAst DetailAST node representing the method body 1030 * @param throwAst DetailAST node representing the 'throw' literal 1031 * @return true if throwAst is inside a block that should be ignored 1032 */ 1033 private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) { 1034 DetailAST ancestor = throwAst.getParent(); 1035 while (ancestor != methodBodyAst) { 1036 if (ancestor.getType() == TokenTypes.LITERAL_TRY 1037 && ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null 1038 || ancestor.getType() == TokenTypes.LAMBDA 1039 || ancestor.getType() == TokenTypes.OBJBLOCK) { 1040 // throw is inside a try block, and there is a catch block, 1041 // or throw is inside a lambda expression/anonymous class/local class 1042 break; 1043 } 1044 if (ancestor.getType() == TokenTypes.LITERAL_CATCH 1045 || ancestor.getType() == TokenTypes.LITERAL_FINALLY) { 1046 // if the throw is inside a catch or finally block, 1047 // skip the immediate ancestor (try token) 1048 ancestor = ancestor.getParent(); 1049 } 1050 ancestor = ancestor.getParent(); 1051 } 1052 return ancestor != methodBodyAst; 1053 } 1054 1055 /** 1056 * Combine ExceptionInfo collections together by matching names. 1057 * 1058 * @param first the first collection of ExceptionInfo 1059 * @param second the second collection of ExceptionInfo 1060 * @return combined list of ExceptionInfo 1061 */ 1062 private static List<ExceptionInfo> combineExceptionInfo(Collection<ExceptionInfo> first, 1063 Iterable<ExceptionInfo> second) { 1064 final List<ExceptionInfo> result = new ArrayList<>(first); 1065 for (ExceptionInfo exceptionInfo : second) { 1066 if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) { 1067 result.add(exceptionInfo); 1068 } 1069 } 1070 return result; 1071 } 1072 1073 /** 1074 * Finds node of specified type among root children, siblings, siblings children 1075 * on any deep level. 1076 * 1077 * @param root DetailAST 1078 * @param astType value of TokenType 1079 * @return {@link List} of {@link DetailAST} nodes which matches the predicate. 1080 */ 1081 public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) { 1082 final List<DetailAST> result = new ArrayList<>(); 1083 // iterative preorder depth-first search 1084 DetailAST curNode = root; 1085 do { 1086 // process curNode 1087 if (curNode.getType() == astType) { 1088 result.add(curNode); 1089 } 1090 // process children (if any) 1091 if (curNode.hasChildren()) { 1092 curNode = curNode.getFirstChild(); 1093 continue; 1094 } 1095 // backtrack to parent if last child, stopping at root 1096 while (curNode != root && curNode.getNextSibling() == null) { 1097 curNode = curNode.getParent(); 1098 } 1099 // explore siblings if not root 1100 if (curNode != root) { 1101 curNode = curNode.getNextSibling(); 1102 } 1103 } while (curNode != root); 1104 return result; 1105 } 1106 1107 /** 1108 * Checks a set of tags for matching parameters. 1109 * 1110 * @param tags the tags to check 1111 * @param parent the node which takes the parameters 1112 * @param reportExpectedTags whether we should report if do not find 1113 * expected tag 1114 */ 1115 private void checkParamTags(final List<JavadocTag> tags, 1116 final DetailAST parent, boolean reportExpectedTags) { 1117 final List<DetailAST> params = getParameters(parent); 1118 final List<DetailAST> typeParams = CheckUtil 1119 .getTypeParameters(parent); 1120 1121 // Loop over the tags, checking to see they exist in the params. 1122 final ListIterator<JavadocTag> tagIt = tags.listIterator(); 1123 while (tagIt.hasNext()) { 1124 final JavadocTag tag = tagIt.next(); 1125 1126 if (!tag.isParamTag()) { 1127 continue; 1128 } 1129 1130 tagIt.remove(); 1131 1132 final String arg1 = tag.getFirstArg(); 1133 boolean found = removeMatchingParam(params, arg1); 1134 1135 if (CommonUtil.startsWithChar(arg1, '<') && CommonUtil.endsWithChar(arg1, '>')) { 1136 found = searchMatchingTypeParameter(typeParams, 1137 arg1.substring(1, arg1.length() - 1)); 1138 } 1139 1140 // Handle extra JavadocTag 1141 if (!found) { 1142 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG, 1143 "@param", arg1); 1144 } 1145 } 1146 1147 // Now dump out all type parameters/parameters without tags :- unless 1148 // the user has chosen to suppress these problems 1149 if (!allowMissingParamTags && reportExpectedTags) { 1150 for (DetailAST param : params) { 1151 log(param, MSG_EXPECTED_TAG, 1152 JavadocTagInfo.PARAM.getText(), param.getText()); 1153 } 1154 1155 for (DetailAST typeParam : typeParams) { 1156 log(typeParam, MSG_EXPECTED_TAG, 1157 JavadocTagInfo.PARAM.getText(), 1158 "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText() 1159 + ">"); 1160 } 1161 } 1162 } 1163 1164 /** 1165 * Returns true if required type found in type parameters. 1166 * 1167 * @param typeParams 1168 * collection of type parameters 1169 * @param requiredTypeName 1170 * name of required type 1171 * @return true if required type found in type parameters. 1172 */ 1173 private static boolean searchMatchingTypeParameter(Iterable<DetailAST> typeParams, 1174 String requiredTypeName) { 1175 // Loop looking for matching type param 1176 final Iterator<DetailAST> typeParamsIt = typeParams.iterator(); 1177 boolean found = false; 1178 while (typeParamsIt.hasNext()) { 1179 final DetailAST typeParam = typeParamsIt.next(); 1180 if (typeParam.findFirstToken(TokenTypes.IDENT).getText() 1181 .equals(requiredTypeName)) { 1182 found = true; 1183 typeParamsIt.remove(); 1184 break; 1185 } 1186 } 1187 return found; 1188 } 1189 1190 /** 1191 * Remove parameter from params collection by name. 1192 * 1193 * @param params collection of DetailAST parameters 1194 * @param paramName name of parameter 1195 * @return true if parameter found and removed 1196 */ 1197 private static boolean removeMatchingParam(Iterable<DetailAST> params, String paramName) { 1198 boolean found = false; 1199 final Iterator<DetailAST> paramIt = params.iterator(); 1200 while (paramIt.hasNext()) { 1201 final DetailAST param = paramIt.next(); 1202 if (param.getText().equals(paramName)) { 1203 found = true; 1204 paramIt.remove(); 1205 break; 1206 } 1207 } 1208 return found; 1209 } 1210 1211 /** 1212 * Checks for only one return tag. All return tags will be removed from the 1213 * supplied list. 1214 * 1215 * @param tags the tags to check 1216 * @param lineNo the line number of the expected tag 1217 * @param reportExpectedTags whether we should report if do not find 1218 * expected tag 1219 */ 1220 private void checkReturnTag(List<JavadocTag> tags, int lineNo, 1221 boolean reportExpectedTags) { 1222 // Loop over tags finding return tags. After the first one, report a 1223 // violation. 1224 boolean found = false; 1225 final ListIterator<JavadocTag> it = tags.listIterator(); 1226 while (it.hasNext()) { 1227 final JavadocTag javadocTag = it.next(); 1228 if (javadocTag.isReturnTag()) { 1229 if (found) { 1230 log(javadocTag.getLineNo(), javadocTag.getColumnNo(), 1231 MSG_DUPLICATE_TAG, 1232 JavadocTagInfo.RETURN.getText()); 1233 } 1234 found = true; 1235 it.remove(); 1236 } 1237 } 1238 1239 // Handle there being no @return tags :- unless 1240 // the user has chosen to suppress these problems 1241 if (!found && !allowMissingReturnTag && reportExpectedTags) { 1242 log(lineNo, MSG_RETURN_EXPECTED); 1243 } 1244 } 1245 1246 /** 1247 * Checks a set of tags for matching throws. 1248 * 1249 * @param tags the tags to check 1250 * @param throwsList the throws to check 1251 * @param reportExpectedTags whether we should report if do not find 1252 * expected tag 1253 */ 1254 private void checkThrowsTags(List<JavadocTag> tags, 1255 List<ExceptionInfo> throwsList, boolean reportExpectedTags) { 1256 // Loop over the tags, checking to see they exist in the throws. 1257 // The foundThrows used for performance only 1258 final Set<String> foundThrows = new HashSet<>(); 1259 final ListIterator<JavadocTag> tagIt = tags.listIterator(); 1260 while (tagIt.hasNext()) { 1261 final JavadocTag tag = tagIt.next(); 1262 1263 if (!tag.isThrowsTag()) { 1264 continue; 1265 } 1266 tagIt.remove(); 1267 1268 // Loop looking for matching throw 1269 final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag 1270 .getColumnNo()); 1271 final ClassInfo documentedClassInfo = new ClassInfo(token); 1272 processThrows(throwsList, documentedClassInfo, foundThrows); 1273 } 1274 // Now dump out all throws without tags :- unless 1275 // the user has chosen to suppress these problems 1276 if (validateThrows && reportExpectedTags) { 1277 throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound()) 1278 .forEach(exceptionInfo -> { 1279 final Token token = exceptionInfo.getName(); 1280 log(exceptionInfo.getAst(), 1281 MSG_EXPECTED_TAG, 1282 JavadocTagInfo.THROWS.getText(), token.getText()); 1283 }); 1284 } 1285 } 1286 1287 /** 1288 * Verifies that documented exception is in throws. 1289 * 1290 * @param throwsIterable collection of throws 1291 * @param documentedClassInfo documented exception class info 1292 * @param foundThrows previously found throws 1293 */ 1294 private static void processThrows(Iterable<ExceptionInfo> throwsIterable, 1295 ClassInfo documentedClassInfo, Set<String> foundThrows) { 1296 ExceptionInfo foundException = null; 1297 1298 // First look for matches on the exception name 1299 for (ExceptionInfo exceptionInfo : throwsIterable) { 1300 if (isClassNamesSame(exceptionInfo.getName().getText(), 1301 documentedClassInfo.getName().getText())) { 1302 foundException = exceptionInfo; 1303 break; 1304 } 1305 } 1306 1307 if (foundException != null) { 1308 foundException.setFound(); 1309 foundThrows.add(documentedClassInfo.getName().getText()); 1310 } 1311 } 1312 1313 /** 1314 * Check that ExceptionInfo objects are same by name. 1315 * 1316 * @param info1 ExceptionInfo object 1317 * @param info2 ExceptionInfo object 1318 * @return true is ExceptionInfo object have the same name 1319 */ 1320 private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) { 1321 return isClassNamesSame(info1.getName().getText(), 1322 info2.getName().getText()); 1323 } 1324 1325 /** 1326 * Check that class names are same by short name of class. If some class name is fully 1327 * qualified it is cut to short name. 1328 * 1329 * @param class1 class name 1330 * @param class2 class name 1331 * @return true is ExceptionInfo object have the same name 1332 */ 1333 private static boolean isClassNamesSame(String class1, String class2) { 1334 boolean result = false; 1335 if (class1.equals(class2)) { 1336 result = true; 1337 } 1338 else { 1339 final String separator = "."; 1340 if (class1.contains(separator) || class2.contains(separator)) { 1341 final String class1ShortName = class1 1342 .substring(class1.lastIndexOf('.') + 1); 1343 final String class2ShortName = class2 1344 .substring(class2.lastIndexOf('.') + 1); 1345 result = class1ShortName.equals(class2ShortName); 1346 } 1347 } 1348 return result; 1349 } 1350 1351 /** 1352 * Processes class definition. 1353 * 1354 * @param ast class definition to process. 1355 */ 1356 private void processClass(DetailAST ast) { 1357 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 1358 String innerClass = ident.getText(); 1359 1360 innerClass = "$" + innerClass; 1361 currentClassName += innerClass; 1362 } 1363 1364 /** 1365 * Contains class's {@code Token}. 1366 */ 1367 private static class ClassInfo { 1368 1369 /** {@code FullIdent} associated with this class. */ 1370 private final Token name; 1371 1372 /** 1373 * Creates new instance of class information object. 1374 * 1375 * @param className token which represents class name. 1376 * @throws IllegalArgumentException when className is nulls 1377 */ 1378 protected ClassInfo(final Token className) { 1379 name = className; 1380 } 1381 1382 /** 1383 * Gets class name. 1384 * 1385 * @return class name 1386 */ 1387 public final Token getName() { 1388 return name; 1389 } 1390 1391 } 1392 1393 /** 1394 * Represents text element with location in the text. 1395 */ 1396 private static final class Token { 1397 1398 /** Token's column number. */ 1399 private final int columnNo; 1400 /** Token's line number. */ 1401 private final int lineNo; 1402 /** Token's text. */ 1403 private final String text; 1404 1405 /** 1406 * Creates token. 1407 * 1408 * @param text token's text 1409 * @param lineNo token's line number 1410 * @param columnNo token's column number 1411 */ 1412 private Token(String text, int lineNo, int columnNo) { 1413 this.text = text; 1414 this.lineNo = lineNo; 1415 this.columnNo = columnNo; 1416 } 1417 1418 /** 1419 * Converts FullIdent to Token. 1420 * 1421 * @param fullIdent full ident to convert. 1422 */ 1423 private Token(FullIdent fullIdent) { 1424 text = fullIdent.getText(); 1425 lineNo = fullIdent.getLineNo(); 1426 columnNo = fullIdent.getColumnNo(); 1427 } 1428 1429 /** 1430 * Gets text of the token. 1431 * 1432 * @return text of the token 1433 */ 1434 public String getText() { 1435 return text; 1436 } 1437 1438 @Override 1439 public String toString() { 1440 return "Token[" + text + "(" + lineNo 1441 + "x" + columnNo + ")]"; 1442 } 1443 1444 } 1445 1446 /** Stores useful information about declared exception. */ 1447 private static final class ExceptionInfo { 1448 1449 /** AST node representing this exception. */ 1450 private final DetailAST ast; 1451 1452 /** Class information associated with this exception. */ 1453 private final ClassInfo classInfo; 1454 /** Does the exception have throws tag associated with. */ 1455 private boolean found; 1456 1457 /** 1458 * Creates new instance for {@code FullIdent}. 1459 * 1460 * @param ast AST node representing this exception 1461 * @param classInfo class info 1462 */ 1463 private ExceptionInfo(DetailAST ast, ClassInfo classInfo) { 1464 this.ast = ast; 1465 this.classInfo = classInfo; 1466 } 1467 1468 /** 1469 * Gets the AST node representing this exception. 1470 * 1471 * @return the AST node representing this exception 1472 */ 1473 private DetailAST getAst() { 1474 return ast; 1475 } 1476 1477 /** Mark that the exception has associated throws tag. */ 1478 private void setFound() { 1479 found = true; 1480 } 1481 1482 /** 1483 * Checks that the exception has throws tag associated with it. 1484 * 1485 * @return whether the exception has throws tag associated with 1486 */ 1487 private boolean isFound() { 1488 return found; 1489 } 1490 1491 /** 1492 * Gets exception name. 1493 * 1494 * @return exception's name 1495 */ 1496 private Token getName() { 1497 return classInfo.getName(); 1498 } 1499 1500 } 1501 1502}