1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 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.checks.regexp; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.util.regex.Pattern; 25 26 import com.puppycrawl.tools.checkstyle.StatelessCheck; 27 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 28 import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 29 import com.puppycrawl.tools.checkstyle.api.FileText; 30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 31 32 /** 33 * <p> 34 * Checks that a specified pattern matches based on file and/or folder path. 35 * It can also be used to verify files 36 * match specific naming patterns not covered by other checks (Ex: properties, 37 * xml, etc.). 38 * </p> 39 * 40 * <p> 41 * When customizing the check, the properties are applied in a specific order. 42 * The fileExtensions property first picks only files that match any of the 43 * specific extensions supplied. Once files are matched against the 44 * fileExtensions, the match property is then used in conjunction with the 45 * patterns to determine if the check is looking for a match or mismatch on 46 * those files. If the fileNamePattern is supplied, the matching is only applied 47 * to the fileNamePattern and not the folderPattern. If no fileNamePattern is 48 * supplied, then matching is applied to the folderPattern only and will result 49 * in all files in a folder to be reported on violations. If no folderPattern is 50 * supplied, then all folders that checkstyle finds are examined for violations. 51 * The ignoreFileNameExtensions property drops the file extension and applies 52 * the fileNamePattern only to the rest of file name. For example, if the file 53 * is named 'test.java' and this property is turned on, the pattern is only 54 * applied to 'test'. 55 * </p> 56 * 57 * <p> 58 * If this check is configured with no properties, then the default behavior of 59 * this check is to report file names with spaces in them. When at least one 60 * pattern property is supplied, the entire check is under the user's control to 61 * allow them to fully customize the behavior. 62 * </p> 63 * 64 * <p> 65 * It is recommended that if you create your own pattern, to also specify a 66 * custom violation message. This allows the violation message printed to be clear what 67 * the violation is, especially if multiple RegexpOnFilename checks are used. 68 * Argument 0 for the message populates the check's folderPattern. Argument 1 69 * for the message populates the check's fileNamePattern. The file name is not 70 * passed as an argument since it is part of CheckStyle's default violation 71 * messages. 72 * </p> 73 * <ul> 74 * <li> 75 * Property {@code fileExtensions} - Specify the file extensions of the files to process. 76 * Type is {@code java.lang.String[]}. 77 * Default value is {@code ""}.</li> 78 * <li> 79 * Property {@code fileNamePattern} - Specify the regular expression to match the file name against. 80 * Type is {@code java.util.regex.Pattern}. 81 * Default value is {@code null}.</li> 82 * <li> 83 * Property {@code folderPattern} - Specify the regular expression to match the folder path against. 84 * Type is {@code java.util.regex.Pattern}. 85 * Default value is {@code null}.</li> 86 * <li> 87 * Property {@code ignoreFileNameExtensions} - Control whether to ignore the file extension for 88 * the file name match. 89 * Type is {@code boolean}. 90 * Default value is {@code false}.</li> 91 * <li> 92 * Property {@code match} - Control whether to look for a match or mismatch on the file name, if 93 * the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 94 * Type is {@code boolean}. 95 * Default value is {@code true}.</li> 96 * </ul> 97 * <p> 98 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker} 99 * </p> 100 * <p> 101 * Violation Message Keys: 102 * </p> 103 * <ul> 104 * <li> 105 * {@code regexp.filename.match} 106 * </li> 107 * <li> 108 * {@code regexp.filename.mismatch} 109 * </li> 110 * </ul> 111 * 112 * @since 6.15 113 */ 114 @StatelessCheck 115 public class RegexpOnFilenameCheck extends AbstractFileSetCheck { 116 117 /** 118 * A key is pointing to the warning message text in "messages.properties" 119 * file. 120 */ 121 public static final String MSG_MATCH = "regexp.filename.match"; 122 /** 123 * A key is pointing to the warning message text in "messages.properties" 124 * file. 125 */ 126 public static final String MSG_MISMATCH = "regexp.filename.mismatch"; 127 128 /** Specify the regular expression to match the folder path against. */ 129 private Pattern folderPattern; 130 /** Specify the regular expression to match the file name against. */ 131 private Pattern fileNamePattern; 132 /** 133 * Control whether to look for a match or mismatch on the file name, 134 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 135 */ 136 private boolean match = true; 137 /** Control whether to ignore the file extension for the file name match. */ 138 private boolean ignoreFileNameExtensions; 139 140 /** 141 * Setter to specify the regular expression to match the folder path against. 142 * 143 * @param folderPattern format of folder. 144 * @since 6.15 145 */ 146 public void setFolderPattern(Pattern folderPattern) { 147 this.folderPattern = folderPattern; 148 } 149 150 /** 151 * Setter to specify the regular expression to match the file name against. 152 * 153 * @param fileNamePattern format of file. 154 * @since 6.15 155 */ 156 public void setFileNamePattern(Pattern fileNamePattern) { 157 this.fileNamePattern = fileNamePattern; 158 } 159 160 /** 161 * Setter to control whether to look for a match or mismatch on the file name, 162 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 163 * 164 * @param match check's option for matching file names. 165 * @since 6.15 166 */ 167 public void setMatch(boolean match) { 168 this.match = match; 169 } 170 171 /** 172 * Setter to control whether to ignore the file extension for the file name match. 173 * 174 * @param ignoreFileNameExtensions check's option for ignoring file extension. 175 * @since 6.15 176 */ 177 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) { 178 this.ignoreFileNameExtensions = ignoreFileNameExtensions; 179 } 180 181 @Override 182 public void init() { 183 if (fileNamePattern == null && folderPattern == null) { 184 fileNamePattern = CommonUtil.createPattern("\\s"); 185 } 186 } 187 188 @Override 189 protected void processFiltered(File file, FileText fileText) throws CheckstyleException { 190 final String fileName = getFileName(file); 191 final String folderPath = getFolderPath(file); 192 193 if (isMatchFolder(folderPath) && isMatchFile(fileName)) { 194 log(); 195 } 196 } 197 198 /** 199 * Retrieves the file name from the given {@code file}. 200 * 201 * @param file Input file to examine. 202 * @return The file name. 203 */ 204 private String getFileName(File file) { 205 String fileName = file.getName(); 206 207 if (ignoreFileNameExtensions) { 208 fileName = CommonUtil.getFileNameWithoutExtension(fileName); 209 } 210 211 return fileName; 212 } 213 214 /** 215 * Retrieves the folder path from the given {@code file}. 216 * 217 * @param file Input file to examine. 218 * @return The folder path. 219 * @throws CheckstyleException if there is an error getting the canonical 220 * path of the {@code file}. 221 */ 222 private static String getFolderPath(File file) throws CheckstyleException { 223 try { 224 return file.getCanonicalFile().getParent(); 225 } 226 catch (IOException ex) { 227 throw new CheckstyleException("unable to create canonical path names for " 228 + file.getAbsolutePath(), ex); 229 } 230 } 231 232 /** 233 * Checks if the given {@code folderPath} matches the specified 234 * {@link #folderPattern}. 235 * 236 * @param folderPath Input folder path to examine. 237 * @return true if they do match. 238 */ 239 private boolean isMatchFolder(String folderPath) { 240 final boolean result; 241 242 // null pattern always matches, regardless of value of 'match' 243 if (folderPattern == null) { 244 result = true; 245 } 246 else { 247 // null pattern means 'match' applies to the folderPattern matching 248 final boolean useMatch = fileNamePattern != null || match; 249 result = folderPattern.matcher(folderPath).find() == useMatch; 250 } 251 252 return result; 253 } 254 255 /** 256 * Checks if the given {@code fileName} matches the specified 257 * {@link #fileNamePattern}. 258 * 259 * @param fileName Input file name to examine. 260 * @return true if they do match. 261 */ 262 private boolean isMatchFile(String fileName) { 263 // null pattern always matches, regardless of value of 'match' 264 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match; 265 } 266 267 /** Logs the violations for the check. */ 268 private void log() { 269 final String folder = getStringOrDefault(folderPattern, ""); 270 final String fileName = getStringOrDefault(fileNamePattern, ""); 271 272 if (match) { 273 log(1, MSG_MATCH, folder, fileName); 274 } 275 else { 276 log(1, MSG_MISMATCH, folder, fileName); 277 } 278 } 279 280 /** 281 * Retrieves the String form of the {@code pattern} or {@code defaultString} 282 * if null. 283 * 284 * @param pattern The pattern to convert. 285 * @param defaultString The result to use if {@code pattern} is null. 286 * @return The String form of the {@code pattern}. 287 */ 288 private static String getStringOrDefault(Pattern pattern, String defaultString) { 289 final String result; 290 291 if (pattern == null) { 292 result = defaultString; 293 } 294 else { 295 result = pattern.toString(); 296 } 297 298 return result; 299 } 300 301 }