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   * <p>
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   * </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 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   * <p>
98   * Parent is {@code com.puppycrawl.tools.checkstyle.Checker}
99   * </p>
100  * <p>
101  * Violation Message Keys:
102  * </p>
103  * <ul>
104  * <li>
105  * {@code regexp.filename.match}
106  * </li>
107  * <li>
108  * {@code regexp.filename.mismatch}
109  * </li>
110  * </ul>
111  *
112  * @since 6.15
113  */
114 @StatelessCheck
115 public class RegexpOnFilenameCheck extends AbstractFileSetCheck {
116 
117     /**
118      * A key is pointing to the warning message text in "messages.properties"
119      * file.
120      */
121     public static final String MSG_MATCH = "regexp.filename.match";
122     /**
123      * A key is pointing to the warning message text in "messages.properties"
124      * file.
125      */
126     public static final String MSG_MISMATCH = "regexp.filename.mismatch";
127 
128     /** Specify the regular expression to match the folder path against. */
129     private Pattern folderPattern;
130     /** Specify the regular expression to match the file name against. */
131     private Pattern fileNamePattern;
132     /**
133      * Control whether to look for a match or mismatch on the file name,
134      * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
135      */
136     private boolean match = true;
137     /** Control whether to ignore the file extension for the file name match. */
138     private boolean ignoreFileNameExtensions;
139 
140     /**
141      * Setter to specify the regular expression to match the folder path against.
142      *
143      * @param folderPattern format of folder.
144      * @since 6.15
145      */
146     public void setFolderPattern(Pattern folderPattern) {
147         this.folderPattern = folderPattern;
148     }
149 
150     /**
151      * Setter to specify the regular expression to match the file name against.
152      *
153      * @param fileNamePattern format of file.
154      * @since 6.15
155      */
156     public void setFileNamePattern(Pattern fileNamePattern) {
157         this.fileNamePattern = fileNamePattern;
158     }
159 
160     /**
161      * Setter to control whether to look for a match or mismatch on the file name,
162      * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
163      *
164      * @param match check's option for matching file names.
165      * @since 6.15
166      */
167     public void setMatch(boolean match) {
168         this.match = match;
169     }
170 
171     /**
172      * Setter to control whether to ignore the file extension for the file name match.
173      *
174      * @param ignoreFileNameExtensions check's option for ignoring file extension.
175      * @since 6.15
176      */
177     public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) {
178         this.ignoreFileNameExtensions = ignoreFileNameExtensions;
179     }
180 
181     @Override
182     public void init() {
183         if (fileNamePattern == null && folderPattern == null) {
184             fileNamePattern = CommonUtil.createPattern("\\s");
185         }
186     }
187 
188     @Override
189     protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
190         final String fileName = getFileName(file);
191         final String folderPath = getFolderPath(file);
192 
193         if (isMatchFolder(folderPath) && isMatchFile(fileName)) {
194             log();
195         }
196     }
197 
198     /**
199      * Retrieves the file name from the given {@code file}.
200      *
201      * @param file Input file to examine.
202      * @return The file name.
203      */
204     private String getFileName(File file) {
205         String fileName = file.getName();
206 
207         if (ignoreFileNameExtensions) {
208             fileName = CommonUtil.getFileNameWithoutExtension(fileName);
209         }
210 
211         return fileName;
212     }
213 
214     /**
215      * Retrieves the folder path from the given {@code file}.
216      *
217      * @param file Input file to examine.
218      * @return The folder path.
219      * @throws CheckstyleException if there is an error getting the canonical
220      *         path of the {@code file}.
221      */
222     private static String getFolderPath(File file) throws CheckstyleException {
223         try {
224             return file.getCanonicalFile().getParent();
225         }
226         catch (IOException ex) {
227             throw new CheckstyleException("unable to create canonical path names for "
228                     + file.getAbsolutePath(), ex);
229         }
230     }
231 
232     /**
233      * Checks if the given {@code folderPath} matches the specified
234      * {@link #folderPattern}.
235      *
236      * @param folderPath Input folder path to examine.
237      * @return true if they do match.
238      */
239     private boolean isMatchFolder(String folderPath) {
240         final boolean result;
241 
242         // null pattern always matches, regardless of value of 'match'
243         if (folderPattern == null) {
244             result = true;
245         }
246         else {
247             // null pattern means 'match' applies to the folderPattern matching
248             final boolean useMatch = fileNamePattern != null || match;
249             result = folderPattern.matcher(folderPath).find() == useMatch;
250         }
251 
252         return result;
253     }
254 
255     /**
256      * Checks if the given {@code fileName} matches the specified
257      * {@link #fileNamePattern}.
258      *
259      * @param fileName Input file name to examine.
260      * @return true if they do match.
261      */
262     private boolean isMatchFile(String fileName) {
263         // null pattern always matches, regardless of value of 'match'
264         return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match;
265     }
266 
267     /** Logs the violations for the check. */
268     private void log() {
269         final String folder = getStringOrDefault(folderPattern, "");
270         final String fileName = getStringOrDefault(fileNamePattern, "");
271 
272         if (match) {
273             log(1, MSG_MATCH, folder, fileName);
274         }
275         else {
276             log(1, MSG_MISMATCH, folder, fileName);
277         }
278     }
279 
280     /**
281      * Retrieves the String form of the {@code pattern} or {@code defaultString}
282      * if null.
283      *
284      * @param pattern The pattern to convert.
285      * @param defaultString The result to use if {@code pattern} is null.
286      * @return The String form of the {@code pattern}.
287      */
288     private static String getStringOrDefault(Pattern pattern, String defaultString) {
289         final String result;
290 
291         if (pattern == null) {
292             result = defaultString;
293         }
294         else {
295             result = pattern.toString();
296         }
297 
298         return result;
299     }
300 
301 }