001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.regexp;
021
022import java.io.File;
023import java.io.IOException;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
028import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
029import com.puppycrawl.tools.checkstyle.api.FileText;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031
032/**
033 * <p>
034 * Checks that a specified pattern matches based on file and/or folder path.
035 * It can also be used to verify files
036 * match specific naming patterns not covered by other checks (Ex: properties,
037 * xml, etc.).
038 * </p>
039 *
040 * <p>
041 * When customizing the check, the properties are applied in a specific order.
042 * The fileExtensions property first picks only files that match any of the
043 * specific extensions supplied. Once files are matched against the
044 * fileExtensions, the match property is then used in conjunction with the
045 * patterns to determine if the check is looking for a match or mis-match on
046 * those files. If the fileNamePattern is supplied, the matching is only applied
047 * to the fileNamePattern and not the folderPattern. If no fileNamePattern is
048 * supplied, then matching is applied to the folderPattern only and will result
049 * in all files in a folder to be reported on violations. If no folderPattern is
050 * supplied, then all folders that checkstyle finds are examined for violations.
051 * The ignoreFileNameExtensions property drops the file extension and applies
052 * the fileNamePattern only to the rest of file name. For example, if the file
053 * is named 'test.java' and this property is turned on, the pattern is only
054 * applied to 'test'.
055 * </p>
056 *
057 * <p>
058 * If this check is configured with no properties, then the default behavior of
059 * this check is to report file names with spaces in them. When at least one
060 * pattern property is supplied, the entire check is under the user's control to
061 * allow them to fully customize the behavior.
062 * </p>
063 *
064 * <p>
065 * It is recommended that if you create your own pattern, to also specify a
066 * custom violation message. This allows the violation message printed to be clear what
067 * the violation is, especially if multiple RegexpOnFilename checks are used.
068 * Argument 0 for the message populates the check's folderPattern. Argument 1
069 * for the message populates the check's fileNamePattern. The file name is not
070 * passed as an argument since it is part of CheckStyle's default violation
071 * messages.
072 * </p>
073 * <ul>
074 * <li>
075 * Property {@code folderPattern} - Specify the regular expression to match the folder path against.
076 * Type is {@code java.util.regex.Pattern}.
077 * Default value is {@code null}.</li>
078 *
079 * <li>
080 * Property {@code fileNamePattern} - Specify the regular expression to match the file name against.
081 * Type is {@code java.util.regex.Pattern}.
082 * Default value is {@code null}.</li>
083 *
084 * <li>
085 * Property {@code match} - Control whether to look for a match or mis-match on the file name, if
086 * the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
087 * Type is {@code boolean}.
088 * Default value is {@code true}.</li>
089 *
090 * <li>
091 * Property {@code ignoreFileNameExtensions} - Control whether to ignore the file extension for
092 * the file name match.
093 * Type is {@code boolean}.
094 * Default value is {@code false}.</li>
095 *
096 * <li>
097 * Property {@code fileExtensions} - Specify the file type extension of files to process. If this is
098 * specified, then only files that match these types are examined with the other
099 * patterns.
100 * Type is {@code java.lang.String[]}.
101 * Default value is {@code all files}.</li>
102 * </ul>
103 *
104 * <p>
105 * To configure the check to report file names that contain a space:
106 * </p>
107 *
108 * <pre>
109 * &lt;module name=&quot;RegexpOnFilename&quot;/&gt;
110 * </pre>
111 *
112 * <p>Example:</p>
113 * <pre>
114 * src/xdocs/config_regexp.xml  //OK, contains no whitespace
115 * src/xdocs/&quot;config regexp&quot;.xml  //violation, contains whitespace
116 * </pre>
117 *
118 * <p>
119 * To configure the check to forbid 'gif' files in folders:
120 * </p>
121 *
122 * <pre>
123 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
124 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;\.gif$&quot;/&gt;
125 * &lt;/module&gt;
126 * </pre>
127 *
128 * <p>Example:</p>
129 * <pre>
130 * src/site/resources/images/favicon.png  //OK
131 * src/site/resources/images/logo.jpg  //OK
132 * src/site/resources/images/groups.gif  //violation, .gif images not allowed
133 * </pre>
134 *
135 * <p>
136 * To configure the check to forbid 'md' files except 'README.md file' in folders,
137 * with custom message:
138 * </p>
139 *
140 * <pre>
141 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
142 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;README&quot;/&gt;
143 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;md&quot;/&gt;
144 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
145 *   &lt;message key=&quot;regexp.filename.mismatch&quot;
146 *     value=&quot;No '*.md' files other then 'README.md'&quot;/&gt;
147 * &lt;/module&gt;
148 * </pre>
149 *
150 * <p>Example:</p>
151 * <pre>
152 * src/site/resources/README.md  //OK
153 * src/site/resources/Logo.png  //OK
154 * src/site/resources/Text.md  //violation, .md files other than 'README.md' are not allowed
155 * </pre>
156 *
157 * <p>
158 * To configure the check to only allow property and xml files to be located in
159 * the resource folder:
160 * </p>
161 *
162 * <pre>
163 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
164 *   &lt;property name=&quot;folderPattern&quot;
165 *     value=&quot;[\\/]src[\\/]\w+[\\/]resources[\\/]&quot;/&gt;
166 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
167 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;properties, xml&quot;/&gt;
168 * &lt;/module&gt;
169 * </pre>
170 *
171 * <p>Example:</p>
172 * <pre>
173 * src/main/resources/sun_checks.xml  //OK
174 * src/main/resources/check_properties.properties  //OK
175 * src/main/resources/JavaClass.java  //violation, xml|property files are allowed in resource folder
176 * </pre>
177 *
178 * <p>
179 * To configure the check to only allow Java and XML files in your folders use
180 * the below.
181 * </p>
182 *
183 * <pre>
184 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
185 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;\.(java|xml)$&quot;/&gt;
186 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
187 * &lt;/module&gt;
188 * </pre>
189 *
190 * <p>Example:</p>
191 * <pre>
192 * src/main/java/JavaClass.java  //OK
193 * src/main/MainClass.java  //OK
194 * src/main/java/java_xml.xml  //OK
195 * src/main/main_xml.xml  //OK
196 * src/main/java/image.png  //violation, folders should only contain java or xml files
197 * src/main/check_properties.properties  //violation, folders should only contain java or xml files
198 * </pre>
199 *
200 * <p>
201 * To configure the check to only allow Java and XML files only in your source
202 * folder and ignore any other folders:
203 * </p>
204 *
205 * <pre>
206 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
207 *   &lt;property name=&quot;folderPattern&quot; value=&quot;[\\/]src[\\/]&quot;/&gt;
208 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;\.(java|xml)$&quot;/&gt;
209 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
210 * &lt;/module&gt;
211 * </pre>
212 *
213 * <p>Example:</p>
214 * <pre>
215 * src/SourceClass.java  //OK
216 * src/source_xml.xml  //OK
217 * src/image.png  //violation, only java and xml files are allowed in src folder
218 * src/main/main_properties.properties  //OK, this check only applies to src folder
219 * </pre>
220 *
221 * <p>
222 * <b>Note:</b> 'folderPattern' must be specified if checkstyle is analyzing
223 * more than the normal source folder, like the 'bin' folder where class files
224 * can be located.
225 * </p>
226 *
227 * <p>
228 * To configure the check to only allow file names to be camel case:
229 * </p>
230 *
231 * <pre>
232 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
233 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;^([A-Z][a-z0-9]+\.?)+$&quot;/&gt;
234 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
235 *   &lt;property name=&quot;ignoreFileNameExtensions&quot; value=&quot;true&quot;/&gt;
236 * &lt;/module&gt;
237 * </pre>
238 *
239 * <p>Example:</p>
240 * <pre>
241 * src/main/java/JavaClass.java  //OK
242 * src/main/MainClass.java  //OK
243 * src/main/java/java_class.java  //violation, file names should be in Camel Case
244 * src/main/main_class.java  //violation, file names should be in Camel Case
245 * </pre>
246 * <p>
247 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker}
248 * </p>
249 * <p>
250 * Violation Message Keys:
251 * </p>
252 * <ul>
253 * <li>
254 * {@code regexp.filename.match}
255 * </li>
256 * <li>
257 * {@code regexp.filename.mismatch}
258 * </li>
259 * </ul>
260 *
261 * @since 6.15
262 */
263@StatelessCheck
264public class RegexpOnFilenameCheck extends AbstractFileSetCheck {
265
266    /**
267     * A key is pointing to the warning message text in "messages.properties"
268     * file.
269     */
270    public static final String MSG_MATCH = "regexp.filename.match";
271    /**
272     * A key is pointing to the warning message text in "messages.properties"
273     * file.
274     */
275    public static final String MSG_MISMATCH = "regexp.filename.mismatch";
276
277    /** Specify the regular expression to match the folder path against. */
278    private Pattern folderPattern;
279    /** Specify the regular expression to match the file name against. */
280    private Pattern fileNamePattern;
281    /**
282     * Control whether to look for a match or mis-match on the file name,
283     * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
284     */
285    private boolean match = true;
286    /** Control whether to ignore the file extension for the file name match. */
287    private boolean ignoreFileNameExtensions;
288
289    /**
290     * Setter to specify the regular expression to match the folder path against.
291     *
292     * @param folderPattern format of folder.
293     */
294    public void setFolderPattern(Pattern folderPattern) {
295        this.folderPattern = folderPattern;
296    }
297
298    /**
299     * Setter to specify the regular expression to match the file name against.
300     *
301     * @param fileNamePattern format of file.
302     */
303    public void setFileNamePattern(Pattern fileNamePattern) {
304        this.fileNamePattern = fileNamePattern;
305    }
306
307    /**
308     * Setter to control whether to look for a match or mis-match on the file name,
309     * if the fileNamePattern is supplied, otherwise it is applied on the folderPattern.
310     *
311     * @param match check's option for matching file names.
312     */
313    public void setMatch(boolean match) {
314        this.match = match;
315    }
316
317    /**
318     * Setter to control whether to ignore the file extension for the file name match.
319     *
320     * @param ignoreFileNameExtensions check's option for ignoring file extension.
321     */
322    public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) {
323        this.ignoreFileNameExtensions = ignoreFileNameExtensions;
324    }
325
326    @Override
327    public void init() {
328        if (fileNamePattern == null && folderPattern == null) {
329            fileNamePattern = CommonUtil.createPattern("\\s");
330        }
331    }
332
333    @Override
334    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
335        final String fileName = getFileName(file);
336        final String folderPath = getFolderPath(file);
337
338        if (isMatchFolder(folderPath) && isMatchFile(fileName)) {
339            log();
340        }
341    }
342
343    /**
344     * Retrieves the file name from the given {@code file}.
345     *
346     * @param file Input file to examine.
347     * @return The file name.
348     */
349    private String getFileName(File file) {
350        String fileName = file.getName();
351
352        if (ignoreFileNameExtensions) {
353            fileName = CommonUtil.getFileNameWithoutExtension(fileName);
354        }
355
356        return fileName;
357    }
358
359    /**
360     * Retrieves the folder path from the given {@code file}.
361     *
362     * @param file Input file to examine.
363     * @return The folder path.
364     * @throws CheckstyleException if there is an error getting the canonical
365     *         path of the {@code file}.
366     */
367    private static String getFolderPath(File file) throws CheckstyleException {
368        try {
369            return file.getCanonicalFile().getParent();
370        }
371        catch (IOException ex) {
372            throw new CheckstyleException("unable to create canonical path names for "
373                    + file.getAbsolutePath(), ex);
374        }
375    }
376
377    /**
378     * Checks if the given {@code folderPath} matches the specified
379     * {@link #folderPattern}.
380     *
381     * @param folderPath Input folder path to examine.
382     * @return true if they do match.
383     */
384    private boolean isMatchFolder(String folderPath) {
385        final boolean result;
386
387        // null pattern always matches, regardless of value of 'match'
388        if (folderPattern == null) {
389            result = true;
390        }
391        else {
392            // null pattern means 'match' applies to the folderPattern matching
393            final boolean useMatch = fileNamePattern != null || match;
394            result = folderPattern.matcher(folderPath).find() == useMatch;
395        }
396
397        return result;
398    }
399
400    /**
401     * Checks if the given {@code fileName} matches the specified
402     * {@link #fileNamePattern}.
403     *
404     * @param fileName Input file name to examine.
405     * @return true if they do match.
406     */
407    private boolean isMatchFile(String fileName) {
408        // null pattern always matches, regardless of value of 'match'
409        return fileNamePattern == null || fileNamePattern.matcher(fileName).find() == match;
410    }
411
412    /** Logs the violations for the check. */
413    private void log() {
414        final String folder = getStringOrDefault(folderPattern, "");
415        final String fileName = getStringOrDefault(fileNamePattern, "");
416
417        if (match) {
418            log(1, MSG_MATCH, folder, fileName);
419        }
420        else {
421            log(1, MSG_MISMATCH, folder, fileName);
422        }
423    }
424
425    /**
426     * Retrieves the String form of the {@code pattern} or {@code defaultString}
427     * if null.
428     *
429     * @param pattern The pattern to convert.
430     * @param defaultString The result to use if {@code pattern} is null.
431     * @return The String form of the {@code pattern}.
432     */
433    private static String getStringOrDefault(Pattern pattern, String defaultString) {
434        final String result;
435
436        if (pattern == null) {
437            result = defaultString;
438        }
439        else {
440            result = pattern.toString();
441        }
442
443        return result;
444    }
445
446}