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