001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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     * Constructs a {@code SuppressFilterElement} for a
069     * file name pattern.
070     *
071     * @param files   regular expression for names of filtered files.
072     * @param checks  regular expression for filtered check classes.
073     * @param message regular expression for messages.
074     * @param modId   the id
075     * @param lines   lines CSV values and ranges for line number filtering.
076     * @param columns columns CSV values and ranges for column number filtering.
077     */
078    public SuppressFilterElement(String files, String checks,
079                           String message, String modId, String lines, String columns) {
080        if (files == null) {
081            fileRegexp = null;
082        }
083        else {
084            fileRegexp = Pattern.compile(files);
085        }
086        if (checks == null) {
087            checkRegexp = null;
088        }
089        else {
090            checkRegexp = Pattern.compile(checks);
091        }
092        if (message == null) {
093            messageRegexp = null;
094        }
095        else {
096            messageRegexp = Pattern.compile(message);
097        }
098        moduleId = modId;
099        linesCsv = lines;
100        if (lines == null) {
101            lineFilter = null;
102        }
103        else {
104            lineFilter = new CsvFilterElement(lines);
105        }
106        columnsCsv = columns;
107        if (columns == null) {
108            columnFilter = null;
109        }
110        else {
111            columnFilter = new CsvFilterElement(columns);
112        }
113    }
114
115    /**
116     * Creates a {@code SuppressFilterElement} instance.
117     *
118     * @param files regular expression for filtered file names
119     * @param checks regular expression for filtered check classes
120     * @param message regular expression for messages.
121     * @param moduleId the module id
122     * @param lines CSV for lines
123     * @param columns CSV for columns
124     */
125    public SuppressFilterElement(Pattern files, Pattern checks, Pattern message, String moduleId,
126            String lines, String columns) {
127        fileRegexp = files;
128        checkRegexp = checks;
129        messageRegexp = message;
130        this.moduleId = moduleId;
131        if (lines == null) {
132            linesCsv = null;
133            lineFilter = null;
134        }
135        else {
136            linesCsv = lines;
137            lineFilter = new CsvFilterElement(lines);
138        }
139        if (columns == null) {
140            columnsCsv = null;
141            columnFilter = null;
142        }
143        else {
144            columnsCsv = columns;
145            columnFilter = new CsvFilterElement(columns);
146        }
147    }
148
149    @Override
150    public boolean accept(AuditEvent event) {
151        return !isFileNameAndModuleNameMatching(event)
152                || !isMessageNameMatching(event)
153                || !isLineAndColumnMatching(event);
154    }
155
156    /**
157     * Is matching by file name, module id, and Check name.
158     *
159     * @param event event
160     * @return true if it is matching
161     */
162    private boolean isFileNameAndModuleNameMatching(AuditEvent event) {
163        return event.getFileName() != null
164                && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find())
165                && event.getViolation() != null
166                && (moduleId == null || moduleId.equals(event.getModuleId()))
167                && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
168    }
169
170    /**
171     * Is matching by message.
172     *
173     * @param event event
174     * @return true if it is matching or not set.
175     */
176    private boolean isMessageNameMatching(AuditEvent event) {
177        return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find();
178    }
179
180    /**
181     * Whether line and column match.
182     *
183     * @param event event to process.
184     * @return true if line and column are matching or not set.
185     */
186    private boolean isLineAndColumnMatching(AuditEvent event) {
187        return lineFilter == null && columnFilter == null
188                || lineFilter != null && lineFilter.accept(event.getLine())
189                || columnFilter != null && columnFilter.accept(event.getColumn());
190    }
191
192    @Override
193    public int hashCode() {
194        return Objects.hash(getPatternSafely(fileRegexp), getPatternSafely(checkRegexp),
195                getPatternSafely(messageRegexp), moduleId, linesCsv, columnsCsv);
196    }
197
198    @Override
199    public boolean equals(Object other) {
200        if (this == other) {
201            return true;
202        }
203        if (other == null || getClass() != other.getClass()) {
204            return false;
205        }
206        final SuppressFilterElement suppressElement = (SuppressFilterElement) other;
207        return Objects.equals(getPatternSafely(fileRegexp),
208                    getPatternSafely(suppressElement.fileRegexp))
209                && Objects.equals(getPatternSafely(checkRegexp),
210                    getPatternSafely(suppressElement.checkRegexp))
211                && Objects.equals(getPatternSafely(messageRegexp),
212                    getPatternSafely(suppressElement.messageRegexp))
213                && Objects.equals(moduleId, suppressElement.moduleId)
214                && Objects.equals(linesCsv, suppressElement.linesCsv)
215                && Objects.equals(columnsCsv, suppressElement.columnsCsv);
216    }
217
218    /**
219     * Util method to get pattern String value from Pattern object safely, return null if
220     * pattern object is null.
221     *
222     * @param pattern pattern object
223     * @return value of pattern or null
224     */
225    private static String getPatternSafely(Pattern pattern) {
226        String result = null;
227        if (pattern != null) {
228            result = pattern.pattern();
229        }
230        return result;
231    }
232}