001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 applied to 'test'. 054 * </p> 055 * 056 * <p> 057 * If this check is configured with no properties, then the default behavior of 058 * this check is to report file names with spaces in them. When at least one 059 * pattern property is supplied, the entire check is under the user's control to 060 * allow them to fully customize the behavior. 061 * </p> 062 * 063 * <p> 064 * It is recommended that if you create your own pattern, to also specify a 065 * custom violation message. This allows the violation message printed to be clear what 066 * the violation is, especially if multiple RegexpOnFilename checks are used. 067 * Argument 0 for the message populates the check's folderPattern. Argument 1 068 * for the message populates the check's fileNamePattern. The file name is not 069 * passed as an argument since it is part of CheckStyle's default violation messages. 070 * </p> 071 * 072 * @since 6.15 073 */ 074@StatelessCheck 075public class RegexpOnFilenameCheck extends AbstractFileSetCheck { 076 077 /** 078 * A key is pointing to the warning message text in "messages.properties" file. 079 */ 080 public static final String MSG_MATCH = "regexp.filename.match"; 081 /** 082 * A key is pointing to the warning message text in "messages.properties" file. 083 */ 084 public static final String MSG_MISMATCH = "regexp.filename.mismatch"; 085 086 /** Specify the regular expression to match the folder path against. */ 087 private Pattern folderPattern; 088 /** Specify the regular expression to match the file name against. */ 089 private Pattern fileNamePattern; 090 /** 091 * Control whether to look for a match or mismatch on the file name, 092 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 093 */ 094 private boolean match = true; 095 /** Control whether to ignore the file extension for the file name match. */ 096 private boolean ignoreFileNameExtensions; 097 098 /** 099 * Creates a new {@code RegexpOnFilenameCheck} instance. 100 */ 101 public RegexpOnFilenameCheck() { 102 // no code by default 103 } 104 105 /** 106 * Setter to specify the regular expression to match the folder path against. 107 * 108 * @param folderPattern format of folder. 109 * @since 6.15 110 */ 111 public void setFolderPattern(Pattern folderPattern) { 112 this.folderPattern = folderPattern; 113 } 114 115 /** 116 * Setter to specify the regular expression to match the file name against. 117 * 118 * @param fileNamePattern format of file. 119 * @since 6.15 120 */ 121 public void setFileNamePattern(Pattern fileNamePattern) { 122 this.fileNamePattern = fileNamePattern; 123 } 124 125 /** 126 * Setter to control whether to look for a match or mismatch on the file name, 127 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern. 128 * 129 * @param match check's option for matching file names. 130 * @since 6.15 131 */ 132 public void setMatch(boolean match) { 133 this.match = match; 134 } 135 136 /** 137 * Setter to control whether to ignore the file extension for the file name match. 138 * 139 * @param ignoreFileNameExtensions check's option for ignoring file extension. 140 * @since 6.15 141 */ 142 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) { 143 this.ignoreFileNameExtensions = ignoreFileNameExtensions; 144 } 145 146 @Override 147 public void init() { 148 if (fileNamePattern == null && folderPattern == null) { 149 fileNamePattern = CommonUtil.createPattern("\\s"); 150 } 151 } 152 153 @Override 154 protected void processFiltered(File file, FileText fileText) throws CheckstyleException { 155 final String fileName = getFileName(file); 156 final String folderPath = getFolderPath(file); 157 158 if (isMatchFolder(folderPath) && isMatchFile(fileName)) { 159 log(); 160 } 161 } 162 163 /** 164 * Retrieves the file name from the given {@code file}. 165 * 166 * @param file Input file to examine. 167 * @return The file name. 168 */ 169 private String getFileName(File file) { 170 String fileName = file.getName(); 171 172 if (ignoreFileNameExtensions) { 173 fileName = CommonUtil.getFileNameWithoutExtension(fileName); 174 } 175 176 return fileName; 177 } 178 179 /** 180 * Retrieves the folder path from the given {@code file}. 181 * 182 * @param file Input file to examine. 183 * @return The folder path. 184 * @throws CheckstyleException if there is an error getting the canonical 185 * path of the {@code file}. 186 */ 187 private static String getFolderPath(File file) throws CheckstyleException { 188 try { 189 return file.getCanonicalFile().getParent(); 190 } 191 catch (IOException exc) { 192 throw new CheckstyleException("unable to create canonical path names for " 193 + file.getAbsolutePath(), exc); 194 } 195 } 196 197 /** 198 * Checks if the given {@code folderPath} matches the specified 199 * {@link #folderPattern}. 200 * 201 * @param folderPath Input folder path to examine. 202 * @return true if they do match. 203 */ 204 private boolean isMatchFolder(String folderPath) { 205 final boolean result; 206 207 // null pattern always matches, regardless of value of 'match' 208 if (folderPattern == null) { 209 result = true; 210 } 211 else { 212 // null pattern means 'match' applies to the folderPattern matching 213 final boolean useMatch = fileNamePattern != null || match; 214 result = folderPattern.matcher(folderPath).find() == useMatch; 215 } 216 217 return result; 218 } 219 220 /** 221 * Checks if the given {@code fileName} matches the specified 222 * {@link #fileNamePattern}. 223 * 224 * @param fileName Input file name to examine. 225 * @return true if they do match. 226 */ 227 private boolean isMatchFile(String fileName) { 228 // null pattern always matches, regardless of value of 'match' 229 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match; 230 } 231 232 /** Logs the violations for the check. */ 233 private void log() { 234 final String folder = getStringOrDefault(folderPattern, ""); 235 final String fileName = getStringOrDefault(fileNamePattern, ""); 236 237 if (match) { 238 log(1, MSG_MATCH, folder, fileName); 239 } 240 else { 241 log(1, MSG_MISMATCH, folder, fileName); 242 } 243 } 244 245 /** 246 * Retrieves the String form of the {@code pattern} or {@code defaultString} if null. 247 * 248 * @param pattern The pattern to convert. 249 * @param defaultString The result to use if {@code pattern} is null. 250 * @return The String form of the {@code pattern}. 251 */ 252 private static String getStringOrDefault(Pattern pattern, String defaultString) { 253 final String result; 254 255 if (pattern == null) { 256 result = defaultString; 257 } 258 else { 259 result = pattern.toString(); 260 } 261 262 return result; 263 } 264 265}