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.List; 026import java.util.Objects; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029import java.util.regex.PatternSyntaxException; 030 031import com.puppycrawl.tools.checkstyle.PropertyType; 032import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent; 033import com.puppycrawl.tools.checkstyle.TreeWalkerFilter; 034import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 035import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 036import com.puppycrawl.tools.checkstyle.api.FileContents; 037import com.puppycrawl.tools.checkstyle.api.TextBlock; 038import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 039 040/** 041 * <p> 042 * Filter {@code SuppressWithNearbyCommentFilter} uses nearby comments to suppress audit events. 043 * </p> 044 * <p> 045 * Rationale: Same as {@code SuppressionCommentFilter}. 046 * Whereas the SuppressionCommentFilter uses matched pairs of filters to turn 047 * on/off comment matching, {@code SuppressWithNearbyCommentFilter} uses single comments. 048 * This requires fewer lines to mark a region, and may be aesthetically preferable in some contexts. 049 * </p> 050 * <p> 051 * Attention: This filter may only be specified within the TreeWalker module 052 * ({@code <module name="TreeWalker"/>}) and only applies to checks which are also 053 * defined within this module. To filter non-TreeWalker checks like {@code RegexpSingleline}, 054 * a <a href="https://checkstyle.org/config_filters.html#SuppressWithPlainTextCommentFilter"> 055 * SuppressWithPlainTextCommentFilter</a> or similar filter must be used. 056 * </p> 057 * <p> 058 * SuppressWithNearbyCommentFilter can suppress Checks that have 059 * Treewalker as parent module. 060 * </p> 061 * <ul> 062 * <li> 063 * Property {@code commentFormat} - Specify comment pattern to trigger filter to begin suppression. 064 * Type is {@code java.util.regex.Pattern}. 065 * Default value is {@code "SUPPRESS CHECKSTYLE (\w+)"}. 066 * </li> 067 * <li> 068 * Property {@code checkFormat} - Specify check pattern to suppress. 069 * Type is {@code java.util.regex.Pattern}. 070 * Default value is {@code ".*"}. 071 * </li> 072 * <li> 073 * Property {@code messageFormat} - Define message pattern to suppress. 074 * Type is {@code java.util.regex.Pattern}. 075 * Default value is {@code null}. 076 * </li> 077 * <li> 078 * Property {@code idFormat} - Specify check ID pattern to suppress. 079 * Type is {@code java.util.regex.Pattern}. 080 * Default value is {@code null}. 081 * </li> 082 * <li> 083 * Property {@code influenceFormat} - Specify negative/zero/positive value that 084 * defines the number of lines preceding/at/following the suppression comment. 085 * Type is {@code java.lang.String}. 086 * Default value is {@code "0"}. 087 * </li> 088 * <li> 089 * Property {@code checkCPP} - Control whether to check C++ style comments ({@code //}). 090 * Type is {@code boolean}. 091 * Default value is {@code true}. 092 * </li> 093 * <li> 094 * Property {@code checkC} - Control whether to check C style comments ({@code /* ... */}). 095 * Type is {@code boolean}. 096 * Default value is {@code true}. 097 * </li> 098 * </ul> 099 * <p> 100 * To configure a filter to suppress audit events for <i>check</i> on any line 101 * with a comment {@code SUPPRESS CHECKSTYLE <i>check</i>}: 102 * </p> 103 * <pre> 104 * <module name="SuppressWithNearbyCommentFilter"/> 105 * </pre> 106 * <pre> 107 * private int [] array; // SUPPRESS CHECKSTYLE 108 * </pre> 109 * <p> 110 * To configure a filter to suppress all audit events on any line containing 111 * the comment {@code CHECKSTYLE IGNORE THIS LINE}: 112 * </p> 113 * <pre> 114 * <module name="SuppressWithNearbyCommentFilter"> 115 * <property name="commentFormat" value="CHECKSTYLE IGNORE THIS LINE"/> 116 * <property name="checkFormat" value=".*"/> 117 * <property name="influenceFormat" value="0"/> 118 * </module> 119 * </pre> 120 * <pre> 121 * public static final int lowerCaseConstant; // CHECKSTYLE IGNORE THIS LINE 122 * </pre> 123 * <p> 124 * To configure a filter so that {@code // OK to catch (Throwable|Exception|RuntimeException) here} 125 * permits the current and previous line to avoid generating an IllegalCatch audit event: 126 * </p> 127 * <pre> 128 * <module name="SuppressWithNearbyCommentFilter"> 129 * <property name="commentFormat" value="OK to catch (\w+) here"/> 130 * <property name="checkFormat" value="IllegalCatchCheck"/> 131 * <property name="messageFormat" value="$1"/> 132 * <property name="influenceFormat" value="-1"/> 133 * </module> 134 * </pre> 135 * <pre> 136 * . . . 137 * catch (RuntimeException re) { 138 * // OK to catch RuntimeException here 139 * } 140 * catch (Throwable th) { ... } 141 * . . . 142 * </pre> 143 * <p> 144 * To configure a filter so that {@code CHECKSTYLE IGNORE <i>check</i> FOR NEXT 145 * <i>var</i> LINES} avoids triggering any audits for the given check for 146 * the current line and the next <i>var</i> lines (for a total of <i>var</i>+1 lines): 147 * </p> 148 * <pre> 149 * <module name="SuppressWithNearbyCommentFilter"> 150 * <property name="commentFormat" 151 * value="CHECKSTYLE IGNORE (\w+) FOR NEXT (\d+) LINES"/> 152 * <property name="checkFormat" value="$1"/> 153 * <property name="influenceFormat" value="$2"/> 154 * </module> 155 * </pre> 156 * <pre> 157 * static final int lowerCaseConstant; // CHECKSTYLE IGNORE ConstantNameCheck FOR NEXT 3 LINES 158 * static final int lowerCaseConstant1; 159 * static final int lowerCaseConstant2; 160 * static final int lowerCaseConstant3; 161 * static final int lowerCaseConstant4; // will warn here 162 * </pre> 163 * <p> 164 * To configure a filter to avoid any audits on code like: 165 * </p> 166 * <pre> 167 * <module name="SuppressWithNearbyCommentFilter"> 168 * <property name="commentFormat" 169 * value="ALLOW (\\w+) ON PREVIOUS LINE"/> 170 * <property name="checkFormat" value="$1"/> 171 * <property name="influenceFormat" value="-1"/> 172 * </module> 173 * </pre> 174 * <pre> 175 * private int D2; 176 * // ALLOW MemberName ON PREVIOUS LINE 177 * . . . 178 * </pre> 179 * <p> 180 * To configure a filter to allow suppress one or more Checks (separated by "|") 181 * and demand comment no less than 14 symbols: 182 * </p> 183 * <pre> 184 * <module name="SuppressWithNearbyCommentFilter"> 185 * <property name="commentFormat" 186 * value="@cs\.suppress \[(\w+(\|\w+)*)\] \w[-\.'`,:;\w ]{14,}"/> 187 * <property name="checkFormat" value="$1"/> 188 * <property name="influenceFormat" value="1"/> 189 * </module> 190 * </pre> 191 * <pre> 192 * public static final int [] array; // @cs.suppress [ConstantName|NoWhitespaceAfter] A comment here 193 * </pre> 194 * <p> 195 * It is possible to specify an ID of checks, so that it can be leveraged by 196 * the SuppressWithNearbyCommentFilter to skip validations. The following examples show how to skip 197 * validations near code that has comment like {@code // @cs-: <ID/> (reason)}, 198 * where ID is the ID of checks you want to suppress. 199 * </p> 200 * <p> 201 * Examples of Checkstyle checks configuration: 202 * </p> 203 * <pre> 204 * <module name="RegexpSinglelineJava"> 205 * <property name="id" value="ignore"/> 206 * <property name="format" value="^.*@Ignore\s*$"/> 207 * <property name="message" value="@Ignore should have a reason."/> 208 * </module> 209 * 210 * <module name="RegexpSinglelineJava"> 211 * <property name="id" value="systemout"/> 212 * <property name="format" value="^.*System\.(out|err).*$"/> 213 * <property name="message" value="Don't use System.out/err, use SLF4J instead."/> 214 * </module> 215 * </pre> 216 * <p> 217 * Example of SuppressWithNearbyCommentFilter configuration (idFormat which is set to 218 * '$1' points that ID of the checks is in the first group of commentFormat regular expressions): 219 * </p> 220 * <pre> 221 * <module name="SuppressWithNearbyCommentFilter"> 222 * <property name="commentFormat" value="@cs-: (\w+) \(.*\)"/> 223 * <property name="idFormat" value="$1"/> 224 * <property name="influenceFormat" value="0"/> 225 * </module> 226 * </pre> 227 * <pre> 228 * @Ignore // @cs-: ignore (test has not been implemented yet) 229 * @Test 230 * public void testMethod() { } 231 * 232 * public static void foo() { 233 * System.out.println("Debug info."); // @cs-: systemout (should not fail RegexpSinglelineJava) 234 * } 235 * </pre> 236 * <p> 237 * Example of how to configure the check to suppress more than one checks. 238 * The influence format format is specified in the second regexp group. 239 * </p> 240 * <pre> 241 * <module name="SuppressWithNearbyCommentFilter"> 242 * <property name="commentFormat" value="@cs-\: ([\w\|]+) influence (\d+)"/> 243 * <property name="checkFormat" value="$1"/> 244 * <property name="influenceFormat" value="$2"/> 245 * </module> 246 * </pre> 247 * <pre> 248 * // @cs-: ClassDataAbstractionCoupling influence 2 249 * // @cs-: MagicNumber influence 4 250 * @Service // no violations from ClassDataAbstractionCoupling here 251 * @Transactional 252 * public class UserService { 253 * private int value = 10022; // no violations from MagicNumber here 254 * } 255 * </pre> 256 * <p> 257 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 258 * </p> 259 * 260 * @since 5.0 261 */ 262public class SuppressWithNearbyCommentFilter 263 extends AutomaticBean 264 implements TreeWalkerFilter { 265 266 /** Format to turn checkstyle reporting off. */ 267 private static final String DEFAULT_COMMENT_FORMAT = 268 "SUPPRESS CHECKSTYLE (\\w+)"; 269 270 /** Default regex for checks that should be suppressed. */ 271 private static final String DEFAULT_CHECK_FORMAT = ".*"; 272 273 /** Default regex for lines that should be suppressed. */ 274 private static final String DEFAULT_INFLUENCE_FORMAT = "0"; 275 276 /** Tagged comments. */ 277 private final List<Tag> tags = new ArrayList<>(); 278 279 /** Control whether to check C style comments ({@code /* ... */}). */ 280 private boolean checkC = true; 281 282 /** Control whether to check C++ style comments ({@code //}). */ 283 // -@cs[AbbreviationAsWordInName] We can not change it as, 284 // check's property is a part of API (used in configurations). 285 private boolean checkCPP = true; 286 287 /** Specify comment pattern to trigger filter to begin suppression. */ 288 private Pattern commentFormat = Pattern.compile(DEFAULT_COMMENT_FORMAT); 289 290 /** Specify check pattern to suppress. */ 291 @XdocsPropertyType(PropertyType.PATTERN) 292 private String checkFormat = DEFAULT_CHECK_FORMAT; 293 294 /** Define message pattern to suppress. */ 295 @XdocsPropertyType(PropertyType.PATTERN) 296 private String messageFormat; 297 298 /** Specify check ID pattern to suppress. */ 299 @XdocsPropertyType(PropertyType.PATTERN) 300 private String idFormat; 301 302 /** 303 * Specify negative/zero/positive value that defines the number of lines 304 * preceding/at/following the suppression comment. 305 */ 306 private String influenceFormat = DEFAULT_INFLUENCE_FORMAT; 307 308 /** 309 * References the current FileContents for this filter. 310 * Since this is a weak reference to the FileContents, the FileContents 311 * can be reclaimed as soon as the strong references in TreeWalker 312 * are reassigned to the next FileContents, at which time filtering for 313 * the current FileContents is finished. 314 */ 315 private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null); 316 317 /** 318 * Setter to specify comment pattern to trigger filter to begin suppression. 319 * 320 * @param pattern a pattern. 321 */ 322 public final void setCommentFormat(Pattern pattern) { 323 commentFormat = pattern; 324 } 325 326 /** 327 * Returns FileContents for this filter. 328 * 329 * @return the FileContents for this filter. 330 */ 331 private FileContents getFileContents() { 332 return fileContentsReference.get(); 333 } 334 335 /** 336 * Set the FileContents for this filter. 337 * 338 * @param fileContents the FileContents for this filter. 339 * @noinspection WeakerAccess 340 */ 341 public void setFileContents(FileContents fileContents) { 342 fileContentsReference = new WeakReference<>(fileContents); 343 } 344 345 /** 346 * Setter to specify check pattern to suppress. 347 * 348 * @param format a {@code String} value 349 */ 350 public final void setCheckFormat(String format) { 351 checkFormat = format; 352 } 353 354 /** 355 * Setter to define message pattern to suppress. 356 * 357 * @param format a {@code String} value 358 */ 359 public void setMessageFormat(String format) { 360 messageFormat = format; 361 } 362 363 /** 364 * Setter to specify check ID pattern to suppress. 365 * 366 * @param format a {@code String} value 367 */ 368 public void setIdFormat(String format) { 369 idFormat = format; 370 } 371 372 /** 373 * Setter to specify negative/zero/positive value that defines the number 374 * of lines preceding/at/following the suppression comment. 375 * 376 * @param format a {@code String} value 377 */ 378 public final void setInfluenceFormat(String format) { 379 influenceFormat = format; 380 } 381 382 /** 383 * Setter to control whether to check C++ style comments ({@code //}). 384 * 385 * @param checkCpp {@code true} if C++ comments are checked. 386 */ 387 // -@cs[AbbreviationAsWordInName] We can not change it as, 388 // check's property is a part of API (used in configurations). 389 public void setCheckCPP(boolean checkCpp) { 390 checkCPP = checkCpp; 391 } 392 393 /** 394 * Setter to control whether to check C style comments ({@code /* ... */}). 395 * 396 * @param checkC {@code true} if C comments are checked. 397 */ 398 public void setCheckC(boolean checkC) { 399 this.checkC = checkC; 400 } 401 402 @Override 403 protected void finishLocalSetup() { 404 // No code by default 405 } 406 407 @Override 408 public boolean accept(TreeWalkerAuditEvent event) { 409 boolean accepted = true; 410 411 if (event.getViolation() != null) { 412 // Lazy update. If the first event for the current file, update file 413 // contents and tag suppressions 414 final FileContents currentContents = event.getFileContents(); 415 416 if (getFileContents() != currentContents) { 417 setFileContents(currentContents); 418 tagSuppressions(); 419 } 420 if (matchesTag(event)) { 421 accepted = false; 422 } 423 } 424 return accepted; 425 } 426 427 /** 428 * Whether current event matches any tag from {@link #tags}. 429 * 430 * @param event TreeWalkerAuditEvent to test match on {@link #tags}. 431 * @return true if event matches any tag from {@link #tags}, false otherwise. 432 */ 433 private boolean matchesTag(TreeWalkerAuditEvent event) { 434 boolean result = false; 435 for (final Tag tag : tags) { 436 if (tag.isMatch(event)) { 437 result = true; 438 break; 439 } 440 } 441 return result; 442 } 443 444 /** 445 * Collects all the suppression tags for all comments into a list and 446 * sorts the list. 447 */ 448 private void tagSuppressions() { 449 tags.clear(); 450 final FileContents contents = getFileContents(); 451 if (checkCPP) { 452 tagSuppressions(contents.getSingleLineComments().values()); 453 } 454 if (checkC) { 455 final Collection<List<TextBlock>> cComments = 456 contents.getBlockComments().values(); 457 cComments.forEach(this::tagSuppressions); 458 } 459 } 460 461 /** 462 * Appends the suppressions in a collection of comments to the full 463 * set of suppression tags. 464 * 465 * @param comments the set of comments. 466 */ 467 private void tagSuppressions(Collection<TextBlock> comments) { 468 for (final TextBlock comment : comments) { 469 final int startLineNo = comment.getStartLineNo(); 470 final String[] text = comment.getText(); 471 tagCommentLine(text[0], startLineNo); 472 for (int i = 1; i < text.length; i++) { 473 tagCommentLine(text[i], startLineNo + i); 474 } 475 } 476 } 477 478 /** 479 * Tags a string if it matches the format for turning 480 * checkstyle reporting on or the format for turning reporting off. 481 * 482 * @param text the string to tag. 483 * @param line the line number of text. 484 */ 485 private void tagCommentLine(String text, int line) { 486 final Matcher matcher = commentFormat.matcher(text); 487 if (matcher.find()) { 488 addTag(matcher.group(0), line); 489 } 490 } 491 492 /** 493 * Adds a comment suppression {@code Tag} to the list of all tags. 494 * 495 * @param text the text of the tag. 496 * @param line the line number of the tag. 497 */ 498 private void addTag(String text, int line) { 499 final Tag tag = new Tag(text, line, this); 500 tags.add(tag); 501 } 502 503 /** 504 * A Tag holds a suppression comment and its location. 505 */ 506 private static final class Tag { 507 508 /** The text of the tag. */ 509 private final String text; 510 511 /** The first line where warnings may be suppressed. */ 512 private final int firstLine; 513 514 /** The last line where warnings may be suppressed. */ 515 private final int lastLine; 516 517 /** The parsed check regexp, expanded for the text of this tag. */ 518 private final Pattern tagCheckRegexp; 519 520 /** The parsed message regexp, expanded for the text of this tag. */ 521 private final Pattern tagMessageRegexp; 522 523 /** The parsed check ID regexp, expanded for the text of this tag. */ 524 private final Pattern tagIdRegexp; 525 526 /** 527 * Constructs a tag. 528 * 529 * @param text the text of the suppression. 530 * @param line the line number. 531 * @param filter the {@code SuppressWithNearbyCommentFilter} with the context 532 * @throws IllegalArgumentException if unable to parse expanded text. 533 */ 534 /* package */ Tag(String text, int line, SuppressWithNearbyCommentFilter filter) { 535 this.text = text; 536 537 // Expand regexp for check and message 538 // Does not intern Patterns with Utils.getPattern() 539 String format = ""; 540 try { 541 format = CommonUtil.fillTemplateWithStringsByRegexp( 542 filter.checkFormat, text, filter.commentFormat); 543 tagCheckRegexp = Pattern.compile(format); 544 if (filter.messageFormat == null) { 545 tagMessageRegexp = null; 546 } 547 else { 548 format = CommonUtil.fillTemplateWithStringsByRegexp( 549 filter.messageFormat, text, filter.commentFormat); 550 tagMessageRegexp = Pattern.compile(format); 551 } 552 if (filter.idFormat == null) { 553 tagIdRegexp = null; 554 } 555 else { 556 format = CommonUtil.fillTemplateWithStringsByRegexp( 557 filter.idFormat, text, filter.commentFormat); 558 tagIdRegexp = Pattern.compile(format); 559 } 560 format = CommonUtil.fillTemplateWithStringsByRegexp( 561 filter.influenceFormat, text, filter.commentFormat); 562 563 final int influence = parseInfluence(format, filter.influenceFormat, text); 564 565 if (influence >= 1) { 566 firstLine = line; 567 lastLine = line + influence; 568 } 569 else { 570 firstLine = line + influence; 571 lastLine = line; 572 } 573 } 574 catch (final PatternSyntaxException ex) { 575 throw new IllegalArgumentException( 576 "unable to parse expanded comment " + format, ex); 577 } 578 } 579 580 /** 581 * Gets influence from suppress filter influence format param. 582 * 583 * @param format influence format to parse 584 * @param influenceFormat raw influence format 585 * @param text text of the suppression 586 * @return parsed influence 587 * @throws IllegalArgumentException when unable to parse int in format 588 */ 589 private static int parseInfluence(String format, String influenceFormat, String text) { 590 try { 591 return Integer.parseInt(format); 592 } 593 catch (final NumberFormatException ex) { 594 throw new IllegalArgumentException("unable to parse influence from '" + text 595 + "' using " + influenceFormat, ex); 596 } 597 } 598 599 @Override 600 public boolean equals(Object other) { 601 if (this == other) { 602 return true; 603 } 604 if (other == null || getClass() != other.getClass()) { 605 return false; 606 } 607 final Tag tag = (Tag) other; 608 return Objects.equals(firstLine, tag.firstLine) 609 && Objects.equals(lastLine, tag.lastLine) 610 && Objects.equals(text, tag.text) 611 && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp) 612 && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp) 613 && Objects.equals(tagIdRegexp, tag.tagIdRegexp); 614 } 615 616 @Override 617 public int hashCode() { 618 return Objects.hash(text, firstLine, lastLine, tagCheckRegexp, tagMessageRegexp, 619 tagIdRegexp); 620 } 621 622 /** 623 * Determines whether the source of an audit event 624 * matches the text of this tag. 625 * 626 * @param event the {@code TreeWalkerAuditEvent} to check. 627 * @return true if the source of event matches the text of this tag. 628 */ 629 public boolean isMatch(TreeWalkerAuditEvent event) { 630 return isInScopeOfSuppression(event) 631 && isCheckMatch(event) 632 && isIdMatch(event) 633 && isMessageMatch(event); 634 } 635 636 /** 637 * Checks whether the {@link TreeWalkerAuditEvent} is in the scope of the suppression. 638 * 639 * @param event {@link TreeWalkerAuditEvent} instance. 640 * @return true if the {@link TreeWalkerAuditEvent} is in the scope of the suppression. 641 */ 642 private boolean isInScopeOfSuppression(TreeWalkerAuditEvent event) { 643 final int line = event.getLine(); 644 return line >= firstLine && line <= lastLine; 645 } 646 647 /** 648 * Checks whether {@link TreeWalkerAuditEvent} source name matches the check format. 649 * 650 * @param event {@link TreeWalkerAuditEvent} instance. 651 * @return true if the {@link TreeWalkerAuditEvent} source name matches the check format. 652 */ 653 private boolean isCheckMatch(TreeWalkerAuditEvent event) { 654 final Matcher checkMatcher = tagCheckRegexp.matcher(event.getSourceName()); 655 return checkMatcher.find(); 656 } 657 658 /** 659 * Checks whether the {@link TreeWalkerAuditEvent} module ID matches the ID format. 660 * 661 * @param event {@link TreeWalkerAuditEvent} instance. 662 * @return true if the {@link TreeWalkerAuditEvent} module ID matches the ID format. 663 */ 664 private boolean isIdMatch(TreeWalkerAuditEvent event) { 665 boolean match = true; 666 if (tagIdRegexp != null) { 667 if (event.getModuleId() == null) { 668 match = false; 669 } 670 else { 671 final Matcher idMatcher = tagIdRegexp.matcher(event.getModuleId()); 672 match = idMatcher.find(); 673 } 674 } 675 return match; 676 } 677 678 /** 679 * Checks whether the {@link TreeWalkerAuditEvent} message matches the message format. 680 * 681 * @param event {@link TreeWalkerAuditEvent} instance. 682 * @return true if the {@link TreeWalkerAuditEvent} message matches the message format. 683 */ 684 private boolean isMessageMatch(TreeWalkerAuditEvent event) { 685 boolean match = true; 686 if (tagMessageRegexp != null) { 687 final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage()); 688 match = messageMatcher.find(); 689 } 690 return match; 691 } 692 693 @Override 694 public String toString() { 695 return "Tag[text='" + text + '\'' 696 + ", firstLine=" + firstLine 697 + ", lastLine=" + lastLine 698 + ", tagCheckRegexp=" + tagCheckRegexp 699 + ", tagMessageRegexp=" + tagMessageRegexp 700 + ", tagIdRegexp=" + tagIdRegexp 701 + ']'; 702 } 703 704 } 705 706}