View Javadoc
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 }