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