1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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 * <div>
34 * Checks that a specified pattern matches based on file and/or folder path.
35 * It can also be used to verify files
36 * match specific naming patterns not covered by other checks (Ex: properties,
37 * xml, etc.).
38 * </div>
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 mismatch 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 violation message. This allows the violation 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 violation
71 * messages.
72 * </p>
73 *
74 * @since 6.15
75 */
76 @StatelessCheck
77 public class RegexpOnFilenameCheck extends AbstractFileSetCheck {
78
79 /**
80 * A key is pointing to the warning message text in "messages.properties"
81 * file.
82 */
83 public static final String MSG_MATCH = "regexp.filename.match";
84 /**
85 * A key is pointing to the warning message text in "messages.properties"
86 * file.
87 */
88 public static final String MSG_MISMATCH = "regexp.filename.mismatch";
89
90 /** Specify the regular expression to match the folder path against. */
91 private Pattern folderPattern;
92 /** Specify the regular expression to match the file name against. */
93 private Pattern fileNamePattern;
94 /**
95 * Control whether to look for a match or mismatch on the file name,
96 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
97 */
98 private boolean match = true;
99 /** Control whether to ignore the file extension for the file name match. */
100 private boolean ignoreFileNameExtensions;
101
102 /**
103 * Setter to specify the regular expression to match the folder path against.
104 *
105 * @param folderPattern format of folder.
106 * @since 6.15
107 */
108 public void setFolderPattern(Pattern folderPattern) {
109 this.folderPattern = folderPattern;
110 }
111
112 /**
113 * Setter to specify the regular expression to match the file name against.
114 *
115 * @param fileNamePattern format of file.
116 * @since 6.15
117 */
118 public void setFileNamePattern(Pattern fileNamePattern) {
119 this.fileNamePattern = fileNamePattern;
120 }
121
122 /**
123 * Setter to control whether to look for a match or mismatch on the file name,
124 * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
125 *
126 * @param match check's option for matching file names.
127 * @since 6.15
128 */
129 public void setMatch(boolean match) {
130 this.match = match;
131 }
132
133 /**
134 * Setter to control whether to ignore the file extension for the file name match.
135 *
136 * @param ignoreFileNameExtensions check's option for ignoring file extension.
137 * @since 6.15
138 */
139 public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) {
140 this.ignoreFileNameExtensions = ignoreFileNameExtensions;
141 }
142
143 @Override
144 public void init() {
145 if (fileNamePattern == null && folderPattern == null) {
146 fileNamePattern = CommonUtil.createPattern("\\s");
147 }
148 }
149
150 @Override
151 protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
152 final String fileName = getFileName(file);
153 final String folderPath = getFolderPath(file);
154
155 if (isMatchFolder(folderPath) && isMatchFile(fileName)) {
156 log();
157 }
158 }
159
160 /**
161 * Retrieves the file name from the given {@code file}.
162 *
163 * @param file Input file to examine.
164 * @return The file name.
165 */
166 private String getFileName(File file) {
167 String fileName = file.getName();
168
169 if (ignoreFileNameExtensions) {
170 fileName = CommonUtil.getFileNameWithoutExtension(fileName);
171 }
172
173 return fileName;
174 }
175
176 /**
177 * Retrieves the folder path from the given {@code file}.
178 *
179 * @param file Input file to examine.
180 * @return The folder path.
181 * @throws CheckstyleException if there is an error getting the canonical
182 * path of the {@code file}.
183 */
184 private static String getFolderPath(File file) throws CheckstyleException {
185 try {
186 return file.getCanonicalFile().getParent();
187 }
188 catch (IOException exc) {
189 throw new CheckstyleException("unable to create canonical path names for "
190 + file.getAbsolutePath(), exc);
191 }
192 }
193
194 /**
195 * Checks if the given {@code folderPath} matches the specified
196 * {@link #folderPattern}.
197 *
198 * @param folderPath Input folder path to examine.
199 * @return true if they do match.
200 */
201 private boolean isMatchFolder(String folderPath) {
202 final boolean result;
203
204 // null pattern always matches, regardless of value of 'match'
205 if (folderPattern == null) {
206 result = true;
207 }
208 else {
209 // null pattern means 'match' applies to the folderPattern matching
210 final boolean useMatch = fileNamePattern != null || match;
211 result = folderPattern.matcher(folderPath).find() == useMatch;
212 }
213
214 return result;
215 }
216
217 /**
218 * Checks if the given {@code fileName} matches the specified
219 * {@link #fileNamePattern}.
220 *
221 * @param fileName Input file name to examine.
222 * @return true if they do match.
223 */
224 private boolean isMatchFile(String fileName) {
225 // null pattern always matches, regardless of value of 'match'
226 return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match;
227 }
228
229 /** Logs the violations for the check. */
230 private void log() {
231 final String folder = getStringOrDefault(folderPattern, "");
232 final String fileName = getStringOrDefault(fileNamePattern, "");
233
234 if (match) {
235 log(1, MSG_MATCH, folder, fileName);
236 }
237 else {
238 log(1, MSG_MISMATCH, folder, fileName);
239 }
240 }
241
242 /**
243 * Retrieves the String form of the {@code pattern} or {@code defaultString}
244 * if null.
245 *
246 * @param pattern The pattern to convert.
247 * @param defaultString The result to use if {@code pattern} is null.
248 * @return The String form of the {@code pattern}.
249 */
250 private static String getStringOrDefault(Pattern pattern, String defaultString) {
251 final String result;
252
253 if (pattern == null) {
254 result = defaultString;
255 }
256 else {
257 result = pattern.toString();
258 }
259
260 return result;
261 }
262
263 }