1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 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 * Implementation of a check that looks for a file name and/or path match (or 35 * mis-match) against specified patterns. 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 mis-match 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 error message. This allows the error 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 error 71 * messages. 72 * </p> 73 * 74 * <p> 75 * Check have following options: 76 * </p> 77 * <ul> 78 * <li> 79 * folderPattern - Regular expression to match the folder path against. Default 80 * value is null.</li> 81 * 82 * <li> 83 * fileNamePattern - Regular expression to match the file name against. Default 84 * value is null.</li> 85 * 86 * <li> 87 * match - Whether to look for a match or mis-match on the file name, if the 88 * fileNamePattern is supplied, otherwise it is applied on the folderPattern. 89 * Default value is true.</li> 90 * 91 * <li> 92 * ignoreFileNameExtensions - Whether to ignore the file extension for the file 93 * name match. Default value is false.</li> 94 * 95 * <li> 96 * fileExtensions - File type extension of files to process. If this is 97 * specified, then only files that match these types are examined with the other 98 * patterns. Default value is {}.</li> 99 * </ul> 100 * <br> 101 * 102 * <p> 103 * To configure the check to report file names that contain a space: 104 * </p> 105 * 106 * <pre> 107 * <module name="RegexpOnFilename"/> 108 * </pre> 109 * <p> 110 * To configure the check to force picture files to not be 'gif': 111 * </p> 112 * 113 * <pre> 114 * <module name="RegexpOnFilename"> 115 * <property name="fileNamePattern" value="\\.gif$"/> 116 * </module> 117 * </pre> 118 * <p> 119 * OR: 120 * </p> 121 * 122 * <pre> 123 * <module name="RegexpOnFilename"> 124 * <property name="fileNamePattern" value="."/> 125 * <property name="fileExtensions" value="gif"/> 126 * </module> 127 * </pre> 128 * 129 * <p> 130 * To configure the check to only allow property and xml files to be located in 131 * the resource folder: 132 * </p> 133 * 134 * <pre> 135 * <module name="RegexpOnFilename"> 136 * <property name="folderPattern" 137 * value="[\\/]src[\\/]\\w+[\\/]resources[\\/]"/> 138 * <property name="match" value="false"/> 139 * <property name="fileExtensions" value="properties, xml"/> 140 * </module> 141 * </pre> 142 * 143 * <p> 144 * To configure the check to only allow Java and XML files in your folders use 145 * the below. 146 * </p> 147 * 148 * <pre> 149 * <module name="RegexpOnFilename"> 150 * <property name="fileNamePattern" value="\\.(java|xml)$"/> 151 * <property name="match" value="false"/> 152 * </module> 153 * </pre> 154 * <p> 155 * To configure the check to only allow Java and XML files only in your source 156 * folder and ignore any other folders: 157 * </p> 158 * 159 * <p> 160 * <b>Note:</b> 'folderPattern' must be specified if checkstyle is analyzing 161 * more than the normal source folder, like the 'bin' folder where class files 162 * can be located. 163 * </p> 164 * 165 * <pre> 166 * <module name="RegexpOnFilename"> 167 * <property name="folderPattern" value="[\\/]src[\\/]"/> 168 * <property name="fileNamePattern" value="\\.(java|xml)$"/> 169 * <property name="match" value="false"/> 170 * </module> 171 * </pre> 172 * <p> 173 * To configure the check to only allow file names to be camel case: 174 * </p> 175 * 176 * <pre> 177 * <module name="RegexpOnFilename"> 178 * <property name="fileNamePattern" 179 * value="^([A-Z][a-z0-9]+\.?)+$"/> 180 * <property name="match" value="false"/> 181 * <property name="ignoreFileNameExtensions" value="true"/> 182 * </module> 183 * </pre> 184 * 185 */ 186 @StatelessCheck 187 public class RegexpOnFilenameCheck extends AbstractFileSetCheck { 188 189 /** 190 * A key is pointing to the warning message text in "messages.properties" 191 * file. 192 */ 193 public static final String MSG_MATCH = "regexp.filename.match"; 194 /** 195 * A key is pointing to the warning message text in "messages.properties" 196 * file. 197 */ 198 public static final String MSG_MISMATCH = "regexp.filename.mismatch"; 199 200 /** Compiled regexp to match a folder. */ 201 private Pattern folderPattern; 202 /** Compiled regexp to match a file. */ 203 private Pattern fileNamePattern; 204 /** Whether to look for a file name match or mismatch. */ 205 private boolean match = true; 206 /** Whether to ignore the file's extension when looking for matches. */ 207 private boolean ignoreFileNameExtensions; 208 209 /** 210 * Setter for folder format. 211 * 212 * @param folderPattern format of folder. 213 */ 214 public void setFolderPattern(Pattern folderPattern) { 215 this.folderPattern = folderPattern; 216 } 217 218 /** 219 * Setter for file name format. 220 * 221 * @param fileNamePattern format of file. 222 */ 223 public void setFileNamePattern(Pattern fileNamePattern) { 224 this.fileNamePattern = fileNamePattern; 225 } 226 227 /** 228 * Sets whether the check should look for a file name match or mismatch. 229 * 230 * @param match check's option for matching file names. 231 */ 232 public void setMatch(boolean match) { 233 this.match = match; 234 } 235 236 /** 237 * Sets whether file name matching should drop the file extension or not. 238 * 239 * @param ignoreFileNameExtensions check's option for ignoring file extension. 240 */ 241 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) { 242 this.ignoreFileNameExtensions = ignoreFileNameExtensions; 243 } 244 245 @Override 246 public void init() { 247 if (fileNamePattern == null && folderPattern == null) { 248 fileNamePattern = CommonUtil.createPattern("\\s"); 249 } 250 } 251 252 @Override 253 protected void processFiltered(File file, FileText fileText) throws CheckstyleException { 254 final String fileName = getFileName(file); 255 final String folderPath = getFolderPath(file); 256 257 if (isMatchFolder(folderPath) && isMatchFile(fileName)) { 258 log(); 259 } 260 } 261 262 /** 263 * Retrieves the file name from the given {@code file}. 264 * 265 * @param file Input file to examine. 266 * @return The file name. 267 */ 268 private String getFileName(File file) { 269 String fileName = file.getName(); 270 271 if (ignoreFileNameExtensions) { 272 fileName = CommonUtil.getFileNameWithoutExtension(fileName); 273 } 274 275 return fileName; 276 } 277 278 /** 279 * Retrieves the folder path from the given {@code file}. 280 * 281 * @param file Input file to examine. 282 * @return The folder path. 283 * @throws CheckstyleException if there is an error getting the canonical 284 * path of the {@code file}. 285 */ 286 private static String getFolderPath(File file) throws CheckstyleException { 287 try { 288 return file.getCanonicalFile().getParent(); 289 } 290 catch (IOException ex) { 291 throw new CheckstyleException("unable to create canonical path names for " 292 + file.getAbsolutePath(), ex); 293 } 294 } 295 296 /** 297 * Checks if the given {@code folderPath} matches the specified 298 * {@link #folderPattern}. 299 * 300 * @param folderPath Input folder path to examine. 301 * @return true if they do match. 302 */ 303 private boolean isMatchFolder(String folderPath) { 304 final boolean result; 305 306 // null pattern always matches, regardless of value of 'match' 307 if (folderPattern == null) { 308 result = true; 309 } 310 else { 311 // null pattern means 'match' applies to the folderPattern matching 312 final boolean useMatch = fileNamePattern != null || match; 313 result = folderPattern.matcher(folderPath).find() == useMatch; 314 } 315 316 return result; 317 } 318 319 /** 320 * Checks if the given {@code fileName} matches the specified 321 * {@link #fileNamePattern}. 322 * 323 * @param fileName Input file name to examine. 324 * @return true if they do match. 325 */ 326 private boolean isMatchFile(String fileName) { 327 // null pattern always matches, regardless of value of 'match' 328 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match; 329 } 330 331 /** Logs the errors for the check. */ 332 private void log() { 333 final String folder = getStringOrDefault(folderPattern, ""); 334 final String fileName = getStringOrDefault(fileNamePattern, ""); 335 336 if (match) { 337 log(1, MSG_MATCH, folder, fileName); 338 } 339 else { 340 log(1, MSG_MISMATCH, folder, fileName); 341 } 342 } 343 344 /** 345 * Retrieves the String form of the {@code pattern} or {@code defaultString} 346 * if null. 347 * 348 * @param pattern The pattern to convert. 349 * @param defaultString The result to use if {@code pattern} is null. 350 * @return The String form of the {@code pattern}. 351 */ 352 private static String getStringOrDefault(Pattern pattern, String defaultString) { 353 final String result; 354 355 if (pattern == null) { 356 result = defaultString; 357 } 358 else { 359 result = pattern.toString(); 360 } 361 362 return result; 363 } 364 365 }