View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FileContents;
29  import com.puppycrawl.tools.checkstyle.api.FileText;
30  import com.puppycrawl.tools.checkstyle.api.LineColumn;
31  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
32  
33  /**
34   * <p>
35   * A check that makes sure that a specified pattern exists (or not) in the file.
36   * </p>
37   * <p>
38   * An example of how to configure the check to make sure a copyright statement
39   * is included in the file (but without requirements on where in the file
40   * it should be):
41   * </p>
42   * <pre>
43   * &lt;module name="RegexpCheck"&gt;
44   *    &lt;property name="format" value="This code is copyrighted"/&gt;
45   * &lt;/module&gt;
46   * </pre>
47   * <p>
48   * And to make sure the same statement appears at the beginning of the file.
49   * </p>
50   * <pre>
51   * &lt;module name="RegexpCheck"&gt;
52   *    &lt;property name="format" value="\AThis code is copyrighted"/&gt;
53   * &lt;/module&gt;
54   * </pre>
55   * @author Stan Quinn
56   */
57  @FileStatefulCheck
58  public class RegexpCheck extends AbstractCheck {
59  
60      /**
61       * A key is pointing to the warning message text in "messages.properties"
62       * file.
63       */
64      public static final String MSG_ILLEGAL_REGEXP = "illegal.regexp";
65  
66      /**
67       * A key is pointing to the warning message text in "messages.properties"
68       * file.
69       */
70      public static final String MSG_REQUIRED_REGEXP = "required.regexp";
71  
72      /**
73       * A key is pointing to the warning message text in "messages.properties"
74       * file.
75       */
76      public static final String MSG_DUPLICATE_REGEXP = "duplicate.regexp";
77  
78      /** Default duplicate limit. */
79      private static final int DEFAULT_DUPLICATE_LIMIT = -1;
80  
81      /** Default error report limit. */
82      private static final int DEFAULT_ERROR_LIMIT = 100;
83  
84      /** Error count exceeded message. */
85      private static final String ERROR_LIMIT_EXCEEDED_MESSAGE =
86          "The error limit has been exceeded, "
87          + "the check is aborting, there may be more unreported errors.";
88  
89      /** Custom message for report. */
90      private String message = "";
91  
92      /** Ignore matches within comments?. **/
93      private boolean ignoreComments;
94  
95      /** Pattern illegal?. */
96      private boolean illegalPattern;
97  
98      /** Error report limit. */
99      private int errorLimit = DEFAULT_ERROR_LIMIT;
100 
101     /** Disallow more than x duplicates?. */
102     private int duplicateLimit;
103 
104     /** Boolean to say if we should check for duplicates. */
105     private boolean checkForDuplicates;
106 
107     /** Tracks number of matches made. */
108     private int matchCount;
109 
110     /** Tracks number of errors. */
111     private int errorCount;
112 
113     /** The regexp to match against. */
114     private Pattern format = Pattern.compile("$^", Pattern.MULTILINE);
115 
116     /** The matcher. */
117     private Matcher matcher;
118 
119     /**
120      * Setter for message property.
121      * @param message custom message which should be used in report.
122      */
123     public void setMessage(String message) {
124         if (message == null) {
125             this.message = "";
126         }
127         else {
128             this.message = message;
129         }
130     }
131 
132     /**
133      * Sets if matches within comments should be ignored.
134      * @param ignoreComments True if comments should be ignored.
135      */
136     public void setIgnoreComments(boolean ignoreComments) {
137         this.ignoreComments = ignoreComments;
138     }
139 
140     /**
141      * Sets if pattern is illegal, otherwise pattern is required.
142      * @param illegalPattern True if pattern is not allowed.
143      */
144     public void setIllegalPattern(boolean illegalPattern) {
145         this.illegalPattern = illegalPattern;
146     }
147 
148     /**
149      * Sets the limit on the number of errors to report.
150      * @param errorLimit the number of errors to report.
151      */
152     public void setErrorLimit(int errorLimit) {
153         this.errorLimit = errorLimit;
154     }
155 
156     /**
157      * Sets the maximum number of instances of required pattern allowed.
158      * @param duplicateLimit negative values mean no duplicate checking,
159      *     any positive value is used as the limit.
160      */
161     public void setDuplicateLimit(int duplicateLimit) {
162         this.duplicateLimit = duplicateLimit;
163         checkForDuplicates = duplicateLimit > DEFAULT_DUPLICATE_LIMIT;
164     }
165 
166     /**
167      * Set the format to the specified regular expression.
168      * @param pattern the new pattern
169      * @throws org.apache.commons.beanutils.ConversionException unable to parse format
170      */
171     public final void setFormat(Pattern pattern) {
172         format = CommonUtils.createPattern(pattern.pattern(), Pattern.MULTILINE);
173     }
174 
175     @Override
176     public int[] getDefaultTokens() {
177         return getAcceptableTokens();
178     }
179 
180     @Override
181     public int[] getAcceptableTokens() {
182         return CommonUtils.EMPTY_INT_ARRAY;
183     }
184 
185     @Override
186     public int[] getRequiredTokens() {
187         return getAcceptableTokens();
188     }
189 
190     @Override
191     public void beginTree(DetailAST rootAST) {
192         matcher = format.matcher(getFileContents().getText().getFullText());
193         matchCount = 0;
194         errorCount = 0;
195         findMatch();
196     }
197 
198     /** Recursive method that finds the matches. */
199     private void findMatch() {
200 
201         final boolean foundMatch = matcher.find();
202         if (foundMatch) {
203             final FileText text = getFileContents().getText();
204             final LineColumn start = text.lineColumn(matcher.start());
205             final int startLine = start.getLine();
206 
207             final boolean ignore = isIgnore(startLine, text, start);
208 
209             if (!ignore) {
210                 matchCount++;
211                 if (illegalPattern || checkForDuplicates
212                         && matchCount - 1 > duplicateLimit) {
213                     errorCount++;
214                     logMessage(startLine);
215                 }
216             }
217             if (canContinueValidation(ignore)) {
218                 findMatch();
219             }
220         }
221         else if (!illegalPattern && matchCount == 0) {
222             logMessage(0);
223         }
224 
225     }
226 
227     /**
228      * Check if we can stop validation.
229      * @param ignore flag
230      * @return true is we can continue
231      */
232     private boolean canContinueValidation(boolean ignore) {
233         return errorCount <= errorLimit - 1
234                 && (ignore || illegalPattern || checkForDuplicates);
235     }
236 
237     /**
238      * Detect ignore situation.
239      * @param startLine position of line
240      * @param text file text
241      * @param start line column
242      * @return true is that need to be ignored
243      */
244     private boolean isIgnore(int startLine, FileText text, LineColumn start) {
245         final LineColumn end;
246         if (matcher.end() == 0) {
247             end = text.lineColumn(0);
248         }
249         else {
250             end = text.lineColumn(matcher.end() - 1);
251         }
252         boolean ignore = false;
253         if (ignoreComments) {
254             final FileContents theFileContents = getFileContents();
255             final int startColumn = start.getColumn();
256             final int endLine = end.getLine();
257             final int endColumn = end.getColumn();
258             ignore = theFileContents.hasIntersectionWithComment(startLine,
259                 startColumn, endLine, endColumn);
260         }
261         return ignore;
262     }
263 
264     /**
265      * Displays the right message.
266      * @param lineNumber the line number the message relates to.
267      */
268     private void logMessage(int lineNumber) {
269         String msg;
270 
271         if (message.isEmpty()) {
272             msg = format.pattern();
273         }
274         else {
275             msg = message;
276         }
277 
278         if (errorCount >= errorLimit) {
279             msg = ERROR_LIMIT_EXCEEDED_MESSAGE + msg;
280         }
281 
282         if (illegalPattern) {
283             log(lineNumber, MSG_ILLEGAL_REGEXP, msg);
284         }
285         else {
286             if (lineNumber > 0) {
287                 log(lineNumber, MSG_DUPLICATE_REGEXP, msg);
288             }
289             else {
290                 log(lineNumber, MSG_REQUIRED_REGEXP, msg);
291             }
292         }
293     }
294 }