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