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