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