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