001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.List;
025import java.util.regex.Pattern;
026
027import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
028import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
030
031/**
032 * <p>
033 * Implementation of a check that looks for a file name and/or path match (or
034 * mis-match) against specified patterns. It can also be used to verify files
035 * match specific naming patterns not covered by other checks (Ex: properties,
036 * xml, etc.).
037 * </p>
038 *
039 * <p>
040 * When customizing the check, the properties are applied in a specific order.
041 * The fileExtensions property first picks only files that match any of the
042 * specific extensions supplied. Once files are matched against the
043 * fileExtensions, the match property is then used in conjunction with the
044 * patterns to determine if the check is looking for a match or mis-match on
045 * those files. If the fileNamePattern is supplied, the matching is only applied
046 * to the fileNamePattern and not the folderPattern. If no fileNamePattern is
047 * supplied, then matching is applied to the folderPattern only and will result
048 * in all files in a folder to be reported on violations. If no folderPattern is
049 * supplied, then all folders that checkstyle finds are examined for violations.
050 * The ignoreFileNameExtensions property drops the file extension and applies
051 * the fileNamePattern only to the rest of file name. For example, if the file
052 * is named 'test.java' and this property is turned on, the pattern is only
053 * applied to 'test'.
054 * </p>
055 *
056 * <p>
057 * If this check is configured with no properties, then the default behavior of
058 * this check is to report file names with spaces in them. When at least one
059 * pattern property is supplied, the entire check is under the user's control to
060 * allow them to fully customize the behavior.
061 * </p>
062 *
063 * <p>
064 * It is recommended that if you create your own pattern, to also specify a
065 * custom error message. This allows the error message printed to be clear what
066 * the violation is, especially if multiple RegexpOnFilename checks are used.
067 * Argument 0 for the message populates the check's folderPattern. Argument 1
068 * for the message populates the check's fileNamePattern. The file name is not
069 * passed as an argument since it is part of CheckStyle's default error
070 * messages.
071 * </p>
072 *
073 * <p>
074 * Check have following options:
075 * </p>
076 * <ul>
077 * <li>
078 * folderPattern - Regular expression to match the folder path against. Default
079 * value is null.</li>
080 *
081 * <li>
082 * fileNamePattern - Regular expression to match the file name against. Default
083 * value is null.</li>
084 *
085 * <li>
086 * match - Whether to look for a match or mis-match on the file name, if the
087 * fileNamePattern is supplied, otherwise it is applied on the folderPattern.
088 * Default value is true.</li>
089 *
090 * <li>
091 * ignoreFileNameExtensions - Whether to ignore the file extension for the file
092 * name match. Default value is false.</li>
093 *
094 * <li>
095 * fileExtensions - File type extension of files to process. If this is
096 * specified, then only files that match these types are examined with the other
097 * patterns. Default value is {}.</li>
098 * </ul>
099 * <br>
100 *
101 * <p>
102 * To configure the check to report file names that contain a space:
103 * </p>
104 *
105 * <pre>
106 * &lt;module name=&quot;RegexpOnFilename&quot;/&gt;
107 * </pre>
108 * <p>
109 * To configure the check to force picture files to not be 'gif':
110 * </p>
111 *
112 * <pre>
113 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
114 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;\\.gif$&quot;/&gt;
115 * &lt;/module&gt;
116 * </pre>
117 * <p>
118 * OR:
119 * </p>
120 *
121 * <pre>
122 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
123 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;.&quot;/&gt;
124 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;gif&quot;/&gt;
125 * &lt;/module&gt;
126 * </pre>
127 *
128 * <p>
129 * To configure the check to only allow property and xml files to be located in
130 * the resource folder:
131 * </p>
132 *
133 * <pre>
134 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
135 *   &lt;property name=&quot;folderPattern&quot;
136 *     value=&quot;[\\/]src[\\/]\\w+[\\/]resources[\\/]&quot;/&gt;
137 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
138 *   &lt;property name=&quot;fileExtensions&quot; value=&quot;properties, xml&quot;/&gt;
139 * &lt;/module&gt;
140 * </pre>
141 *
142 * <p>
143 * To configure the check to only allow Java and XML files in your folders use
144 * the below.
145 * </p>
146 *
147 * <pre>
148 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
149 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;\\.(java|xml)$&quot;/&gt;
150 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
151 * &lt;/module&gt;
152 * </pre>
153 * <p>
154 * To configure the check to only allow Java and XML files only in your source
155 * folder and ignore any other folders:
156 * </p>
157 *
158 * <p>
159 * <b>Note:</b> 'folderPattern' must be specified if checkstyle is analyzing
160 * more than the normal source folder, like the 'bin' folder where class files
161 * can be located.
162 * </p>
163 *
164 * <pre>
165 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
166 *   &lt;property name=&quot;folderPattern&quot; value=&quot;[\\/]src[\\/]&quot;/&gt;
167 *   &lt;property name=&quot;fileNamePattern&quot; value=&quot;\\.(java|xml)$&quot;/&gt;
168 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
169 * &lt;/module&gt;
170 * </pre>
171 * <p>
172 * To configure the check to only allow file names to be camel case:
173 * </p>
174 *
175 * <pre>
176 * &lt;module name=&quot;RegexpOnFilename&quot;&gt;
177 *   &lt;property name=&quot;fileNamePattern&quot;
178 *     value=&quot;^([A-Z][a-z0-9]+\.?)+$&quot;/&gt;
179 *   &lt;property name=&quot;match&quot; value=&quot;false&quot;/&gt;
180 *   &lt;property name=&quot;ignoreFileNameExtensions&quot; value=&quot;true&quot;/&gt;
181 * &lt;/module&gt;
182 * </pre>
183 *
184 * @author Richard Veach
185 */
186public class RegexpOnFilenameCheck extends AbstractFileSetCheck {
187    /**
188     * A key is pointing to the warning message text in "messages.properties"
189     * file.
190     */
191    public static final String MSG_MATCH = "regexp.filename.match";
192    /**
193     * A key is pointing to the warning message text in "messages.properties"
194     * file.
195     */
196    public static final String MSG_MISMATCH = "regexp.filename.mismatch";
197
198    /** Compiled regexp to match a folder. */
199    private Pattern folderPattern;
200    /** Compiled regexp to match a file. */
201    private Pattern fileNamePattern;
202    /** Whether to look for a file name match or mismatch. */
203    private boolean match = true;
204    /** Whether to ignore the file's extension when looking for matches. */
205    private boolean ignoreFileNameExtensions;
206
207    /**
208     * Setter for folder format.
209     *
210     * @param folderPattern format of folder.
211     */
212    public void setFolderPattern(Pattern folderPattern) {
213        this.folderPattern = folderPattern;
214    }
215
216    /**
217     * Setter for file name format.
218     *
219     * @param fileNamePattern format of file.
220     */
221    public void setFileNamePattern(Pattern fileNamePattern) {
222        this.fileNamePattern = fileNamePattern;
223    }
224
225    /**
226     * Sets whether the check should look for a file name match or mismatch.
227     *
228     * @param match check's option for matching file names.
229     */
230    public void setMatch(boolean match) {
231        this.match = match;
232    }
233
234    /**
235     * Sets whether file name matching should drop the file extension or not.
236     *
237     * @param ignoreFileNameExtensions check's option for ignoring file extension.
238     */
239    public void setIgnoreFileNameExtensions(boolean ignoreFileNameExtensions) {
240        this.ignoreFileNameExtensions = ignoreFileNameExtensions;
241    }
242
243    @Override
244    public void init() {
245        if (fileNamePattern == null && folderPattern == null) {
246            fileNamePattern = CommonUtils.createPattern("\\s");
247        }
248    }
249
250    @Override
251    protected void processFiltered(File file, List<String> lines) throws CheckstyleException {
252        final String fileName = getFileName(file);
253        final String folderPath = getFolderPath(file);
254
255        if (isMatchFolder(folderPath) && isMatchFile(fileName)) {
256            log();
257        }
258    }
259
260    /**
261     * Retrieves the file name from the given {@code file}.
262     *
263     * @param file Input file to examine.
264     * @return The file name.
265     */
266    private String getFileName(File file) {
267        String fileName = file.getName();
268
269        if (ignoreFileNameExtensions) {
270            fileName = CommonUtils.getFileNameWithoutExtension(fileName);
271        }
272
273        return fileName;
274    }
275
276    /**
277     * Retrieves the folder path from the given {@code file}.
278     *
279     * @param file Input file to examine.
280     * @return The folder path.
281     * @throws CheckstyleException if there is an error getting the canonical
282     *         path of the {@code file}.
283     */
284    private static String getFolderPath(File file) throws CheckstyleException {
285        try {
286            return file.getParentFile().getCanonicalPath();
287        }
288        catch (IOException ex) {
289            throw new CheckstyleException("unable to create canonical path names for "
290                    + file.getAbsolutePath(), ex);
291        }
292    }
293
294    /**
295     * Checks if the given {@code folderPath} matches the specified
296     * {@link #folderPattern}.
297     *
298     * @param folderPath Input folder path to examine.
299     * @return true if they do match.
300     */
301    private boolean isMatchFolder(String folderPath) {
302        final boolean result;
303
304        // null pattern always matches, regardless of value of 'match'
305        if (folderPattern == null) {
306            result = true;
307        }
308        else {
309            final boolean useMatch;
310
311            // null pattern means 'match' applies to the folderPattern matching
312            if (fileNamePattern == null) {
313                useMatch = match;
314            }
315            else {
316                useMatch = true;
317            }
318
319            result = folderPattern.matcher(folderPath).find() == useMatch;
320        }
321
322        return result;
323    }
324
325    /**
326     * Checks if the given {@code fileName} matches the specified
327     * {@link #fileNamePattern}.
328     *
329     * @param fileName Input file name to examine.
330     * @return true if they do match.
331     */
332    private boolean isMatchFile(String fileName) {
333        final boolean result;
334
335        // null pattern always matches, regardless of value of 'match'
336        if (fileNamePattern == null) {
337            result = true;
338        }
339        else {
340            result = fileNamePattern.matcher(fileName).find() == match;
341        }
342
343        return result;
344    }
345
346    /** Logs the errors for the check. */
347    private void log() {
348        final String folder = getStringOrDefault(folderPattern, "");
349        final String fileName = getStringOrDefault(fileNamePattern, "");
350
351        if (match) {
352            log(0, MSG_MATCH, folder, fileName);
353        }
354        else {
355            log(0, MSG_MISMATCH, folder, fileName);
356        }
357    }
358
359    /**
360     * Retrieves the String form of the {@code pattern} or {@code defaultString}
361     * if null.
362     *
363     * @param pattern The pattern to convert.
364     * @param defaultString The result to use if {@code pattern} is null.
365     * @return The String form of the {@code pattern}.
366     */
367    private static String getStringOrDefault(Pattern pattern, String defaultString) {
368        final String result;
369
370        if (pattern == null) {
371            result = defaultString;
372        }
373        else {
374            result = pattern.toString();
375        }
376
377        return result;
378    }
379}