001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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.io.File;
023import java.io.IOException;
024import java.nio.charset.StandardCharsets;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Objects;
028import java.util.Optional;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031import java.util.regex.PatternSyntaxException;
032
033import com.puppycrawl.tools.checkstyle.api.AuditEvent;
034import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
035import com.puppycrawl.tools.checkstyle.api.FileText;
036import com.puppycrawl.tools.checkstyle.api.Filter;
037import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
038
039/**
040 * <p>
041 * Filter {@code SuppressWithPlainTextCommentFilter} uses plain text to suppress
042 * audit events. The filter can be used only to suppress audit events received
043 * from the checks which implement FileSetCheck interface. In other words, the
044 * checks which have Checker as a parent module. The filter knows nothing about
045 * AST, it treats only plain text comments and extracts the information required
046 * for suppression from the plain text comments. Currently the filter supports
047 * only single line comments.
048 * </p>
049 * <p>
050 * Please, be aware of the fact that, it is not recommended to use the filter
051 * for Java code anymore, however you still are able to use it to suppress audit
052 * events received from the checks which implement FileSetCheck interface.
053 * </p>
054 * <p>
055 * Rationale: Sometimes there are legitimate reasons for violating a check.
056 * When this is a matter of the code in question and not personal preference,
057 * the best place to override the policy is in the code itself. Semi-structured
058 * comments can be associated with the check. This is sometimes superior to
059 * a separate suppressions file, which must be kept up-to-date as the source
060 * file is edited.
061 * </p>
062 * <p>
063 * Note that the suppression comment should be put before the violation.
064 * You can use more than one suppression comment each on separate line.
065 * </p>
066 * <p>
067 * Properties {@code offCommentFormat} and {@code onCommentFormat} must have equal
068 * <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Matcher.html#groupCount()">
069 * paren counts</a>.
070 * </p>
071 * <p>
072 * SuppressionWithPlainTextCommentFilter can suppress Checks that have Treewalker or
073 * Checker as parent module.
074 * </p>
075 * <ul>
076 * <li>
077 * Property {@code offCommentFormat} - Specify comment pattern to trigger filter
078 * to begin suppression.
079 * Type is {@code java.util.regex.Pattern}.
080 * Default value is {@code "// CHECKSTYLE:OFF"}.
081 * </li>
082 * <li>
083 * Property {@code onCommentFormat} - Specify comment pattern to trigger filter
084 * to end suppression.
085 * Type is {@code java.util.regex.Pattern}.
086 * Default value is {@code "// CHECKSTYLE:ON"}.
087 * </li>
088 * <li>
089 * Property {@code checkFormat} - Specify check pattern to suppress.
090 * Type is {@code java.util.regex.Pattern}.
091 * Default value is {@code ".*"}.
092 * </li>
093 * <li>
094 * Property {@code messageFormat} - Specify message pattern to suppress.
095 * Type is {@code java.util.regex.Pattern}.
096 * Default value is {@code null}.
097 * </li>
098 * <li>
099 * Property {@code idFormat} - Specify check ID pattern to suppress.
100 * Type is {@code java.util.regex.Pattern}.
101 * Default value is {@code null}.
102 * </li>
103 * </ul>
104 * <p>
105 * To configure a filter to suppress audit events between a comment containing
106 * {@code CHECKSTYLE:OFF} and a comment containing {@code CHECKSTYLE:ON}:
107 * </p>
108 * <pre>
109 * &lt;module name=&quot;Checker&quot;&gt;
110 *   ...
111 *   &lt;module name=&quot;SuppressWithPlainTextCommentFilter&quot;/&gt;
112 *   ...
113 * &lt;/module&gt;
114 * </pre>
115 * <p>
116 * To configure a filter to suppress audit events between a comment containing
117 * line {@code BEGIN GENERATED CONTENT} and a comment containing line
118 * {@code END GENERATED CONTENT}(Checker is configured to check only properties files):
119 * </p>
120 * <pre>
121 * &lt;module name=&quot;Checker&quot;&gt;
122 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;properties&quot;/&gt;
123 *
124 *   &lt;module name=&quot;SuppressWithPlainTextCommentFilter&quot;&gt;
125 *     &lt;property name=&quot;offCommentFormat&quot; value=&quot;BEGIN GENERATED CONTENT&quot;/&gt;
126 *     &lt;property name=&quot;onCommentFormat&quot; value=&quot;END GENERATED CONTENT&quot;/&gt;
127 *   &lt;/module&gt;
128 *
129 * &lt;/module&gt;
130 * </pre>
131 * <pre>
132 * //BEGIN GENERATED CONTENT
133 * my.property=value1 // No violation events will be reported
134 * my.property=value2 // No violation events will be reported
135 * //END GENERATED CONTENT
136 * . . .
137 * </pre>
138 * <p>
139 * To configure a filter so that {@code -- stop tab check} and {@code -- resume tab check}
140 * marks allowed tab positions (Checker is configured to check only sql files):
141 * </p>
142 * <pre>
143 * &lt;module name=&quot;Checker&quot;&gt;
144 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;sql&quot;/&gt;
145 *
146 *   &lt;module name=&quot;SuppressWithPlainTextCommentFilter&quot;&gt;
147 *     &lt;property name=&quot;offCommentFormat&quot; value=&quot;stop tab check&quot;/&gt;
148 *     &lt;property name=&quot;onCommentFormat&quot; value=&quot;resume tab check&quot;/&gt;
149 *     &lt;property name=&quot;checkFormat&quot; value=&quot;FileTabCharacterCheck&quot;/&gt;
150 *   &lt;/module&gt;
151 *
152 * &lt;/module&gt;
153 * </pre>
154 * <pre>
155 * -- stop tab check
156 *   SELECT * FROM users // won't warn here if there is a tab character on line
157 * -- resume tab check
158 *   SELECT 1 // will warn here if there is a tab character on line
159 * </pre>
160 * <p>
161 * To configure a filter so that name of suppressed check mentioned in comment
162 * {@code CSOFF: <i>regexp</i>} and {@code CSON: <i>regexp</i>} mark a matching
163 * check (Checker is configured to check only xml files):
164 * </p>
165 * <pre>
166 * &lt;module name=&quot;Checker&quot;&gt;
167 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;xml&quot;/&gt;
168 *
169 *   &lt;module name=&quot;SuppressWithPlainTextCommentFilter&quot;&gt;
170 *     &lt;property name=&quot;offCommentFormat&quot; value=&quot;CSOFF\: ([\w\|]+)&quot;/&gt;
171 *     &lt;property name=&quot;onCommentFormat&quot; value=&quot;CSON\: ([\w\|]+)&quot;/&gt;
172 *     &lt;property name=&quot;checkFormat&quot; value=&quot;$1&quot;/&gt;
173 *   &lt;/module&gt;
174 *
175 * &lt;/module&gt;
176 * </pre>
177 * <pre>
178 * // CSOFF: RegexpSinglelineCheck
179 *  // RegexpSingleline check won't warn any lines below here if the line matches regexp
180 * &lt;condition property=&quot;checkstyle.ant.skip&quot;&gt;
181 *   &lt;isset property=&quot;checkstyle.ant.skip&quot;/&gt;
182 * &lt;/condition&gt;
183 * // CSON: RegexpSinglelineCheck
184 * // RegexpSingleline check will warn below here if the line matches regexp
185 * &lt;property name=&quot;checkstyle.pattern.todo&quot; value=&quot;NOTHingWillMatCH_-&quot;/&gt;
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 {@code CHECKSTYLE_OFF: ALMOST_ALL}
190 * except for the <em>EqualsHashCode</em> check (Checker is configured to check only java files):
191 * </p>
192 * <pre>
193 * &lt;module name=&quot;Checker&quot;&gt;
194 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;java&quot;/&gt;
195 *
196 *   &lt;module name=&quot;SuppressWithPlainTextCommentFilter&quot;&gt;
197 *     &lt;property name=&quot;offCommentFormat&quot;
198 *       value=&quot;CHECKSTYLE_OFF: ALMOST_ALL&quot;/&gt;
199 *     &lt;property name=&quot;onCommentFormat&quot;
200 *       value=&quot;CHECKSTYLE_ON: ALMOST_ALL&quot;/&gt;
201 *     &lt;property name=&quot;checkFormat&quot;
202 *       value=&quot;^((?!(FileTabCharacterCheck)).)*$&quot;/&gt;
203 *   &lt;/module&gt;
204 *
205 * &lt;/module&gt;
206 * </pre>
207 * <pre>
208 * // CHECKSTYLE_OFF: ALMOST_ALL
209 * public static final int array [];
210 * private String [] strArray;
211 * // CHECKSTYLE_ON: ALMOST_ALL
212 * private int array1 [];
213 * </pre>
214 * <p>
215 * To configure a filter to suppress Check's violation message <b>which matches
216 * specified message in messageFormat</b>(so suppression will not be only by
217 * Check's name, but also by message text, as the same Check can report violations
218 * with different message format) between a comment containing {@code stop} and
219 * comment containing {@code resume}:
220 * </p>
221 * <pre>
222 * &lt;module name=&quot;Checker&quot;&gt;
223 *   &lt;module name=&quot;SuppressWithPlainTextCommentFilter&quot;&gt;
224 *     &lt;property name=&quot;offCommentFormat&quot; value=&quot;stop&quot;/&gt;
225 *     &lt;property name=&quot;onCommentFormat&quot; value=&quot;resume&quot;/&gt;
226 *     &lt;property name=&quot;checkFormat&quot; value=&quot;FileTabCharacterCheck&quot;/&gt;
227 *     &lt;property name=&quot;messageFormat&quot;
228 *         value=&quot;^File contains tab characters (this is the first instance)\.$&quot;/&gt;
229 *   &lt;/module&gt;
230 * &lt;/module&gt;
231 * </pre>
232 * <p>
233 * It is possible to specify an ID of checks, so that it can be leveraged by the
234 * SuppressWithPlainTextCommentFilter to skip validations. The following examples
235 * show how to skip validations near code that is surrounded with
236 * {@code -- CSOFF &lt;ID&gt; (reason)} and {@code -- CSON &lt;ID&gt;},
237 * where ID is the ID of checks you want to suppress.
238 * </p>
239 * <p>
240 * Examples of Checkstyle checks configuration:
241 * </p>
242 * <pre>
243 * &lt;module name=&quot;RegexpSinglelineJava&quot;&gt;
244 *   &lt;property name=&quot;id&quot; value=&quot;count&quot;/&gt;
245 *   &lt;property name=&quot;format&quot; value=&quot;^.*COUNT(*).*$&quot;/&gt;
246 *   &lt;property name=&quot;message&quot;
247 *     value=&quot;Don't use COUNT(*), use COUNT(1) instead.&quot;/&gt;
248 * &lt;/module&gt;
249 *
250 * &lt;module name=&quot;RegexpSinglelineJava&quot;&gt;
251 *   &lt;property name=&quot;id&quot; value=&quot;join&quot;/&gt;
252 *   &lt;property name=&quot;format&quot; value=&quot;^.*JOIN\s.+\s(ON|USING)$&quot;/&gt;
253 *   &lt;property name=&quot;message&quot;
254 *     value=&quot;Don't use JOIN, use sub-select instead.&quot;/&gt;
255 * &lt;/module&gt;
256 * </pre>
257 * <p>
258 * Example of SuppressWithPlainTextCommentFilter configuration (checkFormat which
259 * is set to '$1' points that ID of the checks is in the first group of offCommentFormat
260 * and onCommentFormat regular expressions):
261 * </p>
262 * <pre>
263 * &lt;module name="Checker"&gt;
264 *   &lt;property name="fileExtensions" value="sql"/&gt;
265 *
266 *   &lt;module name="SuppressWithPlainTextCommentFilter"&gt;
267 *     &lt;property name="offCommentFormat" value="CSOFF (\w+) \(\w+\)"/&gt;
268 *     &lt;property name="onCommentFormat" value="CSON (\w+)"/&gt;
269 *     &lt;property name="idFormat" value="$1"/&gt;
270 *   &lt;/module&gt;
271 *
272 * &lt;/module&gt;
273 * </pre>
274 * <pre>
275 * -- CSOFF join (it is ok to use join here for performance reasons)
276 * SELECT name, job_name
277 * FROM users AS u
278 * JOIN jobs AS j ON u.job_id = j.id
279 * -- CSON join
280 *
281 * -- CSOFF count (test query execution plan)
282 * EXPLAIN SELECT COUNT(*) FROM restaurants
283 * -- CSON count
284 * </pre>
285 * <p>
286 * Example of how to configure the check to suppress more than one check
287 * (Checker is configured to check only sql files).
288 * </p>
289 * <pre>
290 * &lt;module name="Checker"&gt;
291 *   &lt;property name="fileExtensions" value="sql"/&gt;
292 *
293 *   &lt;module name="SuppressWithPlainTextCommentFilter"&gt;
294 *     &lt;property name="offCommentFormat" value="@cs-\: ([\w\|]+)"/&gt;
295 *     &lt;property name="checkFormat" value="$1"/&gt;
296 *   &lt;/module&gt;
297 *
298 * &lt;/module&gt;
299 * </pre>
300 * <pre>
301 * -- @cs-: RegexpSinglelineCheck
302 * -- @cs-: FileTabCharacterCheck
303 * CREATE TABLE STATION (
304 *   ID INTEGER PRIMARY KEY,
305 *   CITY CHAR(20),
306 *   STATE CHAR(2),
307 *   LAT_N REAL,
308 *   LONG_W REAL);
309 * </pre>
310 * <p>
311 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker}
312 * </p>
313 *
314 * @since 8.6
315 */
316public class SuppressWithPlainTextCommentFilter extends AutomaticBean implements Filter {
317
318    /** Comment format which turns checkstyle reporting off. */
319    private static final String DEFAULT_OFF_FORMAT = "// CHECKSTYLE:OFF";
320
321    /** Comment format which turns checkstyle reporting on. */
322    private static final String DEFAULT_ON_FORMAT = "// CHECKSTYLE:ON";
323
324    /** Default check format to suppress. By default the filter suppress all checks. */
325    private static final String DEFAULT_CHECK_FORMAT = ".*";
326
327    /** Specify comment pattern to trigger filter to begin suppression. */
328    private Pattern offCommentFormat = CommonUtil.createPattern(DEFAULT_OFF_FORMAT);
329
330    /** Specify comment pattern to trigger filter to end suppression. */
331    private Pattern onCommentFormat = CommonUtil.createPattern(DEFAULT_ON_FORMAT);
332
333    /** Specify check pattern to suppress. */
334    private String checkFormat = DEFAULT_CHECK_FORMAT;
335
336    /** Specify message pattern to suppress. */
337    private String messageFormat;
338
339    /** Specify check ID pattern to suppress. */
340    private String idFormat;
341
342    /**
343     * Setter to specify comment pattern to trigger filter to begin suppression.
344     *
345     * @param pattern off comment format pattern.
346     */
347    public final void setOffCommentFormat(Pattern pattern) {
348        offCommentFormat = pattern;
349    }
350
351    /**
352     * Setter to specify comment pattern to trigger filter to end suppression.
353     *
354     * @param pattern  on comment format pattern.
355     */
356    public final void setOnCommentFormat(Pattern pattern) {
357        onCommentFormat = pattern;
358    }
359
360    /**
361     * Setter to specify check pattern to suppress.
362     *
363     * @param format pattern for check format.
364     */
365    public final void setCheckFormat(String format) {
366        checkFormat = format;
367    }
368
369    /**
370     * Setter to specify message pattern to suppress.
371     *
372     * @param format pattern for message format.
373     */
374    public final void setMessageFormat(String format) {
375        messageFormat = format;
376    }
377
378    /**
379     * Setter to specify check ID pattern to suppress.
380     *
381     * @param format pattern for check ID format
382     */
383    public final void setIdFormat(String format) {
384        idFormat = format;
385    }
386
387    @Override
388    public boolean accept(AuditEvent event) {
389        boolean accepted = true;
390        if (event.getViolation() != null) {
391            final FileText fileText = getFileText(event.getFileName());
392            if (fileText != null) {
393                final List<Suppression> suppressions = getSuppressions(fileText);
394                accepted = getNearestSuppression(suppressions, event) == null;
395            }
396        }
397        return accepted;
398    }
399
400    @Override
401    protected void finishLocalSetup() {
402        // No code by default
403    }
404
405    /**
406     * Returns {@link FileText} instance created based on the given file name.
407     *
408     * @param fileName the name of the file.
409     * @return {@link FileText} instance.
410     * @throws IllegalStateException if the file could not be read.
411     */
412    private static FileText getFileText(String fileName) {
413        final File file = new File(fileName);
414        FileText result = null;
415
416        // some violations can be on a directory, instead of a file
417        if (!file.isDirectory()) {
418            try {
419                result = new FileText(file, StandardCharsets.UTF_8.name());
420            }
421            catch (IOException ex) {
422                throw new IllegalStateException("Cannot read source file: " + fileName, ex);
423            }
424        }
425
426        return result;
427    }
428
429    /**
430     * Returns the list of {@link Suppression} instances retrieved from the given {@link FileText}.
431     *
432     * @param fileText {@link FileText} instance.
433     * @return list of {@link Suppression} instances.
434     */
435    private List<Suppression> getSuppressions(FileText fileText) {
436        final List<Suppression> suppressions = new ArrayList<>();
437        for (int lineNo = 0; lineNo < fileText.size(); lineNo++) {
438            final Optional<Suppression> suppression = getSuppression(fileText, lineNo);
439            suppression.ifPresent(suppressions::add);
440        }
441        return suppressions;
442    }
443
444    /**
445     * Tries to extract the suppression from the given line.
446     *
447     * @param fileText {@link FileText} instance.
448     * @param lineNo line number.
449     * @return {@link Optional} of {@link Suppression}.
450     */
451    private Optional<Suppression> getSuppression(FileText fileText, int lineNo) {
452        final String line = fileText.get(lineNo);
453        final Matcher onCommentMatcher = onCommentFormat.matcher(line);
454        final Matcher offCommentMatcher = offCommentFormat.matcher(line);
455
456        Suppression suppression = null;
457        if (onCommentMatcher.find()) {
458            suppression = new Suppression(onCommentMatcher.group(0),
459                lineNo + 1, onCommentMatcher.start(), SuppressionType.ON, this);
460        }
461        if (offCommentMatcher.find()) {
462            suppression = new Suppression(offCommentMatcher.group(0),
463                lineNo + 1, offCommentMatcher.start(), SuppressionType.OFF, this);
464        }
465
466        return Optional.ofNullable(suppression);
467    }
468
469    /**
470     * Finds the nearest {@link Suppression} instance which can suppress
471     * the given {@link AuditEvent}. The nearest suppression is the suppression which scope
472     * is before the line and column of the event.
473     *
474     * @param suppressions {@link Suppression} instance.
475     * @param event {@link AuditEvent} instance.
476     * @return {@link Suppression} instance.
477     */
478    private static Suppression getNearestSuppression(List<Suppression> suppressions,
479                                                     AuditEvent event) {
480        return suppressions
481            .stream()
482            .filter(suppression -> suppression.isMatch(event))
483            .reduce((first, second) -> second)
484            .filter(suppression -> suppression.suppressionType != SuppressionType.ON)
485            .orElse(null);
486    }
487
488    /** Enum which represents the type of the suppression. */
489    private enum SuppressionType {
490
491        /** On suppression type. */
492        ON,
493        /** Off suppression type. */
494        OFF,
495
496    }
497
498    /** The class which represents the suppression. */
499    private static final class Suppression {
500
501        /** The regexp which is used to match the event source.*/
502        private final Pattern eventSourceRegexp;
503        /** The regexp which is used to match the event message.*/
504        private final Pattern eventMessageRegexp;
505        /** The regexp which is used to match the event ID.*/
506        private final Pattern eventIdRegexp;
507
508        /** Suppression text.*/
509        private final String text;
510        /** Suppression line.*/
511        private final int lineNo;
512        /** Suppression column number.*/
513        private final int columnNo;
514        /** Suppression type. */
515        private final SuppressionType suppressionType;
516
517        /**
518         * Creates new suppression instance.
519         *
520         * @param text suppression text.
521         * @param lineNo suppression line number.
522         * @param columnNo suppression column number.
523         * @param suppressionType suppression type.
524         * @param filter the {@link SuppressWithPlainTextCommentFilter} with the context.
525         * @throws IllegalArgumentException if there is an error in the filter regex syntax.
526         */
527        /* package */ Suppression(
528            String text,
529            int lineNo,
530            int columnNo,
531            SuppressionType suppressionType,
532            SuppressWithPlainTextCommentFilter filter
533        ) {
534            this.text = text;
535            this.lineNo = lineNo;
536            this.columnNo = columnNo;
537            this.suppressionType = suppressionType;
538
539            final Pattern commentFormat;
540            if (this.suppressionType == SuppressionType.ON) {
541                commentFormat = filter.onCommentFormat;
542            }
543            else {
544                commentFormat = filter.offCommentFormat;
545            }
546
547            // Expand regexp for check and message
548            // Does not intern Patterns with Utils.getPattern()
549            String format = "";
550            try {
551                format = CommonUtil.fillTemplateWithStringsByRegexp(
552                        filter.checkFormat, text, commentFormat);
553                eventSourceRegexp = Pattern.compile(format);
554                if (filter.messageFormat == null) {
555                    eventMessageRegexp = null;
556                }
557                else {
558                    format = CommonUtil.fillTemplateWithStringsByRegexp(
559                            filter.messageFormat, text, commentFormat);
560                    eventMessageRegexp = Pattern.compile(format);
561                }
562                if (filter.idFormat == null) {
563                    eventIdRegexp = null;
564                }
565                else {
566                    format = CommonUtil.fillTemplateWithStringsByRegexp(
567                            filter.idFormat, text, commentFormat);
568                    eventIdRegexp = Pattern.compile(format);
569                }
570            }
571            catch (final PatternSyntaxException ex) {
572                throw new IllegalArgumentException(
573                    "unable to parse expanded comment " + format, ex);
574            }
575        }
576
577        /**
578         * Indicates whether some other object is "equal to" this one.
579         * Suppression on enumeration is needed so code stays consistent.
580         *
581         * @noinspection EqualsCalledOnEnumConstant
582         */
583        @Override
584        public boolean equals(Object other) {
585            if (this == other) {
586                return true;
587            }
588            if (other == null || getClass() != other.getClass()) {
589                return false;
590            }
591            final Suppression suppression = (Suppression) other;
592            return Objects.equals(lineNo, suppression.lineNo)
593                    && Objects.equals(columnNo, suppression.columnNo)
594                    && Objects.equals(suppressionType, suppression.suppressionType)
595                    && Objects.equals(text, suppression.text)
596                    && Objects.equals(eventSourceRegexp, suppression.eventSourceRegexp)
597                    && Objects.equals(eventMessageRegexp, suppression.eventMessageRegexp)
598                    && Objects.equals(eventIdRegexp, suppression.eventIdRegexp);
599        }
600
601        @Override
602        public int hashCode() {
603            return Objects.hash(
604                text, lineNo, columnNo, suppressionType, eventSourceRegexp, eventMessageRegexp,
605                eventIdRegexp);
606        }
607
608        /**
609         * Checks whether the suppression matches the given {@link AuditEvent}.
610         *
611         * @param event {@link AuditEvent} instance.
612         * @return true if the suppression matches {@link AuditEvent}.
613         */
614        private boolean isMatch(AuditEvent event) {
615            return isInScopeOfSuppression(event)
616                    && isCheckMatch(event)
617                    && isIdMatch(event)
618                    && isMessageMatch(event);
619        }
620
621        /**
622         * Checks whether {@link AuditEvent} is in the scope of the suppression.
623         *
624         * @param event {@link AuditEvent} instance.
625         * @return true if {@link AuditEvent} is in the scope of the suppression.
626         */
627        private boolean isInScopeOfSuppression(AuditEvent event) {
628            return lineNo <= event.getLine();
629        }
630
631        /**
632         * Checks whether {@link AuditEvent} source name matches the check format.
633         *
634         * @param event {@link AuditEvent} instance.
635         * @return true if the {@link AuditEvent} source name matches the check format.
636         */
637        private boolean isCheckMatch(AuditEvent event) {
638            final Matcher checkMatcher = eventSourceRegexp.matcher(event.getSourceName());
639            return checkMatcher.find();
640        }
641
642        /**
643         * Checks whether the {@link AuditEvent} module ID matches the ID format.
644         *
645         * @param event {@link AuditEvent} instance.
646         * @return true if the {@link AuditEvent} module ID matches the ID format.
647         */
648        private boolean isIdMatch(AuditEvent event) {
649            boolean match = true;
650            if (eventIdRegexp != null) {
651                if (event.getModuleId() == null) {
652                    match = false;
653                }
654                else {
655                    final Matcher idMatcher = eventIdRegexp.matcher(event.getModuleId());
656                    match = idMatcher.find();
657                }
658            }
659            return match;
660        }
661
662        /**
663         * Checks whether the {@link AuditEvent} message matches the message format.
664         *
665         * @param event {@link AuditEvent} instance.
666         * @return true if the {@link AuditEvent} message matches the message format.
667         */
668        private boolean isMessageMatch(AuditEvent event) {
669            boolean match = true;
670            if (eventMessageRegexp != null) {
671                final Matcher messageMatcher = eventMessageRegexp.matcher(event.getMessage());
672                match = messageMatcher.find();
673            }
674            return match;
675        }
676    }
677
678}