001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.filters; 021 022import java.lang.ref.WeakReference; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.List; 027import java.util.Objects; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030import java.util.regex.PatternSyntaxException; 031 032import com.puppycrawl.tools.checkstyle.PropertyType; 033import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent; 034import com.puppycrawl.tools.checkstyle.TreeWalkerFilter; 035import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 036import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 037import com.puppycrawl.tools.checkstyle.api.FileContents; 038import com.puppycrawl.tools.checkstyle.api.TextBlock; 039import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 040 041/** 042 * <p> 043 * Filter {@code SuppressionCommentFilter} uses pairs of comments to suppress audit events. 044 * </p> 045 * <p> 046 * Rationale: 047 * Sometimes there are legitimate reasons for violating a check. When 048 * this is a matter of the code in question and not personal 049 * preference, the best place to override the policy is in the code 050 * itself. Semi-structured comments can be associated with the check. 051 * This is sometimes superior to a separate suppressions file, which 052 * must be kept up-to-date as the source file is edited. 053 * </p> 054 * <p> 055 * Note that the suppression comment should be put before the violation. 056 * You can use more than one suppression comment each on separate line. 057 * </p> 058 * <p> 059 * Attention: This filter may only be specified within the TreeWalker module 060 * ({@code <module name="TreeWalker"/>}) and only applies to checks which are also 061 * defined within this module. To filter non-TreeWalker checks like {@code RegexpSingleline}, a 062 * <a href="https://checkstyle.org/config_filters.html#SuppressWithPlainTextCommentFilter"> 063 * SuppressWithPlainTextCommentFilter</a> or similar filter must be used. 064 * </p> 065 * <p> 066 * {@code offCommentFormat} and {@code onCommentFormat} must have equal 067 * <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Matcher.html#groupCount()"> 068 * paren counts</a>. 069 * </p> 070 * <p> 071 * SuppressionCommentFilter can suppress Checks that have Treewalker as parent module. 072 * </p> 073 * <ul> 074 * <li> 075 * Property {@code offCommentFormat} - Specify comment pattern to 076 * trigger filter to begin suppression. 077 * Type is {@code java.util.regex.Pattern}. 078 * Default value is {@code "CHECKSTYLE:OFF"}. 079 * </li> 080 * <li> 081 * Property {@code onCommentFormat} - Specify comment pattern to trigger filter to end suppression. 082 * Type is {@code java.util.regex.Pattern}. 083 * Default value is {@code "CHECKSTYLE:ON"}. 084 * </li> 085 * <li> 086 * Property {@code checkFormat} - Specify check pattern to suppress. 087 * Type is {@code java.util.regex.Pattern}. 088 * Default value is {@code ".*"}. 089 * </li> 090 * <li> 091 * Property {@code messageFormat} - Specify message pattern to suppress. 092 * Type is {@code java.util.regex.Pattern}. 093 * Default value is {@code null}. 094 * </li> 095 * <li> 096 * Property {@code idFormat} - Specify check ID pattern to suppress. 097 * Type is {@code java.util.regex.Pattern}. 098 * Default value is {@code null}. 099 * </li> 100 * <li> 101 * Property {@code checkCPP} - Control whether to check C++ style comments ({@code //}). 102 * Type is {@code boolean}. 103 * Default value is {@code true}. 104 * </li> 105 * <li> 106 * Property {@code checkC} - Control whether to check C style comments ({@code /* ... */}). 107 * Type is {@code boolean}. 108 * Default value is {@code true}. 109 * </li> 110 * </ul> 111 * <p> 112 * To configure a filter to suppress audit events between a comment containing 113 * {@code CHECKSTYLE:OFF} and a comment containing {@code CHECKSTYLE:ON}: 114 * </p> 115 * <pre> 116 * <module name="TreeWalker"> 117 * ... 118 * <module name="SuppressionCommentFilter"/> 119 * ... 120 * </module> 121 * </pre> 122 * <p> 123 * To configure a filter to suppress audit events between a comment containing line 124 * {@code BEGIN GENERATED CODE} and a comment containing line {@code END GENERATED CODE}: 125 * </p> 126 * <pre> 127 * <module name="SuppressionCommentFilter"> 128 * <property name="offCommentFormat" value="BEGIN GENERATED CODE"/> 129 * <property name="onCommentFormat" value="END GENERATED CODE"/> 130 * </module> 131 * </pre> 132 * <pre> 133 * //BEGIN GENERATED CODE 134 * @Override 135 * public boolean equals(Object obj) { ... } // No violation events will be reported 136 * 137 * @Override 138 * public int hashCode() { ... } // No violation events will be reported 139 * //END GENERATED CODE 140 * . . . 141 * </pre> 142 * <p> 143 * To configure a filter so that {@code // stop constant check} and 144 * {@code // resume constant check} marks legitimate constant names: 145 * </p> 146 * <pre> 147 * <module name="SuppressionCommentFilter"> 148 * <property name="offCommentFormat" value="stop constant check"/> 149 * <property name="onCommentFormat" value="resume constant check"/> 150 * <property name="checkFormat" value="ConstantNameCheck"/> 151 * </module> 152 * </pre> 153 * <pre> 154 * //stop constant check 155 * public static final int someConstant; // won't warn here 156 * //resume constant check 157 * public static final int someConstant; // will warn here as constant's name doesn't match the 158 * // pattern "^[A-Z][A-Z0-9]*$" 159 * </pre> 160 * <p> 161 * To configure a filter so that {@code UNUSED OFF: <i>var</i>} and 162 * {@code UNUSED ON: <i>var</i>} marks a variable or parameter known not to be 163 * used by the code by matching the variable name in the message: 164 * </p> 165 * <pre> 166 * <module name="SuppressionCommentFilter"> 167 * <property name="offCommentFormat" value="UNUSED OFF\: (\w+)"/> 168 * <property name="onCommentFormat" value="UNUSED ON\: (\w+)"/> 169 * <property name="checkFormat" value="Unused"/> 170 * <property name="messageFormat" value="^Unused \w+ '$1'.$"/> 171 * </module> 172 * </pre> 173 * <pre> 174 * private static void foo(int a, int b) // UNUSED OFF: b 175 * { 176 * System.out.println(a); 177 * } 178 * 179 * private static void foo1(int a, int b) // UNUSED ON: b 180 * { 181 * System.out.println(a); 182 * } 183 * </pre> 184 * <p> 185 * To configure a filter so that name of suppressed check mentioned in comment 186 * {@code CSOFF: <i>regexp</i>} and {@code CSON: <i>regexp</i>} mark a matching check: 187 * </p> 188 * <pre> 189 * <module name="SuppressionCommentFilter"> 190 * <property name="offCommentFormat" value="CSOFF\: ([\w\|]+)"/> 191 * <property name="onCommentFormat" value="CSON\: ([\w\|]+)"/> 192 * <property name="checkFormat" value="$1"/> 193 * </module> 194 * </pre> 195 * <pre> 196 * public static final int lowerCaseConstant; // CSOFF: ConstantNameCheck 197 * public static final int lowerCaseConstant1; // CSON: ConstantNameCheck 198 * </pre> 199 * <p> 200 * To configure a filter to suppress all audit events between a comment containing 201 * {@code CHECKSTYLE_OFF: ALMOST_ALL} and a comment containing 202 * {@code CHECKSTYLE_OFF: ALMOST_ALL} except for the <em>EqualsHashCode</em> check: 203 * </p> 204 * <pre> 205 * <module name="SuppressionCommentFilter"> 206 * <property name="offCommentFormat" value="CHECKSTYLE_OFF: ALMOST_ALL"/> 207 * <property name="onCommentFormat" value="CHECKSTYLE_ON: ALMOST_ALL"/> 208 * <property name="checkFormat" value="^((?!(EqualsHashCode)).)*$"/> 209 * </module> 210 * </pre> 211 * <pre> 212 * public static final int array []; // CHECKSTYLE_OFF: ALMOST_ALL 213 * private String [] strArray; 214 * private int array1 []; // CHECKSTYLE_ON: ALMOST_ALL 215 * </pre> 216 * <p> 217 * To configure a filter to suppress Check's violation message 218 * <b>which matches specified message in messageFormat</b> 219 * (so suppression will be not only by Check's name, but by message text 220 * additionally, as the same Check could report different by message format violations) 221 * between a comment containing {@code stop} and comment containing {@code resume}: 222 * </p> 223 * <pre> 224 * <module name="SuppressionCommentFilter"> 225 * <property name="offCommentFormat" value="stop"/> 226 * <property name="onCommentFormat" value="resume"/> 227 * <property name="checkFormat" value="IllegalTypeCheck"/> 228 * <property name="messageFormat" 229 * value="^Declaring variables, return values or parameters of type 'GregorianCalendar' 230 * is not allowed.$"/> 231 * </module> 232 * </pre> 233 * <p> 234 * Code before filter above is applied with Check's audit events: 235 * </p> 236 * <pre> 237 * ... 238 * // Warning below: Declaring variables, return values or parameters of type 'GregorianCalendar' 239 * // is not allowed. 240 * GregorianCalendar calendar; 241 * // Warning below here: Declaring variables, return values or parameters of type 'HashSet' 242 * // is not allowed. 243 * HashSet hashSet; 244 * ... 245 * </pre> 246 * <p> 247 * Code after filter is applied: 248 * </p> 249 * <pre> 250 * ... 251 * //stop 252 * GregorianCalendar calendar; // No warning here as it is suppressed by filter. 253 * HashSet hashSet; 254 * // Warning above here: Declaring variables, return values or parameters of type 'HashSet' 255 * //is not allowed. 256 * 257 * //resume 258 * ... 259 * </pre> 260 * <p> 261 * It is possible to specify an ID of checks, so that it can be leveraged by the 262 * SuppressionCommentFilter to skip validations. The following examples show how 263 * to skip validations near code that is surrounded with {@code // CSOFF <ID> (reason)} 264 * and {@code // CSON <ID>}, where ID is the ID of checks you want to suppress. 265 * </p> 266 * <p> 267 * Examples of Checkstyle checks configuration: 268 * </p> 269 * <pre> 270 * <module name="RegexpSinglelineJava"> 271 * <property name="id" value="ignore"/> 272 * <property name="format" value="^.*@Ignore\s*$"/> 273 * <property name="message" value="@Ignore should have a reason."/> 274 * </module> 275 * 276 * <module name="RegexpSinglelineJava"> 277 * <property name="id" value="systemout"/> 278 * <property name="format" value="^.*System\.(out|err).*$"/> 279 * <property name="message" value="Don't use System.out/err, use SLF4J instead."/> 280 * </module> 281 * </pre> 282 * <p> 283 * Example of SuppressionCommentFilter configuration (checkFormat which is set 284 * to '$1' points that ID of the checks is in the first group of offCommentFormat 285 * and onCommentFormat regular expressions): 286 * </p> 287 * <pre> 288 * <module name="SuppressionCommentFilter"> 289 * <property name="offCommentFormat" value="CSOFF (\w+) \(\w+\)"/> 290 * <property name="onCommentFormat" value="CSON (\w+)"/> 291 * <property name="idFormat" value="$1"/> 292 * </module> 293 * </pre> 294 * <pre> 295 * // CSOFF ignore (test has not been implemented yet) 296 * @Ignore // should NOT fail RegexpSinglelineJava 297 * @Test 298 * public void testMethod() { } 299 * // CSON ignore 300 * 301 * // CSOFF systemout (debug) 302 * public static void foo() { 303 * System.out.println("Debug info."); // should NOT fail RegexpSinglelineJava 304 * } 305 * // CSON systemout 306 * </pre> 307 * <p> 308 * Example of how to configure the check to suppress more than one checks. 309 * </p> 310 * <pre> 311 * <module name="SuppressionCommentFilter"> 312 * <property name="offCommentFormat" value="@cs-\: ([\w\|]+)"/> 313 * <property name="checkFormat" value="$1"/> 314 * </module> 315 * </pre> 316 * <pre> 317 * // @cs-: ClassDataAbstractionCoupling 318 * // @cs-: MagicNumber 319 * @Service // no violations from ClassDataAbstractionCoupling here 320 * @Transactional 321 * public class UserService { 322 * private int value = 10022; // no violations from MagicNumber here 323 * } 324 * </pre> 325 * <p> 326 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 327 * </p> 328 * 329 * @since 3.5 330 */ 331public class SuppressionCommentFilter 332 extends AutomaticBean 333 implements TreeWalkerFilter { 334 335 /** 336 * Enum to be used for switching checkstyle reporting for tags. 337 */ 338 public enum TagType { 339 340 /** 341 * Switch reporting on. 342 */ 343 ON, 344 /** 345 * Switch reporting off. 346 */ 347 OFF, 348 349 } 350 351 /** Turns checkstyle reporting off. */ 352 private static final String DEFAULT_OFF_FORMAT = "CHECKSTYLE:OFF"; 353 354 /** Turns checkstyle reporting on. */ 355 private static final String DEFAULT_ON_FORMAT = "CHECKSTYLE:ON"; 356 357 /** Control all checks. */ 358 private static final String DEFAULT_CHECK_FORMAT = ".*"; 359 360 /** Tagged comments. */ 361 private final List<Tag> tags = new ArrayList<>(); 362 363 /** Control whether to check C style comments ({@code /* ... */}). */ 364 private boolean checkC = true; 365 366 /** Control whether to check C++ style comments ({@code //}). */ 367 // -@cs[AbbreviationAsWordInName] we can not change it as, 368 // Check property is a part of API (used in configurations) 369 private boolean checkCPP = true; 370 371 /** Specify comment pattern to trigger filter to begin suppression. */ 372 private Pattern offCommentFormat = Pattern.compile(DEFAULT_OFF_FORMAT); 373 374 /** Specify comment pattern to trigger filter to end suppression. */ 375 private Pattern onCommentFormat = Pattern.compile(DEFAULT_ON_FORMAT); 376 377 /** Specify check pattern to suppress. */ 378 @XdocsPropertyType(PropertyType.PATTERN) 379 private String checkFormat = DEFAULT_CHECK_FORMAT; 380 381 /** Specify message pattern to suppress. */ 382 @XdocsPropertyType(PropertyType.PATTERN) 383 private String messageFormat; 384 385 /** Specify check ID pattern to suppress. */ 386 @XdocsPropertyType(PropertyType.PATTERN) 387 private String idFormat; 388 389 /** 390 * References the current FileContents for this filter. 391 * Since this is a weak reference to the FileContents, the FileContents 392 * can be reclaimed as soon as the strong references in TreeWalker 393 * are reassigned to the next FileContents, at which time filtering for 394 * the current FileContents is finished. 395 */ 396 private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null); 397 398 /** 399 * Setter to specify comment pattern to trigger filter to begin suppression. 400 * 401 * @param pattern a pattern. 402 */ 403 public final void setOffCommentFormat(Pattern pattern) { 404 offCommentFormat = pattern; 405 } 406 407 /** 408 * Setter to specify comment pattern to trigger filter to end suppression. 409 * 410 * @param pattern a pattern. 411 */ 412 public final void setOnCommentFormat(Pattern pattern) { 413 onCommentFormat = pattern; 414 } 415 416 /** 417 * Returns FileContents for this filter. 418 * 419 * @return the FileContents for this filter. 420 */ 421 private FileContents getFileContents() { 422 return fileContentsReference.get(); 423 } 424 425 /** 426 * Set the FileContents for this filter. 427 * 428 * @param fileContents the FileContents for this filter. 429 * @noinspection WeakerAccess 430 */ 431 public void setFileContents(FileContents fileContents) { 432 fileContentsReference = new WeakReference<>(fileContents); 433 } 434 435 /** 436 * Setter to specify check pattern to suppress. 437 * 438 * @param format a {@code String} value 439 */ 440 public final void setCheckFormat(String format) { 441 checkFormat = format; 442 } 443 444 /** 445 * Setter to specify message pattern to suppress. 446 * 447 * @param format a {@code String} value 448 */ 449 public void setMessageFormat(String format) { 450 messageFormat = format; 451 } 452 453 /** 454 * Setter to specify check ID pattern to suppress. 455 * 456 * @param format a {@code String} value 457 */ 458 public void setIdFormat(String format) { 459 idFormat = format; 460 } 461 462 /** 463 * Setter to control whether to check C++ style comments ({@code //}). 464 * 465 * @param checkCpp {@code true} if C++ comments are checked. 466 */ 467 // -@cs[AbbreviationAsWordInName] We can not change it as, 468 // check's property is a part of API (used in configurations). 469 public void setCheckCPP(boolean checkCpp) { 470 checkCPP = checkCpp; 471 } 472 473 /** 474 * Setter to control whether to check C style comments ({@code /* ... */}). 475 * 476 * @param checkC {@code true} if C comments are checked. 477 */ 478 public void setCheckC(boolean checkC) { 479 this.checkC = checkC; 480 } 481 482 @Override 483 protected void finishLocalSetup() { 484 // No code by default 485 } 486 487 @Override 488 public boolean accept(TreeWalkerAuditEvent event) { 489 boolean accepted = true; 490 491 if (event.getViolation() != null) { 492 // Lazy update. If the first event for the current file, update file 493 // contents and tag suppressions 494 final FileContents currentContents = event.getFileContents(); 495 496 if (getFileContents() != currentContents) { 497 setFileContents(currentContents); 498 tagSuppressions(); 499 } 500 final Tag matchTag = findNearestMatch(event); 501 accepted = matchTag == null || matchTag.getTagType() == TagType.ON; 502 } 503 return accepted; 504 } 505 506 /** 507 * Finds the nearest comment text tag that matches an audit event. 508 * The nearest tag is before the line and column of the event. 509 * 510 * @param event the {@code TreeWalkerAuditEvent} to match. 511 * @return The {@code Tag} nearest event. 512 */ 513 private Tag findNearestMatch(TreeWalkerAuditEvent event) { 514 Tag result = null; 515 for (Tag tag : tags) { 516 if (tag.getLine() > event.getLine() 517 || tag.getLine() == event.getLine() 518 && tag.getColumn() > event.getColumn()) { 519 break; 520 } 521 if (tag.isMatch(event)) { 522 result = tag; 523 } 524 } 525 return result; 526 } 527 528 /** 529 * Collects all the suppression tags for all comments into a list and 530 * sorts the list. 531 */ 532 private void tagSuppressions() { 533 tags.clear(); 534 final FileContents contents = getFileContents(); 535 if (checkCPP) { 536 tagSuppressions(contents.getSingleLineComments().values()); 537 } 538 if (checkC) { 539 final Collection<List<TextBlock>> cComments = contents 540 .getBlockComments().values(); 541 cComments.forEach(this::tagSuppressions); 542 } 543 Collections.sort(tags); 544 } 545 546 /** 547 * Appends the suppressions in a collection of comments to the full 548 * set of suppression tags. 549 * 550 * @param comments the set of comments. 551 */ 552 private void tagSuppressions(Collection<TextBlock> comments) { 553 for (TextBlock comment : comments) { 554 final int startLineNo = comment.getStartLineNo(); 555 final String[] text = comment.getText(); 556 tagCommentLine(text[0], startLineNo, comment.getStartColNo()); 557 for (int i = 1; i < text.length; i++) { 558 tagCommentLine(text[i], startLineNo + i, 0); 559 } 560 } 561 } 562 563 /** 564 * Tags a string if it matches the format for turning 565 * checkstyle reporting on or the format for turning reporting off. 566 * 567 * @param text the string to tag. 568 * @param line the line number of text. 569 * @param column the column number of text. 570 */ 571 private void tagCommentLine(String text, int line, int column) { 572 final Matcher offMatcher = offCommentFormat.matcher(text); 573 if (offMatcher.find()) { 574 addTag(offMatcher.group(0), line, column, TagType.OFF); 575 } 576 else { 577 final Matcher onMatcher = onCommentFormat.matcher(text); 578 if (onMatcher.find()) { 579 addTag(onMatcher.group(0), line, column, TagType.ON); 580 } 581 } 582 } 583 584 /** 585 * Adds a {@code Tag} to the list of all tags. 586 * 587 * @param text the text of the tag. 588 * @param line the line number of the tag. 589 * @param column the column number of the tag. 590 * @param reportingOn {@code true} if the tag turns checkstyle reporting on. 591 */ 592 private void addTag(String text, int line, int column, TagType reportingOn) { 593 final Tag tag = new Tag(line, column, text, reportingOn, this); 594 tags.add(tag); 595 } 596 597 /** 598 * A Tag holds a suppression comment and its location, and determines 599 * whether the suppression turns checkstyle reporting on or off. 600 */ 601 private static final class Tag 602 implements Comparable<Tag> { 603 604 /** The text of the tag. */ 605 private final String text; 606 607 /** The line number of the tag. */ 608 private final int line; 609 610 /** The column number of the tag. */ 611 private final int column; 612 613 /** Determines whether the suppression turns checkstyle reporting on. */ 614 private final TagType tagType; 615 616 /** The parsed check regexp, expanded for the text of this tag. */ 617 private final Pattern tagCheckRegexp; 618 619 /** The parsed message regexp, expanded for the text of this tag. */ 620 private final Pattern tagMessageRegexp; 621 622 /** The parsed check ID regexp, expanded for the text of this tag. */ 623 private final Pattern tagIdRegexp; 624 625 /** 626 * Constructs a tag. 627 * 628 * @param line the line number. 629 * @param column the column number. 630 * @param text the text of the suppression. 631 * @param tagType {@code ON} if the tag turns checkstyle reporting. 632 * @param filter the {@code SuppressionCommentFilter} with the context 633 * @throws IllegalArgumentException if unable to parse expanded text. 634 */ 635 /* package */ Tag(int line, int column, String text, TagType tagType, 636 SuppressionCommentFilter filter) { 637 this.line = line; 638 this.column = column; 639 this.text = text; 640 this.tagType = tagType; 641 642 final Pattern commentFormat; 643 if (this.tagType == TagType.ON) { 644 commentFormat = filter.onCommentFormat; 645 } 646 else { 647 commentFormat = filter.offCommentFormat; 648 } 649 650 // Expand regexp for check and message 651 // Does not intern Patterns with Utils.getPattern() 652 String format = ""; 653 try { 654 format = CommonUtil.fillTemplateWithStringsByRegexp( 655 filter.checkFormat, text, commentFormat); 656 tagCheckRegexp = Pattern.compile(format); 657 658 if (filter.messageFormat == null) { 659 tagMessageRegexp = null; 660 } 661 else { 662 format = CommonUtil.fillTemplateWithStringsByRegexp( 663 filter.messageFormat, text, commentFormat); 664 tagMessageRegexp = Pattern.compile(format); 665 } 666 667 if (filter.idFormat == null) { 668 tagIdRegexp = null; 669 } 670 else { 671 format = CommonUtil.fillTemplateWithStringsByRegexp( 672 filter.idFormat, text, commentFormat); 673 tagIdRegexp = Pattern.compile(format); 674 } 675 } 676 catch (final PatternSyntaxException ex) { 677 throw new IllegalArgumentException( 678 "unable to parse expanded comment " + format, ex); 679 } 680 } 681 682 /** 683 * Returns line number of the tag in the source file. 684 * 685 * @return the line number of the tag in the source file. 686 */ 687 public int getLine() { 688 return line; 689 } 690 691 /** 692 * Determines the column number of the tag in the source file. 693 * Will be 0 for all lines of multiline comment, except the 694 * first line. 695 * 696 * @return the column number of the tag in the source file. 697 */ 698 public int getColumn() { 699 return column; 700 } 701 702 /** 703 * Determines whether the suppression turns checkstyle reporting on or 704 * off. 705 * 706 * @return {@code ON} if the suppression turns reporting on. 707 */ 708 public TagType getTagType() { 709 return tagType; 710 } 711 712 /** 713 * Compares the position of this tag in the file 714 * with the position of another tag. 715 * 716 * @param object the tag to compare with this one. 717 * @return a negative number if this tag is before the other tag, 718 * 0 if they are at the same position, and a positive number if this 719 * tag is after the other tag. 720 */ 721 @Override 722 public int compareTo(Tag object) { 723 final int result; 724 if (line == object.line) { 725 result = Integer.compare(column, object.column); 726 } 727 else { 728 result = Integer.compare(line, object.line); 729 } 730 return result; 731 } 732 733 /** 734 * Indicates whether some other object is "equal to" this one. 735 * Suppression on enumeration is needed so code stays consistent. 736 * 737 * @noinspection EqualsCalledOnEnumConstant 738 */ 739 @Override 740 public boolean equals(Object other) { 741 if (this == other) { 742 return true; 743 } 744 if (other == null || getClass() != other.getClass()) { 745 return false; 746 } 747 final Tag tag = (Tag) other; 748 return Objects.equals(line, tag.line) 749 && Objects.equals(column, tag.column) 750 && Objects.equals(tagType, tag.tagType) 751 && Objects.equals(text, tag.text) 752 && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp) 753 && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp) 754 && Objects.equals(tagIdRegexp, tag.tagIdRegexp); 755 } 756 757 @Override 758 public int hashCode() { 759 return Objects.hash(text, line, column, tagType, tagCheckRegexp, tagMessageRegexp, 760 tagIdRegexp); 761 } 762 763 /** 764 * Determines whether the source of an audit event 765 * matches the text of this tag. 766 * 767 * @param event the {@code TreeWalkerAuditEvent} to check. 768 * @return true if the source of event matches the text of this tag. 769 */ 770 public boolean isMatch(TreeWalkerAuditEvent event) { 771 return isCheckMatch(event) && isIdMatch(event) && isMessageMatch(event); 772 } 773 774 /** 775 * Checks whether {@link TreeWalkerAuditEvent} source name matches the check format. 776 * 777 * @param event {@link TreeWalkerAuditEvent} instance. 778 * @return true if the {@link TreeWalkerAuditEvent} source name matches the check format. 779 */ 780 private boolean isCheckMatch(TreeWalkerAuditEvent event) { 781 final Matcher checkMatcher = tagCheckRegexp.matcher(event.getSourceName()); 782 return checkMatcher.find(); 783 } 784 785 /** 786 * Checks whether the {@link TreeWalkerAuditEvent} module ID matches the ID format. 787 * 788 * @param event {@link TreeWalkerAuditEvent} instance. 789 * @return true if the {@link TreeWalkerAuditEvent} module ID matches the ID format. 790 */ 791 private boolean isIdMatch(TreeWalkerAuditEvent event) { 792 boolean match = true; 793 if (tagIdRegexp != null) { 794 if (event.getModuleId() == null) { 795 match = false; 796 } 797 else { 798 final Matcher idMatcher = tagIdRegexp.matcher(event.getModuleId()); 799 match = idMatcher.find(); 800 } 801 } 802 return match; 803 } 804 805 /** 806 * Checks whether the {@link TreeWalkerAuditEvent} message matches the message format. 807 * 808 * @param event {@link TreeWalkerAuditEvent} instance. 809 * @return true if the {@link TreeWalkerAuditEvent} message matches the message format. 810 */ 811 private boolean isMessageMatch(TreeWalkerAuditEvent event) { 812 boolean match = true; 813 if (tagMessageRegexp != null) { 814 final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage()); 815 match = messageMatcher.find(); 816 } 817 return match; 818 } 819 820 @Override 821 public String toString() { 822 return "Tag[text='" + text + '\'' 823 + ", line=" + line 824 + ", column=" + column 825 + ", type=" + tagType 826 + ", tagCheckRegexp=" + tagCheckRegexp 827 + ", tagMessageRegexp=" + tagMessageRegexp 828 + ", tagIdRegexp=" + tagIdRegexp + ']'; 829 } 830 831 } 832 833}