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