001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.checks.regexp; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 028import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 029import com.puppycrawl.tools.checkstyle.api.FileText; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 031 032/** 033 * <p> 034 * Checks that a specified pattern matches based on file and/or folder path. 035 * It can also be used to verify files 036 * match specific naming patterns not covered by other checks (Ex: properties, 037 * xml, etc.). 038 * </p> 039 * 040 * <p> 041 * When customizing the check, the properties are applied in a specific order. 042 * The fileExtensions property first picks only files that match any of the 043 * specific extensions supplied. Once files are matched against the 044 * fileExtensions, the match property is then used in conjunction with the 045 * patterns to determine if the check is looking for a match or mismatch on 046 * those files. If the fileNamePattern is supplied, the matching is only applied 047 * to the fileNamePattern and not the folderPattern. If no fileNamePattern is 048 * supplied, then matching is applied to the folderPattern only and will result 049 * in all files in a folder to be reported on violations. If no folderPattern is 050 * supplied, then all folders that checkstyle finds are examined for violations. 051 * The ignoreFileNameExtensions property drops the file extension and applies 052 * the fileNamePattern only to the rest of file name. For example, if the file 053 * is named 'test.java' and this property is turned on, the pattern is only 054 * applied to 'test'. 055 * </p> 056 * 057 * <p> 058 * If this check is configured with no properties, then the default behavior of 059 * this check is to report file names with spaces in them. When at least one 060 * pattern property is supplied, the entire check is under the user's control to 061 * allow them to fully customize the behavior. 062 * </p> 063 * 064 * <p> 065 * It is recommended that if you create your own pattern, to also specify a 066 * custom violation message. This allows the violation message printed to be clear what 067 * the violation is, especially if multiple RegexpOnFilename checks are used. 068 * Argument 0 for the message populates the check's folderPattern. Argument 1 069 * for the message populates the check's fileNamePattern. The file name is not 070 * passed as an argument since it is part of CheckStyle's default violation 071 * messages. 072 * </p> 073 * <ul> 074 * <li> 075 * Property {@code folderPattern} - Specify the regular expression to match the folder path against. 076 * Type is {@code java.util.regex.Pattern}. 077 * Default value is {@code null}.</li> 078 * 079 * <li> 080 * Property {@code fileNamePattern} - Specify the regular expression to match the file name against. 081 * Type is {@code java.util.regex.Pattern}. 082 * Default value is {@code null}.</li> 083 * 084 * <li> 085 * Property {@code match} - Control whether to look for a match or mismatch on the file name, if 086 * the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 087 * Type is {@code boolean}. 088 * Default value is {@code true}.</li> 089 * 090 * <li> 091 * Property {@code ignoreFileNameExtensions} - Control whether to ignore the file extension for 092 * the file name match. 093 * Type is {@code boolean}. 094 * Default value is {@code false}.</li> 095 * 096 * <li> 097 * Property {@code fileExtensions} - Specify the file type extension of files to process. If this is 098 * specified, then only files that match these types are examined with the other 099 * patterns. 100 * Type is {@code java.lang.String[]}. 101 * Default value is {@code ""}.</li> 102 * </ul> 103 * <p> 104 * To configure the check to report file names that contain a space: 105 * </p> 106 * 107 * <pre> 108 * <module name="RegexpOnFilename"/> 109 * </pre> 110 * 111 * <p>Example:</p> 112 * <pre> 113 * src/xdocs/config_regexp.xml //OK, contains no whitespace 114 * src/xdocs/"config regexp".xml //violation, contains whitespace 115 * </pre> 116 * 117 * <p> 118 * To configure the check to forbid 'gif' files in folders: 119 * </p> 120 * 121 * <pre> 122 * <module name="RegexpOnFilename"> 123 * <property name="fileNamePattern" value="\.gif$"/> 124 * </module> 125 * </pre> 126 * 127 * <p>Example:</p> 128 * <pre> 129 * src/site/resources/images/favicon.png //OK 130 * src/site/resources/images/logo.jpg //OK 131 * src/site/resources/images/groups.gif //violation, .gif images not allowed 132 * </pre> 133 * 134 * <p> 135 * To configure the check to forbid 'md' files except 'README.md file' in folders, 136 * with custom message: 137 * </p> 138 * 139 * <pre> 140 * <module name="RegexpOnFilename"> 141 * <property name="fileNamePattern" value="README"/> 142 * <property name="fileExtensions" value="md"/> 143 * <property name="match" value="false"/> 144 * <message key="regexp.filename.mismatch" 145 * value="No '*.md' files other then 'README.md'"/> 146 * </module> 147 * </pre> 148 * 149 * <p>Example:</p> 150 * <pre> 151 * src/site/resources/README.md //OK 152 * src/site/resources/Logo.png //OK 153 * src/site/resources/Text.md //violation, .md files other than 'README.md' are not allowed 154 * </pre> 155 * 156 * <p> 157 * To configure the check to only allow property and xml files to be located in 158 * the resource folder: 159 * </p> 160 * 161 * <pre> 162 * <module name="RegexpOnFilename"> 163 * <property name="folderPattern" 164 * value="[\\/]src[\\/]\w+[\\/]resources[\\/]"/> 165 * <property name="match" value="false"/> 166 * <property name="fileExtensions" value="properties, xml"/> 167 * </module> 168 * </pre> 169 * 170 * <p>Example:</p> 171 * <pre> 172 * src/main/resources/sun_checks.xml //OK 173 * src/main/resources/check_properties.properties //OK 174 * src/main/resources/JavaClass.java //violation, xml|property files are allowed in resource folder 175 * </pre> 176 * 177 * <p> 178 * To configure the check to only allow Java and XML files in your folders use 179 * the below. 180 * </p> 181 * 182 * <pre> 183 * <module name="RegexpOnFilename"> 184 * <property name="fileNamePattern" value="\.(java|xml)$"/> 185 * <property name="match" value="false"/> 186 * </module> 187 * </pre> 188 * 189 * <p>Example:</p> 190 * <pre> 191 * src/main/java/JavaClass.java //OK 192 * src/main/MainClass.java //OK 193 * src/main/java/java_xml.xml //OK 194 * src/main/main_xml.xml //OK 195 * src/main/java/image.png //violation, folders should only contain java or xml files 196 * src/main/check_properties.properties //violation, folders should only contain java or xml files 197 * </pre> 198 * 199 * <p> 200 * To configure the check to only allow Java and XML files only in your source 201 * folder and ignore any other folders: 202 * </p> 203 * 204 * <pre> 205 * <module name="RegexpOnFilename"> 206 * <property name="folderPattern" value="[\\/]src[\\/]"/> 207 * <property name="fileNamePattern" value="\.(java|xml)$"/> 208 * <property name="match" value="false"/> 209 * </module> 210 * </pre> 211 * 212 * <p>Example:</p> 213 * <pre> 214 * src/SourceClass.java //OK 215 * src/source_xml.xml //OK 216 * src/image.png //violation, only java and xml files are allowed in src folder 217 * src/main/main_properties.properties //OK, this check only applies to src folder 218 * </pre> 219 * 220 * <p> 221 * <b>Note:</b> 'folderPattern' must be specified if checkstyle is analyzing 222 * more than the normal source folder, like the 'bin' folder where class files 223 * can be located. 224 * </p> 225 * 226 * <p> 227 * To configure the check to only allow file names to be camel case: 228 * </p> 229 * 230 * <pre> 231 * <module name="RegexpOnFilename"> 232 * <property name="fileNamePattern" value="^([A-Z][a-z0-9]+\.?)+$"/> 233 * <property name="match" value="false"/> 234 * <property name="ignoreFileNameExtensions" value="true"/> 235 * </module> 236 * </pre> 237 * 238 * <p>Example:</p> 239 * <pre> 240 * src/main/java/JavaClass.java //OK 241 * src/main/MainClass.java //OK 242 * src/main/java/java_class.java //violation, file names should be in Camel Case 243 * src/main/main_class.java //violation, file names should be in Camel Case 244 * </pre> 245 * <p> 246 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker} 247 * </p> 248 * <p> 249 * Violation Message Keys: 250 * </p> 251 * <ul> 252 * <li> 253 * {@code regexp.filename.match} 254 * </li> 255 * <li> 256 * {@code regexp.filename.mismatch} 257 * </li> 258 * </ul> 259 * 260 * @since 6.15 261 */ 262@StatelessCheck 263public class RegexpOnFilenameCheck extends AbstractFileSetCheck { 264 265 /** 266 * A key is pointing to the warning message text in "messages.properties" 267 * file. 268 */ 269 public static final String MSG_MATCH = "regexp.filename.match"; 270 /** 271 * A key is pointing to the warning message text in "messages.properties" 272 * file. 273 */ 274 public static final String MSG_MISMATCH = "regexp.filename.mismatch"; 275 276 /** Specify the regular expression to match the folder path against. */ 277 private Pattern folderPattern; 278 /** Specify the regular expression to match the file name against. */ 279 private Pattern fileNamePattern; 280 /** 281 * Control whether to look for a match or mismatch on the file name, 282 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 283 */ 284 private boolean match = true; 285 /** Control whether to ignore the file extension for the file name match. */ 286 private boolean ignoreFileNameExtensions; 287 288 /** 289 * Setter to specify the regular expression to match the folder path against. 290 * 291 * @param folderPattern format of folder. 292 */ 293 public void setFolderPattern(Pattern folderPattern) { 294 this.folderPattern = folderPattern; 295 } 296 297 /** 298 * Setter to specify the regular expression to match the file name against. 299 * 300 * @param fileNamePattern format of file. 301 */ 302 public void setFileNamePattern(Pattern fileNamePattern) { 303 this.fileNamePattern = fileNamePattern; 304 } 305 306 /** 307 * Setter to control whether to look for a match or mismatch on the file name, 308 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 309 * 310 * @param match check's option for matching file names. 311 */ 312 public void setMatch(boolean match) { 313 this.match = match; 314 } 315 316 /** 317 * Setter to control whether to ignore the file extension for the file name match. 318 * 319 * @param ignoreFileNameExtensions check's option for ignoring file extension. 320 */ 321 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) { 322 this.ignoreFileNameExtensions = ignoreFileNameExtensions; 323 } 324 325 @Override 326 public void init() { 327 if (fileNamePattern == null && folderPattern == null) { 328 fileNamePattern = CommonUtil.createPattern("\\s"); 329 } 330 } 331 332 @Override 333 protected void processFiltered(File file, FileText fileText) throws CheckstyleException { 334 final String fileName = getFileName(file); 335 final String folderPath = getFolderPath(file); 336 337 if (isMatchFolder(folderPath) && isMatchFile(fileName)) { 338 log(); 339 } 340 } 341 342 /** 343 * Retrieves the file name from the given {@code file}. 344 * 345 * @param file Input file to examine. 346 * @return The file name. 347 */ 348 private String getFileName(File file) { 349 String fileName = file.getName(); 350 351 if (ignoreFileNameExtensions) { 352 fileName = CommonUtil.getFileNameWithoutExtension(fileName); 353 } 354 355 return fileName; 356 } 357 358 /** 359 * Retrieves the folder path from the given {@code file}. 360 * 361 * @param file Input file to examine. 362 * @return The folder path. 363 * @throws CheckstyleException if there is an error getting the canonical 364 * path of the {@code file}. 365 */ 366 private static String getFolderPath(File file) throws CheckstyleException { 367 try { 368 return file.getCanonicalFile().getParent(); 369 } 370 catch (IOException ex) { 371 throw new CheckstyleException("unable to create canonical path names for " 372 + file.getAbsolutePath(), ex); 373 } 374 } 375 376 /** 377 * Checks if the given {@code folderPath} matches the specified 378 * {@link #folderPattern}. 379 * 380 * @param folderPath Input folder path to examine. 381 * @return true if they do match. 382 */ 383 private boolean isMatchFolder(String folderPath) { 384 final boolean result; 385 386 // null pattern always matches, regardless of value of 'match' 387 if (folderPattern == null) { 388 result = true; 389 } 390 else { 391 // null pattern means 'match' applies to the folderPattern matching 392 final boolean useMatch = fileNamePattern != null || match; 393 result = folderPattern.matcher(folderPath).find() == useMatch; 394 } 395 396 return result; 397 } 398 399 /** 400 * Checks if the given {@code fileName} matches the specified 401 * {@link #fileNamePattern}. 402 * 403 * @param fileName Input file name to examine. 404 * @return true if they do match. 405 */ 406 private boolean isMatchFile(String fileName) { 407 // null pattern always matches, regardless of value of 'match' 408 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match; 409 } 410 411 /** Logs the violations for the check. */ 412 private void log() { 413 final String folder = getStringOrDefault(folderPattern, ""); 414 final String fileName = getStringOrDefault(fileNamePattern, ""); 415 416 if (match) { 417 log(1, MSG_MATCH, folder, fileName); 418 } 419 else { 420 log(1, MSG_MISMATCH, folder, fileName); 421 } 422 } 423 424 /** 425 * Retrieves the String form of the {@code pattern} or {@code defaultString} 426 * if null. 427 * 428 * @param pattern The pattern to convert. 429 * @param defaultString The result to use if {@code pattern} is null. 430 * @return The String form of the {@code pattern}. 431 */ 432 private static String getStringOrDefault(Pattern pattern, String defaultString) { 433 final String result; 434 435 if (pattern == null) { 436 result = defaultString; 437 } 438 else { 439 result = pattern.toString(); 440 } 441 442 return result; 443 } 444 445}