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