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