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.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 regexp to match against. */ 112 private Pattern format = Pattern.compile("$^", Pattern.MULTILINE); 113 114 /** The matcher. */ 115 private Matcher matcher; 116 117 /** 118 * Setter for message property. 119 * @param message custom message which should be used in report. 120 */ 121 public void setMessage(String message) { 122 if (message == null) { 123 this.message = ""; 124 } 125 else { 126 this.message = message; 127 } 128 } 129 130 /** 131 * Sets if matches within comments should be ignored. 132 * @param ignoreComments True if comments should be ignored. 133 */ 134 public void setIgnoreComments(boolean ignoreComments) { 135 this.ignoreComments = ignoreComments; 136 } 137 138 /** 139 * Sets if pattern is illegal, otherwise pattern is required. 140 * @param illegalPattern True if pattern is not allowed. 141 */ 142 public void setIllegalPattern(boolean illegalPattern) { 143 this.illegalPattern = illegalPattern; 144 } 145 146 /** 147 * Sets the limit on the number of errors to report. 148 * @param errorLimit the number of errors to report. 149 */ 150 public void setErrorLimit(int errorLimit) { 151 this.errorLimit = errorLimit; 152 } 153 154 /** 155 * Sets the maximum number of instances of required pattern allowed. 156 * @param duplicateLimit negative values mean no duplicate checking, 157 * any positive value is used as the limit. 158 */ 159 public void setDuplicateLimit(int duplicateLimit) { 160 this.duplicateLimit = duplicateLimit; 161 checkForDuplicates = duplicateLimit > DEFAULT_DUPLICATE_LIMIT; 162 } 163 164 /** 165 * Set the format to the specified regular expression. 166 * @param pattern the new pattern 167 * @throws org.apache.commons.beanutils.ConversionException unable to parse format 168 */ 169 public final void setFormat(Pattern pattern) { 170 format = CommonUtils.createPattern(pattern.pattern(), Pattern.MULTILINE); 171 } 172 173 @Override 174 public int[] getDefaultTokens() { 175 return getAcceptableTokens(); 176 } 177 178 @Override 179 public int[] getAcceptableTokens() { 180 return CommonUtils.EMPTY_INT_ARRAY; 181 } 182 183 @Override 184 public int[] getRequiredTokens() { 185 return getAcceptableTokens(); 186 } 187 188 @Override 189 public void beginTree(DetailAST rootAST) { 190 matcher = format.matcher(getFileContents().getText().getFullText()); 191 matchCount = 0; 192 errorCount = 0; 193 findMatch(); 194 } 195 196 /** Recursive method that finds the matches. */ 197 private void findMatch() { 198 199 final boolean foundMatch = matcher.find(); 200 if (foundMatch) { 201 final FileText text = getFileContents().getText(); 202 final LineColumn start = text.lineColumn(matcher.start()); 203 final int startLine = start.getLine(); 204 205 final boolean ignore = isIgnore(startLine, text, start); 206 207 if (!ignore) { 208 matchCount++; 209 if (illegalPattern || checkForDuplicates 210 && matchCount - 1 > duplicateLimit) { 211 errorCount++; 212 logMessage(startLine); 213 } 214 } 215 if (canContinueValidation(ignore)) { 216 findMatch(); 217 } 218 } 219 else if (!illegalPattern && matchCount == 0) { 220 logMessage(0); 221 } 222 223 } 224 225 /** 226 * Check if we can stop validation. 227 * @param ignore flag 228 * @return true is we can continue 229 */ 230 private boolean canContinueValidation(boolean ignore) { 231 return errorCount < errorLimit 232 && (ignore || illegalPattern || checkForDuplicates); 233 } 234 235 /** 236 * Detect ignore situation. 237 * @param startLine position of line 238 * @param text file text 239 * @param start line column 240 * @return true is that need to be ignored 241 */ 242 private boolean isIgnore(int startLine, FileText text, LineColumn start) { 243 final LineColumn end; 244 if (matcher.end() == 0) { 245 end = text.lineColumn(0); 246 } 247 else { 248 end = text.lineColumn(matcher.end() - 1); 249 } 250 boolean ignore = false; 251 if (ignoreComments) { 252 final FileContents theFileContents = getFileContents(); 253 final int startColumn = start.getColumn(); 254 final int endLine = end.getLine(); 255 final int endColumn = end.getColumn(); 256 ignore = theFileContents.hasIntersectionWithComment(startLine, 257 startColumn, endLine, endColumn); 258 } 259 return ignore; 260 } 261 262 /** 263 * Displays the right message. 264 * @param lineNumber the line number the message relates to. 265 */ 266 private void logMessage(int lineNumber) { 267 String msg; 268 269 if (message.isEmpty()) { 270 msg = format.pattern(); 271 } 272 else { 273 msg = message; 274 } 275 276 if (errorCount >= errorLimit) { 277 msg = ERROR_LIMIT_EXCEEDED_MESSAGE + msg; 278 } 279 280 if (illegalPattern) { 281 log(lineNumber, MSG_ILLEGAL_REGEXP, msg); 282 } 283 else { 284 if (lineNumber > 0) { 285 log(lineNumber, MSG_DUPLICATE_REGEXP, msg); 286 } 287 else { 288 log(lineNumber, MSG_REQUIRED_REGEXP, msg); 289 } 290 } 291 } 292}