1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.filters;
21
22 import java.util.Objects;
23 import java.util.regex.Pattern;
24
25 import javax.annotation.Nullable;
26
27 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
28 import com.puppycrawl.tools.checkstyle.api.Filter;
29
30 /**
31 * This filter element is immutable and processes {@link AuditEvent}
32 * objects based on the criteria of file, check, module id, line, and
33 * column. It rejects an AuditEvent if the following match:
34 * <ul>
35 * <li>the event's file name; and</li>
36 * <li>the check name or the module identifier; and</li>
37 * <li>(optionally) the event's line is in the filter's line CSV; and</li>
38 * <li>(optionally) the check's columns is in the filter's column CSV.</li>
39 * </ul>
40 * If none of the criteria are configured, the element accepts all events.
41 *
42 */
43 public class SuppressFilterElement
44 implements Filter {
45
46 /** The regexp to match file names against. */
47 private final Pattern fileRegexp;
48
49 /** The regexp to match check names against. */
50 private final Pattern checkRegexp;
51
52 /** The regexp to match message names against. */
53 private final Pattern messageRegexp;
54
55 /** Module id filter. */
56 private final String moduleId;
57
58 /** Line number filter. */
59 private final CsvFilterElement lineFilter;
60
61 /** CSV for line number filter. */
62 private final String linesCsv;
63
64 /** Column number filter. */
65 private final CsvFilterElement columnFilter;
66
67 /** CSV for column number filter. */
68 private final String columnsCsv;
69
70 /**
71 * Creates a {@code SuppressFilterElement} instance.
72 *
73 * @param files regular expression for filtered file names
74 * @param checks regular expression for filtered check classes
75 * @param message regular expression for messages.
76 * @param moduleId the module id
77 * @param lines CSV for lines
78 * @param columns CSV for columns
79 */
80 public SuppressFilterElement(Pattern files, Pattern checks, Pattern message, String moduleId,
81 String lines, String columns) {
82 fileRegexp = files;
83 checkRegexp = checks;
84 messageRegexp = message;
85 this.moduleId = moduleId;
86 if (lines == null) {
87 linesCsv = null;
88 lineFilter = null;
89 }
90 else {
91 linesCsv = lines;
92 lineFilter = new CsvFilterElement(lines);
93 }
94 if (columns == null) {
95 columnsCsv = null;
96 columnFilter = null;
97 }
98 else {
99 columnsCsv = columns;
100 columnFilter = new CsvFilterElement(columns);
101 }
102 }
103
104 /**
105 * Constructs a {@code SuppressFilterElement} using regular expressions
106 * as {@code String}s. These are internally compiled into {@code Pattern}
107 * objects and passed to the main constructor.
108 *
109 * @param files regular expression for names of filtered files.
110 * @param checks regular expression for filtered check classes.
111 * @param message regular expression for messages.
112 * @param modId the id
113 * @param lines lines CSV values and ranges for line number filtering.
114 * @param columns columns CSV values and ranges for column number filtering.
115 */
116 public SuppressFilterElement(String files, String checks,
117 String message, String modId, String lines, String columns) {
118 this(toPattern(files), toPattern(checks), toPattern(message),
119 modId, lines, columns);
120 }
121
122 /**
123 * Converts a string into a compiled {@code Pattern}, or return {@code null}
124 * if input is {@code null}.
125 *
126 * @param regex the regular expression as a string, may be {@code null}.
127 * @return the compiled {@code Pattern}, or {@code null} if input is {@code null}.
128 */
129 private static Pattern toPattern(String regex) {
130 final Pattern result;
131 if (regex != null) {
132 result = Pattern.compile(regex);
133 }
134 else {
135 result = null;
136 }
137 return result;
138 }
139
140 @Override
141 public boolean accept(AuditEvent event) {
142 return isNotConfigured()
143 || !isFileNameAndModuleNameMatching(event)
144 || !isMessageNameMatching(event)
145 || !isLineAndColumnMatching(event);
146 }
147
148 /**
149 * Checks whether none of the suppression criteria have been configured. With every
150 * criterion unset, each individual matching check trivially reports a match, which would
151 * otherwise make {@link #accept(AuditEvent)} reject every event. An element with nothing
152 * to match against must instead accept all events.
153 *
154 * @return true if no suppression criteria are set
155 */
156 private boolean isNotConfigured() {
157 return fileRegexp == null && checkRegexp == null && messageRegexp == null
158 && moduleId == null && lineFilter == null && columnFilter == null;
159 }
160
161 /**
162 * Is matching by file name, module id, and Check name.
163 *
164 * @param event event
165 * @return true if it is matching
166 */
167 private boolean isFileNameAndModuleNameMatching(AuditEvent event) {
168 return event.getFileName() != null
169 && (fileRegexp == null || isFileMatch(event.getFileName()))
170 && event.getViolation() != null
171 && (moduleId == null || moduleId.equals(event.getModuleId()))
172 && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
173 }
174
175 /**
176 * Checks if the given file name matches the file regexp.
177 * If there is no match and the OS uses backslashes (Windows),
178 * it converts backslashes to forward slashes and tries again.
179 *
180 * @param fileName the name of the file to check
181 * @return true if the file matches the regexp
182 */
183 private boolean isFileMatch(String fileName) {
184 return fileRegexp.matcher(fileName).find()
185 || matchAfterSlashNormalization(fileName);
186 }
187
188 /**
189 * Performance optimization: returns the file regex match against the
190 * backslash-normalized file name, but only when normalization changed the
191 * string. {@link String#replace(char, char)} returns the same instance when
192 * there is nothing to replace, so guarding against that case avoids running
193 * the same regex twice on identical input.
194 *
195 * @param fileName the original file name
196 * @return true if the normalized file name matches the file regex
197 */
198 private boolean matchAfterSlashNormalization(String fileName) {
199 final String slashesFileName = fileName.replace('\\', '/');
200 return !slashesFileName.equals(fileName)
201 && fileRegexp.matcher(slashesFileName).find();
202 }
203
204 /**
205 * Is matching by message.
206 *
207 * @param event event
208 * @return true if it is matching or not set.
209 */
210 private boolean isMessageNameMatching(AuditEvent event) {
211 return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find();
212 }
213
214 /**
215 * Whether line and column match.
216 *
217 * @param event event to process.
218 * @return true if line and column are matching or not set.
219 */
220 private boolean isLineAndColumnMatching(AuditEvent event) {
221 return lineFilter == null && columnFilter == null
222 || lineFilter != null && lineFilter.accept(event.getLine())
223 || columnFilter != null && columnFilter.accept(event.getColumn());
224 }
225
226 @Override
227 public int hashCode() {
228 return Objects.hash(getPatternSafely(fileRegexp), getPatternSafely(checkRegexp),
229 getPatternSafely(messageRegexp), moduleId, linesCsv, columnsCsv);
230 }
231
232 @Override
233 public boolean equals(Object other) {
234 if (this == other) {
235 return true;
236 }
237 if (other == null || getClass() != other.getClass()) {
238 return false;
239 }
240 final SuppressFilterElement suppressElement = (SuppressFilterElement) other;
241 return Objects.equals(getPatternSafely(fileRegexp),
242 getPatternSafely(suppressElement.fileRegexp))
243 && Objects.equals(getPatternSafely(checkRegexp),
244 getPatternSafely(suppressElement.checkRegexp))
245 && Objects.equals(getPatternSafely(messageRegexp),
246 getPatternSafely(suppressElement.messageRegexp))
247 && Objects.equals(moduleId, suppressElement.moduleId)
248 && Objects.equals(linesCsv, suppressElement.linesCsv)
249 && Objects.equals(columnsCsv, suppressElement.columnsCsv);
250 }
251
252 /**
253 * Util method to get pattern String value from Pattern object safely, return null if
254 * pattern object is null.
255 *
256 * @param pattern pattern object
257 * @return value of pattern or null
258 */
259 @Nullable
260 private static String getPatternSafely(Pattern pattern) {
261 String result = null;
262 if (pattern != null) {
263 result = pattern.pattern();
264 }
265 return result;
266 }
267
268 }