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