View Javadoc
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 }