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.CommonUtil; 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 */ 056@FileStatefulCheck 057public class RegexpCheck extends AbstractCheck { 058 059 /** 060 * A key is pointing to the warning message text in "messages.properties" 061 * file. 062 */ 063 public static final String MSG_ILLEGAL_REGEXP = "illegal.regexp"; 064 065 /** 066 * A key is pointing to the warning message text in "messages.properties" 067 * file. 068 */ 069 public static final String MSG_REQUIRED_REGEXP = "required.regexp"; 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MSG_DUPLICATE_REGEXP = "duplicate.regexp"; 076 077 /** Default duplicate limit. */ 078 private static final int DEFAULT_DUPLICATE_LIMIT = -1; 079 080 /** Default error report limit. */ 081 private static final int DEFAULT_ERROR_LIMIT = 100; 082 083 /** Error count exceeded message. */ 084 private static final String ERROR_LIMIT_EXCEEDED_MESSAGE = 085 "The error limit has been exceeded, " 086 + "the check is aborting, there may be more unreported errors."; 087 088 /** Custom message for report. */ 089 private String message; 090 091 /** Ignore matches within comments?. **/ 092 private boolean ignoreComments; 093 094 /** Pattern illegal?. */ 095 private boolean illegalPattern; 096 097 /** Error report limit. */ 098 private int errorLimit = DEFAULT_ERROR_LIMIT; 099 100 /** Disallow more than x duplicates?. */ 101 private int duplicateLimit; 102 103 /** Boolean to say if we should check for duplicates. */ 104 private boolean checkForDuplicates; 105 106 /** Tracks number of matches made. */ 107 private int matchCount; 108 109 /** Tracks number of errors. */ 110 private int errorCount; 111 112 /** The regexp to match against. */ 113 private Pattern format = Pattern.compile("^$", Pattern.MULTILINE); 114 115 /** The matcher. */ 116 private Matcher matcher; 117 118 /** 119 * Setter for message property. 120 * @param message custom message which should be used in report. 121 */ 122 public void setMessage(String message) { 123 if (message == null) { 124 this.message = ""; 125 } 126 else { 127 this.message = message; 128 } 129 } 130 131 /** 132 * Sets if matches within comments should be ignored. 133 * @param ignoreComments True if comments should be ignored. 134 */ 135 public void setIgnoreComments(boolean ignoreComments) { 136 this.ignoreComments = ignoreComments; 137 } 138 139 /** 140 * Sets if pattern is illegal, otherwise pattern is required. 141 * @param illegalPattern True if pattern is not allowed. 142 */ 143 public void setIllegalPattern(boolean illegalPattern) { 144 this.illegalPattern = illegalPattern; 145 } 146 147 /** 148 * Sets the limit on the number of errors to report. 149 * @param errorLimit the number of errors to report. 150 */ 151 public void setErrorLimit(int errorLimit) { 152 this.errorLimit = errorLimit; 153 } 154 155 /** 156 * Sets the maximum number of instances of required pattern allowed. 157 * @param duplicateLimit negative values mean no duplicate checking, 158 * any positive value is used as the limit. 159 */ 160 public void setDuplicateLimit(int duplicateLimit) { 161 this.duplicateLimit = duplicateLimit; 162 checkForDuplicates = duplicateLimit > DEFAULT_DUPLICATE_LIMIT; 163 } 164 165 /** 166 * Set the format to the specified regular expression. 167 * @param pattern the new pattern 168 * @throws org.apache.commons.beanutils.ConversionException unable to parse format 169 */ 170 public final void setFormat(Pattern pattern) { 171 format = CommonUtil.createPattern(pattern.pattern(), Pattern.MULTILINE); 172 } 173 174 @Override 175 public int[] getDefaultTokens() { 176 return getRequiredTokens(); 177 } 178 179 @Override 180 public int[] getAcceptableTokens() { 181 return getRequiredTokens(); 182 } 183 184 @Override 185 public int[] getRequiredTokens() { 186 return CommonUtil.EMPTY_INT_ARRAY; 187 } 188 189 @Override 190 public void beginTree(DetailAST rootAST) { 191 matcher = format.matcher(getFileContents().getText().getFullText()); 192 matchCount = 0; 193 errorCount = 0; 194 findMatch(); 195 } 196 197 /** Recursive method that finds the matches. */ 198 private void findMatch() { 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 * Check if we can stop validation. 226 * @param ignore flag 227 * @return true is we can continue 228 */ 229 private boolean canContinueValidation(boolean ignore) { 230 return errorCount <= errorLimit - 1 231 && (ignore || illegalPattern || checkForDuplicates); 232 } 233 234 /** 235 * Detect ignore situation. 236 * @param startLine position of line 237 * @param text file text 238 * @param start line column 239 * @return true is that need to be ignored 240 */ 241 private boolean isIgnore(int startLine, FileText text, LineColumn start) { 242 final LineColumn end; 243 if (matcher.end() == 0) { 244 end = text.lineColumn(0); 245 } 246 else { 247 end = text.lineColumn(matcher.end() - 1); 248 } 249 boolean ignore = false; 250 if (ignoreComments) { 251 final FileContents theFileContents = getFileContents(); 252 final int startColumn = start.getColumn(); 253 final int endLine = end.getLine(); 254 final int endColumn = end.getColumn(); 255 ignore = theFileContents.hasIntersectionWithComment(startLine, 256 startColumn, endLine, endColumn); 257 } 258 return ignore; 259 } 260 261 /** 262 * Displays the right message. 263 * @param lineNumber the line number the message relates to. 264 */ 265 private void logMessage(int lineNumber) { 266 String msg; 267 268 if (message == null || message.isEmpty()) { 269 msg = format.pattern(); 270 } 271 else { 272 msg = message; 273 } 274 275 if (errorCount >= errorLimit) { 276 msg = ERROR_LIMIT_EXCEEDED_MESSAGE + msg; 277 } 278 279 if (illegalPattern) { 280 log(lineNumber, MSG_ILLEGAL_REGEXP, msg); 281 } 282 else { 283 if (lineNumber > 0) { 284 log(lineNumber, MSG_DUPLICATE_REGEXP, msg); 285 } 286 else { 287 log(lineNumber, MSG_REQUIRED_REGEXP, msg); 288 } 289 } 290 } 291 292}