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 * If none of the criteria are configured, the element accepts all events. 041 * 042 */ 043public class SuppressFilterElement 044 implements Filter { 045 046 /** The regexp to match file names against. */ 047 private final Pattern fileRegexp; 048 049 /** The regexp to match check names against. */ 050 private final Pattern checkRegexp; 051 052 /** The regexp to match message names against. */ 053 private final Pattern messageRegexp; 054 055 /** Module id filter. */ 056 private final String moduleId; 057 058 /** Line number filter. */ 059 private final CsvFilterElement lineFilter; 060 061 /** CSV for line number filter. */ 062 private final String linesCsv; 063 064 /** Column number filter. */ 065 private final CsvFilterElement columnFilter; 066 067 /** CSV for column number filter. */ 068 private final String columnsCsv; 069 070 /** 071 * Creates a {@code SuppressFilterElement} instance. 072 * 073 * @param files regular expression for filtered file names 074 * @param checks regular expression for filtered check classes 075 * @param message regular expression for messages. 076 * @param moduleId the module id 077 * @param lines CSV for lines 078 * @param columns CSV for columns 079 */ 080 public SuppressFilterElement(Pattern files, Pattern checks, Pattern message, String moduleId, 081 String lines, String columns) { 082 fileRegexp = files; 083 checkRegexp = checks; 084 messageRegexp = message; 085 this.moduleId = moduleId; 086 if (lines == null) { 087 linesCsv = null; 088 lineFilter = null; 089 } 090 else { 091 linesCsv = lines; 092 lineFilter = new CsvFilterElement(lines); 093 } 094 if (columns == null) { 095 columnsCsv = null; 096 columnFilter = null; 097 } 098 else { 099 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}