001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.apache.commons.lang3.ArrayUtils; 026 027import com.puppycrawl.tools.checkstyle.api.Check; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.FileContents; 030import com.puppycrawl.tools.checkstyle.api.FileText; 031import com.puppycrawl.tools.checkstyle.api.LineColumn; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 033 034/** 035 * <p> 036 * A check that makes sure that a specified pattern exists (or not) in the file. 037 * </p> 038 * <p> 039 * An example of how to configure the check to make sure a copyright statement 040 * is included in the file (but without requirements on where in the file 041 * it should be): 042 * </p> 043 * <pre> 044 * <module name="RegexpCheck"> 045 * <property name="format" value="This code is copyrighted"/> 046 * </module> 047 * </pre> 048 * <p> 049 * And to make sure the same statement appears at the beginning of the file. 050 * </p> 051 * <pre> 052 * <module name="RegexpCheck"> 053 * <property name="format" value="\AThis code is copyrighted"/> 054 * </module> 055 * </pre> 056 * @author Stan Quinn 057 */ 058public class RegexpCheck extends Check { 059 060 /** 061 * A key is pointing to the warning message text in "messages.properties" 062 * file. 063 */ 064 public static final String MSG_ILLEGAL_REGEXP = "illegal.regexp"; 065 066 /** 067 * A key is pointing to the warning message text in "messages.properties" 068 * file. 069 */ 070 public static final String MSG_REQUIRED_REGEXP = "required.regexp"; 071 072 /** 073 * A key is pointing to the warning message text in "messages.properties" 074 * file. 075 */ 076 public static final String MSG_DUPLICATE_REGEXP = "duplicate.regexp"; 077 078 /** Default duplicate limit. */ 079 private static final int DEFAULT_DUPLICATE_LIMIT = -1; 080 081 /** Default error report limit. */ 082 private static final int DEFAULT_ERROR_LIMIT = 100; 083 084 /** Error count exceeded message. */ 085 private static final String ERROR_LIMIT_EXCEEDED_MESSAGE = 086 "The error limit has been exceeded, " 087 + "the check is aborting, there may be more unreported errors."; 088 089 /** Custom message for report. */ 090 private String message = ""; 091 092 /** Ignore matches within comments?. **/ 093 private boolean ignoreComments; 094 095 /** Pattern illegal?. */ 096 private boolean illegalPattern; 097 098 /** Error report limit. */ 099 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 format string of the regexp. */ 114 private String format = "$^"; 115 116 /** The regexp to match against. */ 117 private Pattern regexp = Pattern.compile(format, Pattern.MULTILINE); 118 119 /** The matcher. */ 120 private Matcher matcher; 121 122 /** 123 * Setter for message property. 124 * @param message custom message which should be used in report. 125 */ 126 public void setMessage(String message) { 127 if (message == null) { 128 this.message = ""; 129 } 130 else { 131 this.message = message; 132 } 133 } 134 135 /** 136 * Sets if matches within comments should be ignored. 137 * @param ignoreComments True if comments should be ignored. 138 */ 139 public void setIgnoreComments(boolean ignoreComments) { 140 this.ignoreComments = ignoreComments; 141 } 142 143 /** 144 * Sets if pattern is illegal, otherwise pattern is required. 145 * @param illegalPattern True if pattern is not allowed. 146 */ 147 public void setIllegalPattern(boolean illegalPattern) { 148 this.illegalPattern = illegalPattern; 149 } 150 151 /** 152 * Sets the limit on the number of errors to report. 153 * @param errorLimit the number of errors to report. 154 */ 155 public void setErrorLimit(int errorLimit) { 156 this.errorLimit = errorLimit; 157 } 158 159 /** 160 * Sets the maximum number of instances of required pattern allowed. 161 * @param duplicateLimit negative values mean no duplicate checking, 162 * any positive value is used as the limit. 163 */ 164 public void setDuplicateLimit(int duplicateLimit) { 165 this.duplicateLimit = duplicateLimit; 166 checkForDuplicates = duplicateLimit > DEFAULT_DUPLICATE_LIMIT; 167 } 168 169 /** 170 * Set the format to the specified regular expression. 171 * @param format a {@code String} value 172 * @throws org.apache.commons.beanutils.ConversionException unable to parse format 173 */ 174 public final void setFormat(String format) { 175 this.format = format; 176 regexp = CommonUtils.createPattern(format, Pattern.MULTILINE); 177 } 178 179 @Override 180 public int[] getDefaultTokens() { 181 return getAcceptableTokens(); 182 } 183 184 @Override 185 public int[] getAcceptableTokens() { 186 return ArrayUtils.EMPTY_INT_ARRAY; 187 } 188 189 @Override 190 public int[] getRequiredTokens() { 191 return getAcceptableTokens(); 192 } 193 194 @Override 195 public void beginTree(DetailAST rootAST) { 196 matcher = regexp.matcher(getFileContents().getText().getFullText()); 197 matchCount = 0; 198 errorCount = 0; 199 findMatch(); 200 } 201 202 /** Recursive method that finds the matches. */ 203 private void findMatch() { 204 205 final boolean foundMatch = matcher.find(); 206 if (foundMatch) { 207 final FileText text = getFileContents().getText(); 208 final LineColumn start = text.lineColumn(matcher.start()); 209 final int startLine = start.getLine(); 210 211 final boolean ignore = isIgnore(startLine, text, start); 212 213 if (!ignore) { 214 matchCount++; 215 if (illegalPattern || checkForDuplicates 216 && matchCount - 1 > duplicateLimit) { 217 errorCount++; 218 logMessage(startLine); 219 } 220 } 221 if (canContinueValidation(ignore)) { 222 findMatch(); 223 } 224 } 225 else if (!illegalPattern && matchCount == 0) { 226 logMessage(0); 227 } 228 229 } 230 231 /** 232 * Check if we can stop validation. 233 * @param ignore flag 234 * @return true is we can continue 235 */ 236 private boolean canContinueValidation(boolean ignore) { 237 return errorCount < errorLimit 238 && (ignore || illegalPattern || checkForDuplicates); 239 } 240 241 /** 242 * Detect ignore situation. 243 * @param startLine position of line 244 * @param text file text 245 * @param start line column 246 * @return true is that need to be ignored 247 */ 248 private boolean isIgnore(int startLine, FileText text, LineColumn start) { 249 final LineColumn end; 250 if (matcher.end() == 0) { 251 end = text.lineColumn(0); 252 } 253 else { 254 end = text.lineColumn(matcher.end() - 1); 255 } 256 final int startColumn = start.getColumn(); 257 final int endLine = end.getLine(); 258 final int endColumn = end.getColumn(); 259 boolean ignore = false; 260 if (ignoreComments) { 261 final FileContents theFileContents = getFileContents(); 262 ignore = theFileContents.hasIntersectionWithComment(startLine, 263 startColumn, endLine, endColumn); 264 } 265 return ignore; 266 } 267 268 /** 269 * Displays the right message. 270 * @param lineNumber the line number the message relates to. 271 */ 272 private void logMessage(int lineNumber) { 273 String msg; 274 275 if (message.isEmpty()) { 276 msg = format; 277 } 278 else { 279 msg = message; 280 } 281 282 if (errorCount >= errorLimit) { 283 msg = ERROR_LIMIT_EXCEEDED_MESSAGE + msg; 284 } 285 286 if (illegalPattern) { 287 log(lineNumber, MSG_ILLEGAL_REGEXP, msg); 288 } 289 else { 290 if (lineNumber > 0) { 291 log(lineNumber, MSG_DUPLICATE_REGEXP, msg); 292 } 293 else { 294 log(lineNumber, MSG_REQUIRED_REGEXP, msg); 295 } 296 } 297 } 298}