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.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 * @noinspectionreason WeakerAccess - we avoid 'protected' when possible 431 */ 432 public void setFileContents(FileContents fileContents) { 433 fileContentsReference = new WeakReference<>(fileContents); 434 } 435 436 /** 437 * Setter to specify check pattern to suppress. 438 * 439 * @param format a {@code String} value 440 */ 441 public final void setCheckFormat(String format) { 442 checkFormat = format; 443 } 444 445 /** 446 * Setter to specify message pattern to suppress. 447 * 448 * @param format a {@code String} value 449 */ 450 public void setMessageFormat(String format) { 451 messageFormat = format; 452 } 453 454 /** 455 * Setter to specify check ID pattern to suppress. 456 * 457 * @param format a {@code String} value 458 */ 459 public void setIdFormat(String format) { 460 idFormat = format; 461 } 462 463 /** 464 * Setter to control whether to check C++ style comments ({@code //}). 465 * 466 * @param checkCpp {@code true} if C++ comments are checked. 467 */ 468 // -@cs[AbbreviationAsWordInName] We can not change it as, 469 // check's property is a part of API (used in configurations). 470 public void setCheckCPP(boolean checkCpp) { 471 checkCPP = checkCpp; 472 } 473 474 /** 475 * Setter to control whether to check C style comments ({@code /* ... */}). 476 * 477 * @param checkC {@code true} if C comments are checked. 478 */ 479 public void setCheckC(boolean checkC) { 480 this.checkC = checkC; 481 } 482 483 @Override 484 protected void finishLocalSetup() { 485 // No code by default 486 } 487 488 @Override 489 public boolean accept(TreeWalkerAuditEvent event) { 490 boolean accepted = true; 491 492 if (event.getViolation() != null) { 493 // Lazy update. If the first event for the current file, update file 494 // contents and tag suppressions 495 final FileContents currentContents = event.getFileContents(); 496 497 if (getFileContents() != currentContents) { 498 setFileContents(currentContents); 499 tagSuppressions(); 500 } 501 final Tag matchTag = findNearestMatch(event); 502 accepted = matchTag == null || matchTag.getTagType() == TagType.ON; 503 } 504 return accepted; 505 } 506 507 /** 508 * Finds the nearest comment text tag that matches an audit event. 509 * The nearest tag is before the line and column of the event. 510 * 511 * @param event the {@code TreeWalkerAuditEvent} to match. 512 * @return The {@code Tag} nearest event. 513 */ 514 private Tag findNearestMatch(TreeWalkerAuditEvent event) { 515 Tag result = null; 516 for (Tag tag : tags) { 517 final int eventLine = event.getLine(); 518 if (tag.getLine() > eventLine 519 || tag.getLine() == eventLine 520 && tag.getColumn() > event.getColumn()) { 521 break; 522 } 523 if (tag.isMatch(event)) { 524 result = tag; 525 } 526 } 527 return result; 528 } 529 530 /** 531 * Collects all the suppression tags for all comments into a list and 532 * sorts the list. 533 */ 534 private void tagSuppressions() { 535 tags.clear(); 536 final FileContents contents = getFileContents(); 537 if (checkCPP) { 538 tagSuppressions(contents.getSingleLineComments().values()); 539 } 540 if (checkC) { 541 final Collection<List<TextBlock>> cComments = contents 542 .getBlockComments().values(); 543 cComments.forEach(this::tagSuppressions); 544 } 545 Collections.sort(tags); 546 } 547 548 /** 549 * Appends the suppressions in a collection of comments to the full 550 * set of suppression tags. 551 * 552 * @param comments the set of comments. 553 */ 554 private void tagSuppressions(Collection<TextBlock> comments) { 555 for (TextBlock comment : comments) { 556 final int startLineNo = comment.getStartLineNo(); 557 final String[] text = comment.getText(); 558 tagCommentLine(text[0], startLineNo, comment.getStartColNo()); 559 for (int i = 1; i < text.length; i++) { 560 tagCommentLine(text[i], startLineNo + i, 0); 561 } 562 } 563 } 564 565 /** 566 * Tags a string if it matches the format for turning 567 * checkstyle reporting on or the format for turning reporting off. 568 * 569 * @param text the string to tag. 570 * @param line the line number of text. 571 * @param column the column number of text. 572 */ 573 private void tagCommentLine(String text, int line, int column) { 574 final Matcher offMatcher = offCommentFormat.matcher(text); 575 if (offMatcher.find()) { 576 addTag(offMatcher.group(0), line, column, TagType.OFF); 577 } 578 else { 579 final Matcher onMatcher = onCommentFormat.matcher(text); 580 if (onMatcher.find()) { 581 addTag(onMatcher.group(0), line, column, TagType.ON); 582 } 583 } 584 } 585 586 /** 587 * Adds a {@code Tag} to the list of all tags. 588 * 589 * @param text the text of the tag. 590 * @param line the line number of the tag. 591 * @param column the column number of the tag. 592 * @param reportingOn {@code true} if the tag turns checkstyle reporting on. 593 */ 594 private void addTag(String text, int line, int column, TagType reportingOn) { 595 final Tag tag = new Tag(line, column, text, reportingOn, this); 596 tags.add(tag); 597 } 598 599 /** 600 * A Tag holds a suppression comment and its location, and determines 601 * whether the suppression turns checkstyle reporting on or off. 602 */ 603 private static final class Tag 604 implements Comparable<Tag> { 605 606 /** The text of the tag. */ 607 private final String text; 608 609 /** The line number of the tag. */ 610 private final int line; 611 612 /** The column number of the tag. */ 613 private final int column; 614 615 /** Determines whether the suppression turns checkstyle reporting on. */ 616 private final TagType tagType; 617 618 /** The parsed check regexp, expanded for the text of this tag. */ 619 private final Pattern tagCheckRegexp; 620 621 /** The parsed message regexp, expanded for the text of this tag. */ 622 private final Pattern tagMessageRegexp; 623 624 /** The parsed check ID regexp, expanded for the text of this tag. */ 625 private final Pattern tagIdRegexp; 626 627 /** 628 * Constructs a tag. 629 * 630 * @param line the line number. 631 * @param column the column number. 632 * @param text the text of the suppression. 633 * @param tagType {@code ON} if the tag turns checkstyle reporting. 634 * @param filter the {@code SuppressionCommentFilter} with the context 635 * @throws IllegalArgumentException if unable to parse expanded text. 636 */ 637 private Tag(int line, int column, String text, TagType tagType, 638 SuppressionCommentFilter filter) { 639 this.line = line; 640 this.column = column; 641 this.text = text; 642 this.tagType = tagType; 643 644 final Pattern commentFormat; 645 if (this.tagType == TagType.ON) { 646 commentFormat = filter.onCommentFormat; 647 } 648 else { 649 commentFormat = filter.offCommentFormat; 650 } 651 652 // Expand regexp for check and message 653 // Does not intern Patterns with Utils.getPattern() 654 String format = ""; 655 try { 656 format = CommonUtil.fillTemplateWithStringsByRegexp( 657 filter.checkFormat, text, commentFormat); 658 tagCheckRegexp = Pattern.compile(format); 659 660 if (filter.messageFormat == null) { 661 tagMessageRegexp = null; 662 } 663 else { 664 format = CommonUtil.fillTemplateWithStringsByRegexp( 665 filter.messageFormat, text, commentFormat); 666 tagMessageRegexp = Pattern.compile(format); 667 } 668 669 if (filter.idFormat == null) { 670 tagIdRegexp = null; 671 } 672 else { 673 format = CommonUtil.fillTemplateWithStringsByRegexp( 674 filter.idFormat, text, commentFormat); 675 tagIdRegexp = Pattern.compile(format); 676 } 677 } 678 catch (final PatternSyntaxException ex) { 679 throw new IllegalArgumentException( 680 "unable to parse expanded comment " + format, ex); 681 } 682 } 683 684 /** 685 * Returns line number of the tag in the source file. 686 * 687 * @return the line number of the tag in the source file. 688 */ 689 public int getLine() { 690 return line; 691 } 692 693 /** 694 * Determines the column number of the tag in the source file. 695 * Will be 0 for all lines of multiline comment, except the 696 * first line. 697 * 698 * @return the column number of the tag in the source file. 699 */ 700 public int getColumn() { 701 return column; 702 } 703 704 /** 705 * Determines whether the suppression turns checkstyle reporting on or 706 * off. 707 * 708 * @return {@code ON} if the suppression turns reporting on. 709 */ 710 public TagType getTagType() { 711 return tagType; 712 } 713 714 /** 715 * Compares the position of this tag in the file 716 * with the position of another tag. 717 * 718 * @param object the tag to compare with this one. 719 * @return a negative number if this tag is before the other tag, 720 * 0 if they are at the same position, and a positive number if this 721 * tag is after the other tag. 722 */ 723 @Override 724 public int compareTo(Tag object) { 725 final int result; 726 if (line == object.line) { 727 result = Integer.compare(column, object.column); 728 } 729 else { 730 result = Integer.compare(line, object.line); 731 } 732 return result; 733 } 734 735 /** 736 * Indicates whether some other object is "equal to" this one. 737 * Suppression on enumeration is needed so code stays consistent. 738 * 739 * @noinspection EqualsCalledOnEnumConstant 740 * @noinspectionreason EqualsCalledOnEnumConstant - enumeration is needed to keep 741 * code consistent 742 */ 743 @Override 744 public boolean equals(Object other) { 745 if (this == other) { 746 return true; 747 } 748 if (other == null || getClass() != other.getClass()) { 749 return false; 750 } 751 final Tag tag = (Tag) other; 752 return Objects.equals(line, tag.line) 753 && Objects.equals(column, tag.column) 754 && Objects.equals(tagType, tag.tagType) 755 && Objects.equals(text, tag.text) 756 && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp) 757 && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp) 758 && Objects.equals(tagIdRegexp, tag.tagIdRegexp); 759 } 760 761 @Override 762 public int hashCode() { 763 return Objects.hash(text, line, column, tagType, tagCheckRegexp, tagMessageRegexp, 764 tagIdRegexp); 765 } 766 767 /** 768 * Determines whether the source of an audit event 769 * matches the text of this tag. 770 * 771 * @param event the {@code TreeWalkerAuditEvent} to check. 772 * @return true if the source of event matches the text of this tag. 773 */ 774 public boolean isMatch(TreeWalkerAuditEvent event) { 775 return isCheckMatch(event) && isIdMatch(event) && isMessageMatch(event); 776 } 777 778 /** 779 * Checks whether {@link TreeWalkerAuditEvent} source name matches the check format. 780 * 781 * @param event {@link TreeWalkerAuditEvent} instance. 782 * @return true if the {@link TreeWalkerAuditEvent} source name matches the check format. 783 */ 784 private boolean isCheckMatch(TreeWalkerAuditEvent event) { 785 final Matcher checkMatcher = tagCheckRegexp.matcher(event.getSourceName()); 786 return checkMatcher.find(); 787 } 788 789 /** 790 * Checks whether the {@link TreeWalkerAuditEvent} module ID matches the ID format. 791 * 792 * @param event {@link TreeWalkerAuditEvent} instance. 793 * @return true if the {@link TreeWalkerAuditEvent} module ID matches the ID format. 794 */ 795 private boolean isIdMatch(TreeWalkerAuditEvent event) { 796 boolean match = true; 797 if (tagIdRegexp != null) { 798 if (event.getModuleId() == null) { 799 match = false; 800 } 801 else { 802 final Matcher idMatcher = tagIdRegexp.matcher(event.getModuleId()); 803 match = idMatcher.find(); 804 } 805 } 806 return match; 807 } 808 809 /** 810 * Checks whether the {@link TreeWalkerAuditEvent} message matches the message format. 811 * 812 * @param event {@link TreeWalkerAuditEvent} instance. 813 * @return true if the {@link TreeWalkerAuditEvent} message matches the message format. 814 */ 815 private boolean isMessageMatch(TreeWalkerAuditEvent event) { 816 boolean match = true; 817 if (tagMessageRegexp != null) { 818 final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage()); 819 match = messageMatcher.find(); 820 } 821 return match; 822 } 823 824 @Override 825 public String toString() { 826 return "Tag[text='" + text + '\'' 827 + ", line=" + line 828 + ", column=" + column 829 + ", type=" + tagType 830 + ", tagCheckRegexp=" + tagCheckRegexp 831 + ", tagMessageRegexp=" + tagMessageRegexp 832 + ", tagIdRegexp=" + tagIdRegexp + ']'; 833 } 834 835 } 836 837}