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 * <div> 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 * </div> 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 * 98 * <p> 99 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker} 100 * </p> 101 * 102 * <p> 103 * Violation Message Keys: 104 * </p> 105 * <ul> 106 * <li> 107 * {@code regexp.filename.match} 108 * </li> 109 * <li> 110 * {@code regexp.filename.mismatch} 111 * </li> 112 * </ul> 113 * 114 * @since 6.15 115 */ 116 @StatelessCheck 117 public class RegexpOnFilenameCheck extends AbstractFileSetCheck { 118 119 /** 120 * A key is pointing to the warning message text in "messages.properties" 121 * file. 122 */ 123 public static final String MSG_MATCH = "regexp.filename.match"; 124 /** 125 * A key is pointing to the warning message text in "messages.properties" 126 * file. 127 */ 128 public static final String MSG_MISMATCH = "regexp.filename.mismatch"; 129 130 /** Specify the regular expression to match the folder path against. */ 131 private Pattern folderPattern; 132 /** Specify the regular expression to match the file name against. */ 133 private Pattern fileNamePattern; 134 /** 135 * Control whether to look for a match or mismatch on the file name, 136 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 137 */ 138 private boolean match = true; 139 /** Control whether to ignore the file extension for the file name match. */ 140 private boolean ignoreFileNameExtensions; 141 142 /** 143 * Setter to specify the regular expression to match the folder path against. 144 * 145 * @param folderPattern format of folder. 146 * @since 6.15 147 */ 148 public void setFolderPattern(Pattern folderPattern) { 149 this.folderPattern = folderPattern; 150 } 151 152 /** 153 * Setter to specify the regular expression to match the file name against. 154 * 155 * @param fileNamePattern format of file. 156 * @since 6.15 157 */ 158 public void setFileNamePattern(Pattern fileNamePattern) { 159 this.fileNamePattern = fileNamePattern; 160 } 161 162 /** 163 * Setter to control whether to look for a match or mismatch on the file name, 164 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 165 * 166 * @param match check's option for matching file names. 167 * @since 6.15 168 */ 169 public void setMatch(boolean match) { 170 this.match = match; 171 } 172 173 /** 174 * Setter to control whether to ignore the file extension for the file name match. 175 * 176 * @param ignoreFileNameExtensions check's option for ignoring file extension. 177 * @since 6.15 178 */ 179 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) { 180 this.ignoreFileNameExtensions = ignoreFileNameExtensions; 181 } 182 183 @Override 184 public void init() { 185 if (fileNamePattern == null && folderPattern == null) { 186 fileNamePattern = CommonUtil.createPattern("\\s"); 187 } 188 } 189 190 @Override 191 protected void processFiltered(File file, FileText fileText) throws CheckstyleException { 192 final String fileName = getFileName(file); 193 final String folderPath = getFolderPath(file); 194 195 if (isMatchFolder(folderPath) && isMatchFile(fileName)) { 196 log(); 197 } 198 } 199 200 /** 201 * Retrieves the file name from the given {@code file}. 202 * 203 * @param file Input file to examine. 204 * @return The file name. 205 */ 206 private String getFileName(File file) { 207 String fileName = file.getName(); 208 209 if (ignoreFileNameExtensions) { 210 fileName = CommonUtil.getFileNameWithoutExtension(fileName); 211 } 212 213 return fileName; 214 } 215 216 /** 217 * Retrieves the folder path from the given {@code file}. 218 * 219 * @param file Input file to examine. 220 * @return The folder path. 221 * @throws CheckstyleException if there is an error getting the canonical 222 * path of the {@code file}. 223 */ 224 private static String getFolderPath(File file) throws CheckstyleException { 225 try { 226 return file.getCanonicalFile().getParent(); 227 } 228 catch (IOException ex) { 229 throw new CheckstyleException("unable to create canonical path names for " 230 + file.getAbsolutePath(), ex); 231 } 232 } 233 234 /** 235 * Checks if the given {@code folderPath} matches the specified 236 * {@link #folderPattern}. 237 * 238 * @param folderPath Input folder path to examine. 239 * @return true if they do match. 240 */ 241 private boolean isMatchFolder(String folderPath) { 242 final boolean result; 243 244 // null pattern always matches, regardless of value of 'match' 245 if (folderPattern == null) { 246 result = true; 247 } 248 else { 249 // null pattern means 'match' applies to the folderPattern matching 250 final boolean useMatch = fileNamePattern != null || match; 251 result = folderPattern.matcher(folderPath).find() == useMatch; 252 } 253 254 return result; 255 } 256 257 /** 258 * Checks if the given {@code fileName} matches the specified 259 * {@link #fileNamePattern}. 260 * 261 * @param fileName Input file name to examine. 262 * @return true if they do match. 263 */ 264 private boolean isMatchFile(String fileName) { 265 // null pattern always matches, regardless of value of 'match' 266 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match; 267 } 268 269 /** Logs the violations for the check. */ 270 private void log() { 271 final String folder = getStringOrDefault(folderPattern, ""); 272 final String fileName = getStringOrDefault(fileNamePattern, ""); 273 274 if (match) { 275 log(1, MSG_MATCH, folder, fileName); 276 } 277 else { 278 log(1, MSG_MISMATCH, folder, fileName); 279 } 280 } 281 282 /** 283 * Retrieves the String form of the {@code pattern} or {@code defaultString} 284 * if null. 285 * 286 * @param pattern The pattern to convert. 287 * @param defaultString The result to use if {@code pattern} is null. 288 * @return The String form of the {@code pattern}. 289 */ 290 private static String getStringOrDefault(Pattern pattern, String defaultString) { 291 final String result; 292 293 if (pattern == null) { 294 result = defaultString; 295 } 296 else { 297 result = pattern.toString(); 298 } 299 300 return result; 301 } 302 303 }