View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <ul>
74   * <li>
75   * Property {@code fileExtensions} - Specify the file extensions of the files to process.
76   * Type is {@code java.lang.String[]}.
77   * Default value is {@code ""}.</li>
78   * <li>
79   * Property {@code fileNamePattern} - Specify the regular expression to match the file name against.
80   * Type is {@code java.util.regex.Pattern}.
81   * Default value is {@code null}.</li>
82   * <li>
83   * Property {@code folderPattern} - Specify the regular expression to match the folder path against.
84   * Type is {@code java.util.regex.Pattern}.
85   * Default value is {@code null}.</li>
86   * <li>
87   * Property {@code ignoreFileNameExtensions} - Control whether to ignore the file extension for
88   * the file name match.
89   * Type is {@code boolean}.
90   * Default value is {@code false}.</li>
91   * <li>
92   * Property {@code match} - Control whether to look for a match or mismatch on the file name, if
93   * the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
94   * Type is {@code boolean}.
95   * Default value is {@code true}.</li>
96   * </ul>
97   *
98   * <p>
99   * 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
117 public 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 }