001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 mis-match 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 mis-match 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 all files}.</li> 102 * </ul> 103 * 104 * <p> 105 * To configure the check to report file names that contain a space: 106 * </p> 107 * 108 * <pre> 109 * <module name="RegexpOnFilename"/> 110 * </pre> 111 * 112 * <p>Example:</p> 113 * <pre> 114 * src/xdocs/config_regexp.xml //OK, contains no whitespace 115 * src/xdocs/"config regexp".xml //violation, contains whitespace 116 * </pre> 117 * 118 * <p> 119 * To configure the check to forbid 'gif' files in folders: 120 * </p> 121 * 122 * <pre> 123 * <module name="RegexpOnFilename"> 124 * <property name="fileNamePattern" value="\.gif$"/> 125 * </module> 126 * </pre> 127 * 128 * <p>Example:</p> 129 * <pre> 130 * src/site/resources/images/favicon.png //OK 131 * src/site/resources/images/logo.jpg //OK 132 * src/site/resources/images/groups.gif //violation, .gif images not allowed 133 * </pre> 134 * 135 * <p> 136 * To configure the check to forbid 'md' files except 'README.md file' in folders, 137 * with custom message: 138 * </p> 139 * 140 * <pre> 141 * <module name="RegexpOnFilename"> 142 * <property name="fileNamePattern" value="README"/> 143 * <property name="fileExtensions" value="md"/> 144 * <property name="match" value="false"/> 145 * <message key="regexp.filename.mismatch" 146 * value="No '*.md' files other then 'README.md'"/> 147 * </module> 148 * </pre> 149 * 150 * <p>Example:</p> 151 * <pre> 152 * src/site/resources/README.md //OK 153 * src/site/resources/Logo.png //OK 154 * src/site/resources/Text.md //violation, .md files other than 'README.md' are not allowed 155 * </pre> 156 * 157 * <p> 158 * To configure the check to only allow property and xml files to be located in 159 * the resource folder: 160 * </p> 161 * 162 * <pre> 163 * <module name="RegexpOnFilename"> 164 * <property name="folderPattern" 165 * value="[\\/]src[\\/]\w+[\\/]resources[\\/]"/> 166 * <property name="match" value="false"/> 167 * <property name="fileExtensions" value="properties, xml"/> 168 * </module> 169 * </pre> 170 * 171 * <p>Example:</p> 172 * <pre> 173 * src/main/resources/sun_checks.xml //OK 174 * src/main/resources/check_properties.properties //OK 175 * src/main/resources/JavaClass.java //violation, xml|property files are allowed in resource folder 176 * </pre> 177 * 178 * <p> 179 * To configure the check to only allow Java and XML files in your folders use 180 * the below. 181 * </p> 182 * 183 * <pre> 184 * <module name="RegexpOnFilename"> 185 * <property name="fileNamePattern" value="\.(java|xml)$"/> 186 * <property name="match" value="false"/> 187 * </module> 188 * </pre> 189 * 190 * <p>Example:</p> 191 * <pre> 192 * src/main/java/JavaClass.java //OK 193 * src/main/MainClass.java //OK 194 * src/main/java/java_xml.xml //OK 195 * src/main/main_xml.xml //OK 196 * src/main/java/image.png //violation, folders should only contain java or xml files 197 * src/main/check_properties.properties //violation, folders should only contain java or xml files 198 * </pre> 199 * 200 * <p> 201 * To configure the check to only allow Java and XML files only in your source 202 * folder and ignore any other folders: 203 * </p> 204 * 205 * <pre> 206 * <module name="RegexpOnFilename"> 207 * <property name="folderPattern" value="[\\/]src[\\/]"/> 208 * <property name="fileNamePattern" value="\.(java|xml)$"/> 209 * <property name="match" value="false"/> 210 * </module> 211 * </pre> 212 * 213 * <p>Example:</p> 214 * <pre> 215 * src/SourceClass.java //OK 216 * src/source_xml.xml //OK 217 * src/image.png //violation, only java and xml files are allowed in src folder 218 * src/main/main_properties.properties //OK, this check only applies to src folder 219 * </pre> 220 * 221 * <p> 222 * <b>Note:</b> 'folderPattern' must be specified if checkstyle is analyzing 223 * more than the normal source folder, like the 'bin' folder where class files 224 * can be located. 225 * </p> 226 * 227 * <p> 228 * To configure the check to only allow file names to be camel case: 229 * </p> 230 * 231 * <pre> 232 * <module name="RegexpOnFilename"> 233 * <property name="fileNamePattern" value="^([A-Z][a-z0-9]+\.?)+$"/> 234 * <property name="match" value="false"/> 235 * <property name="ignoreFileNameExtensions" value="true"/> 236 * </module> 237 * </pre> 238 * 239 * <p>Example:</p> 240 * <pre> 241 * src/main/java/JavaClass.java //OK 242 * src/main/MainClass.java //OK 243 * src/main/java/java_class.java //violation, file names should be in Camel Case 244 * src/main/main_class.java //violation, file names should be in Camel Case 245 * </pre> 246 * <p> 247 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker} 248 * </p> 249 * <p> 250 * Violation Message Keys: 251 * </p> 252 * <ul> 253 * <li> 254 * {@code regexp.filename.match} 255 * </li> 256 * <li> 257 * {@code regexp.filename.mismatch} 258 * </li> 259 * </ul> 260 * 261 * @since 6.15 262 */ 263@StatelessCheck 264public class RegexpOnFilenameCheck extends AbstractFileSetCheck { 265 266 /** 267 * A key is pointing to the warning message text in "messages.properties" 268 * file. 269 */ 270 public static final String MSG_MATCH = "regexp.filename.match"; 271 /** 272 * A key is pointing to the warning message text in "messages.properties" 273 * file. 274 */ 275 public static final String MSG_MISMATCH = "regexp.filename.mismatch"; 276 277 /** Specify the regular expression to match the folder path against. */ 278 private Pattern folderPattern; 279 /** Specify the regular expression to match the file name against. */ 280 private Pattern fileNamePattern; 281 /** 282 * Control whether to look for a match or mis-match on the file name, 283 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 284 */ 285 private boolean match = true; 286 /** Control whether to ignore the file extension for the file name match. */ 287 private boolean ignoreFileNameExtensions; 288 289 /** 290 * Setter to specify the regular expression to match the folder path against. 291 * 292 * @param folderPattern format of folder. 293 */ 294 public void setFolderPattern(Pattern folderPattern) { 295 this.folderPattern = folderPattern; 296 } 297 298 /** 299 * Setter to specify the regular expression to match the file name against. 300 * 301 * @param fileNamePattern format of file. 302 */ 303 public void setFileNamePattern(Pattern fileNamePattern) { 304 this.fileNamePattern = fileNamePattern; 305 } 306 307 /** 308 * Setter to control whether to look for a match or mis-match on the file name, 309 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 310 * 311 * @param match check's option for matching file names. 312 */ 313 public void setMatch(boolean match) { 314 this.match = match; 315 } 316 317 /** 318 * Setter to control whether to ignore the file extension for the file name match. 319 * 320 * @param ignoreFileNameExtensions check's option for ignoring file extension. 321 */ 322 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) { 323 this.ignoreFileNameExtensions = ignoreFileNameExtensions; 324 } 325 326 @Override 327 public void init() { 328 if (fileNamePattern == null && folderPattern == null) { 329 fileNamePattern = CommonUtil.createPattern("\\s"); 330 } 331 } 332 333 @Override 334 protected void processFiltered(File file, FileText fileText) throws CheckstyleException { 335 final String fileName = getFileName(file); 336 final String folderPath = getFolderPath(file); 337 338 if (isMatchFolder(folderPath) && isMatchFile(fileName)) { 339 log(); 340 } 341 } 342 343 /** 344 * Retrieves the file name from the given {@code file}. 345 * 346 * @param file Input file to examine. 347 * @return The file name. 348 */ 349 private String getFileName(File file) { 350 String fileName = file.getName(); 351 352 if (ignoreFileNameExtensions) { 353 fileName = CommonUtil.getFileNameWithoutExtension(fileName); 354 } 355 356 return fileName; 357 } 358 359 /** 360 * Retrieves the folder path from the given {@code file}. 361 * 362 * @param file Input file to examine. 363 * @return The folder path. 364 * @throws CheckstyleException if there is an error getting the canonical 365 * path of the {@code file}. 366 */ 367 private static String getFolderPath(File file) throws CheckstyleException { 368 try { 369 return file.getCanonicalFile().getParent(); 370 } 371 catch (IOException ex) { 372 throw new CheckstyleException("unable to create canonical path names for " 373 + file.getAbsolutePath(), ex); 374 } 375 } 376 377 /** 378 * Checks if the given {@code folderPath} matches the specified 379 * {@link #folderPattern}. 380 * 381 * @param folderPath Input folder path to examine. 382 * @return true if they do match. 383 */ 384 private boolean isMatchFolder(String folderPath) { 385 final boolean result; 386 387 // null pattern always matches, regardless of value of 'match' 388 if (folderPattern == null) { 389 result = true; 390 } 391 else { 392 // null pattern means 'match' applies to the folderPattern matching 393 final boolean useMatch = fileNamePattern != null || match; 394 result = folderPattern.matcher(folderPath).find() == useMatch; 395 } 396 397 return result; 398 } 399 400 /** 401 * Checks if the given {@code fileName} matches the specified 402 * {@link #fileNamePattern}. 403 * 404 * @param fileName Input file name to examine. 405 * @return true if they do match. 406 */ 407 private boolean isMatchFile(String fileName) { 408 // null pattern always matches, regardless of value of 'match' 409 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match; 410 } 411 412 /** Logs the violations for the check. */ 413 private void log() { 414 final String folder = getStringOrDefault(folderPattern, ""); 415 final String fileName = getStringOrDefault(fileNamePattern, ""); 416 417 if (match) { 418 log(1, MSG_MATCH, folder, fileName); 419 } 420 else { 421 log(1, MSG_MISMATCH, folder, fileName); 422 } 423 } 424 425 /** 426 * Retrieves the String form of the {@code pattern} or {@code defaultString} 427 * if null. 428 * 429 * @param pattern The pattern to convert. 430 * @param defaultString The result to use if {@code pattern} is null. 431 * @return The String form of the {@code pattern}. 432 */ 433 private static String getStringOrDefault(Pattern pattern, String defaultString) { 434 final String result; 435 436 if (pattern == null) { 437 result = defaultString; 438 } 439 else { 440 result = pattern.toString(); 441 } 442 443 return result; 444 } 445 446}