001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.util.Objects;
023import java.util.regex.Pattern;
024
025import com.puppycrawl.tools.checkstyle.api.AuditEvent;
026import com.puppycrawl.tools.checkstyle.api.Filter;
027
028/**
029 * This filter element is immutable and processes {@link AuditEvent}
030 * objects based on the criteria of file, check, module id, line, and
031 * column. It rejects an AuditEvent if the following match:
032 * <ul>
033 *   <li>the event's file name; and</li>
034 *   <li>the check name or the module identifier; and</li>
035 *   <li>(optionally) the event's line is in the filter's line CSV; and</li>
036 *   <li>(optionally) the check's columns is in the filter's column CSV.</li>
037 * </ul>
038 *
039 */
040public class SuppressFilterElement
041    implements Filter {
042
043    /** The regexp to match file names against. */
044    private final Pattern fileRegexp;
045
046    /** The regexp to match check names against. */
047    private final Pattern checkRegexp;
048
049    /** The regexp to match message names against. */
050    private final Pattern messageRegexp;
051
052    /** Module id filter. */
053    private final String moduleId;
054
055    /** Line number filter. */
056    private final CsvFilterElement lineFilter;
057
058    /** CSV for line number filter. */
059    private final String linesCsv;
060
061    /** Column number filter. */
062    private final CsvFilterElement columnFilter;
063
064    /** CSV for column number filter. */
065    private final String columnsCsv;
066
067    /**
068     * Creates a {@code SuppressFilterElement} instance.
069     *
070     * @param files regular expression for filtered file names
071     * @param checks regular expression for filtered check classes
072     * @param message regular expression for messages.
073     * @param moduleId the module id
074     * @param lines CSV for lines
075     * @param columns CSV for columns
076     */
077    public SuppressFilterElement(Pattern files, Pattern checks, Pattern message, String moduleId,
078            String lines, String columns) {
079        fileRegexp = files;
080        checkRegexp = checks;
081        messageRegexp = message;
082        this.moduleId = moduleId;
083        if (lines == null) {
084            linesCsv = null;
085            lineFilter = null;
086        }
087        else {
088            linesCsv = lines;
089            lineFilter = new CsvFilterElement(lines);
090        }
091        if (columns == null) {
092            columnsCsv = null;
093            columnFilter = null;
094        }
095        else {
096            columnsCsv = columns;
097            columnFilter = new CsvFilterElement(columns);
098        }
099    }
100
101    /**
102     * Constructs a {@code SuppressFilterElement} using regular expressions
103     * as {@code String}s. These are internally compiled into {@code Pattern}
104     * objects and passed to the main constructor.
105     *
106     * @param files   regular expression for names of filtered files.
107     * @param checks  regular expression for filtered check classes.
108     * @param message regular expression for messages.
109     * @param modId   the id
110     * @param lines   lines CSV values and ranges for line number filtering.
111     * @param columns columns CSV values and ranges for column number filtering.
112     */
113    public SuppressFilterElement(String files, String checks,
114                                 String message, String modId, String lines, String columns) {
115        this(toPattern(files), toPattern(checks), toPattern(message),
116                modId, lines, columns);
117    }
118
119    /**
120     * Converts a string into a compiled {@code Pattern}, or return {@code null}
121     * if input is {@code null}.
122     *
123     * @param regex the regular expression as a string, may be {@code null}.
124     * @return the compiled {@code Pattern}, or {@code null} if input is {@code null}.
125     */
126    private static Pattern toPattern(String regex) {
127        final Pattern result;
128        if (regex != null) {
129            result = Pattern.compile(regex);
130        }
131        else {
132            result = null;
133        }
134        return result;
135    }
136
137    @Override
138    public boolean accept(AuditEvent event) {
139        return !isFileNameAndModuleNameMatching(event)
140                || !isMessageNameMatching(event)
141                || !isLineAndColumnMatching(event);
142    }
143
144    /**
145     * Is matching by file name, module id, and Check name.
146     *
147     * @param event event
148     * @return true if it is matching
149     */
150    private boolean isFileNameAndModuleNameMatching(AuditEvent event) {
151        return event.getFileName() != null
152                && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find())
153                && event.getViolation() != null
154                && (moduleId == null || moduleId.equals(event.getModuleId()))
155                && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
156    }
157
158    /**
159     * Is matching by message.
160     *
161     * @param event event
162     * @return true if it is matching or not set.
163     */
164    private boolean isMessageNameMatching(AuditEvent event) {
165        return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find();
166    }
167
168    /**
169     * Whether line and column match.
170     *
171     * @param event event to process.
172     * @return true if line and column are matching or not set.
173     */
174    private boolean isLineAndColumnMatching(AuditEvent event) {
175        return lineFilter == null && columnFilter == null
176                || lineFilter != null && lineFilter.accept(event.getLine())
177                || columnFilter != null && columnFilter.accept(event.getColumn());
178    }
179
180    @Override
181    public int hashCode() {
182        return Objects.hash(getPatternSafely(fileRegexp), getPatternSafely(checkRegexp),
183                getPatternSafely(messageRegexp), moduleId, linesCsv, columnsCsv);
184    }
185
186    @Override
187    public boolean equals(Object other) {
188        if (this == other) {
189            return true;
190        }
191        if (other == null || getClass() != other.getClass()) {
192            return false;
193        }
194        final SuppressFilterElement suppressElement = (SuppressFilterElement) other;
195        return Objects.equals(getPatternSafely(fileRegexp),
196                    getPatternSafely(suppressElement.fileRegexp))
197                && Objects.equals(getPatternSafely(checkRegexp),
198                    getPatternSafely(suppressElement.checkRegexp))
199                && Objects.equals(getPatternSafely(messageRegexp),
200                    getPatternSafely(suppressElement.messageRegexp))
201                && Objects.equals(moduleId, suppressElement.moduleId)
202                && Objects.equals(linesCsv, suppressElement.linesCsv)
203                && Objects.equals(columnsCsv, suppressElement.columnsCsv);
204    }
205
206    /**
207     * Util method to get pattern String value from Pattern object safely, return null if
208     * pattern object is null.
209     *
210     * @param pattern pattern object
211     * @return value of pattern or null
212     */
213    private static String getPatternSafely(Pattern pattern) {
214        String result = null;
215        if (pattern != null) {
216            result = pattern.pattern();
217        }
218        return result;
219    }
220}