001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.FileContents; 029import com.puppycrawl.tools.checkstyle.api.FileText; 030import com.puppycrawl.tools.checkstyle.api.LineColumn; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 032 033/** 034 * <p> 035 * A check that makes sure that a specified pattern exists (or not) in the file. 036 * </p> 037 * <p> 038 * An example of how to configure the check to make sure a copyright statement 039 * is included in the file (but without requirements on where in the file 040 * it should be): 041 * </p> 042 * <pre> 043 * <module name="RegexpCheck"> 044 * <property name="format" value="This code is copyrighted"/> 045 * </module> 046 * </pre> 047 * <p> 048 * And to make sure the same statement appears at the beginning of the file. 049 * </p> 050 * <pre> 051 * <module name="RegexpCheck"> 052 * <property name="format" value="\AThis code is copyrighted"/> 053 * </module> 054 * </pre> 055 * @author Stan Quinn 056 */ 057@FileStatefulCheck 058public class RegexpCheck extends AbstractCheck { 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 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 getRequiredTokens(); 178 } 179 180 @Override 181 public int[] getAcceptableTokens() { 182 return getRequiredTokens(); 183 } 184 185 @Override 186 public int[] getRequiredTokens() { 187 return CommonUtils.EMPTY_INT_ARRAY; 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 final boolean foundMatch = matcher.find(); 201 if (foundMatch) { 202 final FileText text = getFileContents().getText(); 203 final LineColumn start = text.lineColumn(matcher.start()); 204 final int startLine = start.getLine(); 205 206 final boolean ignore = isIgnore(startLine, text, start); 207 208 if (!ignore) { 209 matchCount++; 210 if (illegalPattern || checkForDuplicates 211 && matchCount - 1 > duplicateLimit) { 212 errorCount++; 213 logMessage(startLine); 214 } 215 } 216 if (canContinueValidation(ignore)) { 217 findMatch(); 218 } 219 } 220 else if (!illegalPattern && matchCount == 0) { 221 logMessage(0); 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 - 1 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 == null || 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 293}