View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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      * Creates a new {@code RegexpOnFilenameCheck} instance.
104      */
105     public RegexpOnFilenameCheck() {
106         // no code by default
107     }
108 
109     /**
110      * Setter to specify the regular expression to match the folder path against.
111      *
112      * @param folderPattern format of folder.
113      * @since 6.15
114      */
115     public void setFolderPattern(Pattern folderPattern) {
116         this.folderPattern = folderPattern;
117     }
118 
119     /**
120      * Setter to specify the regular expression to match the file name against.
121      *
122      * @param fileNamePattern format of file.
123      * @since 6.15
124      */
125     public void setFileNamePattern(Pattern fileNamePattern) {
126         this.fileNamePattern = fileNamePattern;
127     }
128 
129     /**
130      * Setter to control whether to look for a match or mismatch on the file name,
131      * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
132      *
133      * @param match check's option for matching file names.
134      * @since 6.15
135      */
136     public void setMatch(boolean match) {
137         this.match = match;
138     }
139 
140     /**
141      * Setter to control whether to ignore the file extension for the file name match.
142      *
143      * @param ignoreFileNameExtensions check's option for ignoring file extension.
144      * @since 6.15
145      */
146     public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) {
147         this.ignoreFileNameExtensions = ignoreFileNameExtensions;
148     }
149 
150     @Override
151     public void init() {
152         if (fileNamePattern == null && folderPattern == null) {
153             fileNamePattern = CommonUtil.createPattern("\\s");
154         }
155     }
156 
157     @Override
158     protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
159         final String fileName = getFileName(file);
160         final String folderPath = getFolderPath(file);
161 
162         if (isMatchFolder(folderPath) && isMatchFile(fileName)) {
163             log();
164         }
165     }
166 
167     /**
168      * Retrieves the file name from the given {@code file}.
169      *
170      * @param file Input file to examine.
171      * @return The file name.
172      */
173     private String getFileName(File file) {
174         String fileName = file.getName();
175 
176         if (ignoreFileNameExtensions) {
177             fileName = CommonUtil.getFileNameWithoutExtension(fileName);
178         }
179 
180         return fileName;
181     }
182 
183     /**
184      * Retrieves the folder path from the given {@code file}.
185      *
186      * @param file Input file to examine.
187      * @return The folder path.
188      * @throws CheckstyleException if there is an error getting the canonical
189      *         path of the {@code file}.
190      */
191     private static String getFolderPath(File file) throws CheckstyleException {
192         try {
193             return file.getCanonicalFile().getParent();
194         }
195         catch (IOException exc) {
196             throw new CheckstyleException("unable to create canonical path names for "
197                     + file.getAbsolutePath(), exc);
198         }
199     }
200 
201     /**
202      * Checks if the given {@code folderPath} matches the specified
203      * {@link #folderPattern}.
204      *
205      * @param folderPath Input folder path to examine.
206      * @return true if they do match.
207      */
208     private boolean isMatchFolder(String folderPath) {
209         final boolean result;
210 
211         // null pattern always matches, regardless of value of 'match'
212         if (folderPattern == null) {
213             result = true;
214         }
215         else {
216             // null pattern means 'match' applies to the folderPattern matching
217             final boolean useMatch = fileNamePattern != null || match;
218             result = folderPattern.matcher(folderPath).find() == useMatch;
219         }
220 
221         return result;
222     }
223 
224     /**
225      * Checks if the given {@code fileName} matches the specified
226      * {@link #fileNamePattern}.
227      *
228      * @param fileName Input file name to examine.
229      * @return true if they do match.
230      */
231     private boolean isMatchFile(String fileName) {
232         // null pattern always matches, regardless of value of 'match'
233         return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match;
234     }
235 
236     /** Logs the violations for the check. */
237     private void log() {
238         final String folder = getStringOrDefault(folderPattern, "");
239         final String fileName = getStringOrDefault(fileNamePattern, "");
240 
241         if (match) {
242             log(1, MSG_MATCH, folder, fileName);
243         }
244         else {
245             log(1, MSG_MISMATCH, folder, fileName);
246         }
247     }
248 
249     /**
250      * Retrieves the String form of the {@code pattern} or {@code defaultString}
251      * if null.
252      *
253      * @param pattern The pattern to convert.
254      * @param defaultString The result to use if {@code pattern} is null.
255      * @return The String form of the {@code pattern}.
256      */
257     private static String getStringOrDefault(Pattern pattern, String defaultString) {
258         final String result;
259 
260         if (pattern == null) {
261             result = defaultString;
262         }
263         else {
264             result = pattern.toString();
265         }
266 
267         return result;
268     }
269 
270 }