001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks; 020 021import com.puppycrawl.tools.checkstyle.api.DetailAST; 022import com.puppycrawl.tools.checkstyle.api.FileContents; 023import com.puppycrawl.tools.checkstyle.api.FileText; 024import com.puppycrawl.tools.checkstyle.api.LineColumn; 025 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029/** 030 * <p> 031 * A check that makes sure that a specified pattern exists (or not) in the file. 032 * </p> 033 * <p> 034 * An example of how to configure the check to make sure a copyright statement 035 * is included in the file (but without requirements on where in the file 036 * it should be): 037 * </p> 038 * <pre> 039 * <module name="RequiredRegexp"> 040 * <property name="format" value="This code is copyrighted"/> 041 * </module> 042 * </pre> 043 * <p> 044 * And to make sure the same statement appears at the beginning of the file. 045 * </p> 046 * <pre> 047 * <module name="RequiredRegexp"> 048 * <property name="format" value="\AThis code is copyrighted"/> 049 * </module> 050 * </pre> 051 * @author Stan Quinn 052 */ 053public class RegexpCheck extends AbstractFormatCheck 054{ 055 /** Default duplicate limit */ 056 private static final int DEFAULT_DUPLICATE_LIMIT = -1; 057 058 /** Default error report limit */ 059 private static final int DEFAULT_ERROR_LIMIT = 100; 060 061 /** Error count exceeded message */ 062 private static final String ERROR_LIMIT_EXCEEDED_MESSAGE = 063 "The error limit has been exceeded, " 064 + "the check is aborting, there may be more unreported errors."; 065 066 /** Custom message for report. */ 067 private String message = ""; 068 069 /** Ignore matches within comments? **/ 070 private boolean ignoreComments; 071 072 /** Pattern illegal? */ 073 private boolean illegalPattern; 074 075 /** Error report limit */ 076 private int errorLimit = DEFAULT_ERROR_LIMIT; 077 078 /** Disallow more than x duplicates? */ 079 private int duplicateLimit; 080 081 /** Boolean to say if we should check for duplicates. */ 082 private boolean checkForDuplicates; 083 084 /** Tracks number of matches made */ 085 private int matchCount; 086 087 /** Tracks number of errors */ 088 private int errorCount; 089 090 /** The matcher */ 091 private Matcher matcher; 092 093 /** 094 * Instantiates an new RegexpCheck. 095 */ 096 public RegexpCheck() 097 { 098 super("$^", Pattern.MULTILINE); // the empty language 099 } 100 101 /** 102 * Setter for message property. 103 * @param message custom message which should be used in report. 104 */ 105 public void setMessage(String message) 106 { 107 this.message = (message == null) ? "" : message; 108 } 109 110 /** 111 * Getter for message property. 112 * I'm not sure if this gets used by anything outside, 113 * I just included it because GenericIllegalRegexp had it, 114 * it's being used in logMessage() so it's covered in EMMA. 115 * @return custom message to be used in report. 116 */ 117 public String getMessage() 118 { 119 return message; 120 } 121 122 /** 123 * Sets if matches within comments should be ignored. 124 * @param ignoreComments True if comments should be ignored. 125 */ 126 public void setIgnoreComments(boolean ignoreComments) 127 { 128 this.ignoreComments = ignoreComments; 129 } 130 131 /** 132 * Sets if pattern is illegal, otherwise pattern is required. 133 * @param illegalPattern True if pattern is not allowed. 134 */ 135 public void setIllegalPattern(boolean illegalPattern) 136 { 137 this.illegalPattern = illegalPattern; 138 } 139 140 /** 141 * Sets the limit on the number of errors to report. 142 * @param errorLimit the number of errors to report. 143 */ 144 public void setErrorLimit(int errorLimit) 145 { 146 this.errorLimit = errorLimit; 147 } 148 149 /** 150 * Sets the maximum number of instances of required pattern allowed. 151 * @param duplicateLimit negative values mean no duplicate checking, 152 * any positive value is used as the limit. 153 */ 154 public void setDuplicateLimit(int duplicateLimit) 155 { 156 this.duplicateLimit = duplicateLimit; 157 checkForDuplicates = (duplicateLimit > DEFAULT_DUPLICATE_LIMIT); 158 } 159 160 @Override 161 public int[] getDefaultTokens() 162 { 163 return new int[0]; 164 } 165 166 @Override 167 public void beginTree(DetailAST rootAST) 168 { 169 final Pattern pattern = getRegexp(); 170 matcher = pattern.matcher(getFileContents().getText().getFullText()); 171 matchCount = 0; 172 errorCount = 0; 173 findMatch(); 174 } 175 176 /** recursive method that finds the matches. */ 177 private void findMatch() 178 { 179 int startLine; 180 int startColumn; 181 int endLine; 182 int endColumn; 183 boolean foundMatch; 184 boolean ignore = false; 185 186 foundMatch = matcher.find(); 187 if (!foundMatch && !illegalPattern && (matchCount == 0)) { 188 logMessage(0); 189 } 190 else if (foundMatch) { 191 final FileText text = getFileContents().getText(); 192 final LineColumn start = text.lineColumn(matcher.start()); 193 final LineColumn end = text.lineColumn(matcher.end() - 1); 194 startLine = start.getLine(); 195 startColumn = start.getColumn(); 196 endLine = end.getLine(); 197 endColumn = end.getColumn(); 198 if (ignoreComments) { 199 final FileContents theFileContents = getFileContents(); 200 ignore = theFileContents.hasIntersectionWithComment(startLine, 201 startColumn, endLine, endColumn); 202 } 203 if (!ignore) { 204 matchCount++; 205 if (illegalPattern || (checkForDuplicates 206 && ((matchCount - 1) > duplicateLimit))) 207 { 208 errorCount++; 209 logMessage(startLine); 210 } 211 } 212 if ((errorCount < errorLimit) 213 && (ignore || illegalPattern || checkForDuplicates)) 214 { 215 findMatch(); 216 } 217 } 218 } 219 220 /** 221 * Displays the right message. 222 * @param lineNumber the line number the message relates to. 223 */ 224 private void logMessage(int lineNumber) 225 { 226 String msg = "".equals(getMessage()) ? getFormat() : message; 227 if (errorCount >= errorLimit) { 228 msg = ERROR_LIMIT_EXCEEDED_MESSAGE + msg; 229 } 230 if (illegalPattern) { 231 log(lineNumber, "illegal.regexp", msg); 232 } 233 else { 234 if (lineNumber > 0) { 235 log(lineNumber, "duplicate.regexp", msg); 236 } 237 else { 238 log(lineNumber, "required.regexp", msg); 239 } 240 } 241 } 242} 243