001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 * <div> 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 * </div> 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 fileExtensions} - Specify the file extensions of the files to process. 076 * Type is {@code java.lang.String[]}. 077 * Default value is {@code ""}.</li> 078 * <li> 079 * Property {@code fileNamePattern} - Specify the regular expression to match the file name against. 080 * Type is {@code java.util.regex.Pattern}. 081 * Default value is {@code null}.</li> 082 * <li> 083 * Property {@code folderPattern} - Specify the regular expression to match the folder path against. 084 * Type is {@code java.util.regex.Pattern}. 085 * Default value is {@code null}.</li> 086 * <li> 087 * Property {@code ignoreFileNameExtensions} - Control whether to ignore the file extension for 088 * the file name match. 089 * Type is {@code boolean}. 090 * Default value is {@code false}.</li> 091 * <li> 092 * Property {@code match} - Control whether to look for a match or mismatch on the file name, if 093 * the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 094 * Type is {@code boolean}. 095 * Default value is {@code true}.</li> 096 * </ul> 097 * 098 * <p> 099 * 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 117public 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}