Coverage Report - com.puppycrawl.tools.checkstyle.checks.regexp.RegexpCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
RegexpCheck
100%
67/67
100%
40/40
2.286
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.regexp;
 21  
 
 22  
 import java.util.regex.Matcher;
 23  
 import java.util.regex.Pattern;
 24  
 
 25  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 26  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 28  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 29  
 import com.puppycrawl.tools.checkstyle.api.FileText;
 30  
 import com.puppycrawl.tools.checkstyle.api.LineColumn;
 31  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 32  
 
 33  
 /**
 34  
  * <p>
 35  
  * A check that makes sure that a specified pattern exists (or not) in the file.
 36  
  * </p>
 37  
  * <p>
 38  
  * An example of how to configure the check to make sure a copyright statement
 39  
  * is included in the file (but without requirements on where in the file
 40  
  * it should be):
 41  
  * </p>
 42  
  * <pre>
 43  
  * &lt;module name="RegexpCheck"&gt;
 44  
  *    &lt;property name="format" value="This code is copyrighted"/&gt;
 45  
  * &lt;/module&gt;
 46  
  * </pre>
 47  
  * <p>
 48  
  * And to make sure the same statement appears at the beginning of the file.
 49  
  * </p>
 50  
  * <pre>
 51  
  * &lt;module name="RegexpCheck"&gt;
 52  
  *    &lt;property name="format" value="\AThis code is copyrighted"/&gt;
 53  
  * &lt;/module&gt;
 54  
  * </pre>
 55  
  * @author Stan Quinn
 56  
  */
 57  
 @FileStatefulCheck
 58  44
 public class RegexpCheck extends AbstractCheck {
 59  
 
 60  
     /**
 61  
      * A key is pointing to the warning message text in "messages.properties"
 62  
      * file.
 63  
      */
 64  
     public static final String MSG_ILLEGAL_REGEXP = "illegal.regexp";
 65  
 
 66  
     /**
 67  
      * A key is pointing to the warning message text in "messages.properties"
 68  
      * file.
 69  
      */
 70  
     public static final String MSG_REQUIRED_REGEXP = "required.regexp";
 71  
 
 72  
     /**
 73  
      * A key is pointing to the warning message text in "messages.properties"
 74  
      * file.
 75  
      */
 76  
     public static final String MSG_DUPLICATE_REGEXP = "duplicate.regexp";
 77  
 
 78  
     /** Default duplicate limit. */
 79  
     private static final int DEFAULT_DUPLICATE_LIMIT = -1;
 80  
 
 81  
     /** Default error report limit. */
 82  
     private static final int DEFAULT_ERROR_LIMIT = 100;
 83  
 
 84  
     /** Error count exceeded message. */
 85  
     private static final String ERROR_LIMIT_EXCEEDED_MESSAGE =
 86  
         "The error limit has been exceeded, "
 87  
         + "the check is aborting, there may be more unreported errors.";
 88  
 
 89  
     /** Custom message for report. */
 90  44
     private String message = "";
 91  
 
 92  
     /** Ignore matches within comments?. **/
 93  
     private boolean ignoreComments;
 94  
 
 95  
     /** Pattern illegal?. */
 96  
     private boolean illegalPattern;
 97  
 
 98  
     /** Error report limit. */
 99  44
     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  44
     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  6
         if (message == null) {
 125  1
             this.message = "";
 126  
         }
 127  
         else {
 128  5
             this.message = message;
 129  
         }
 130  6
     }
 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  12
         this.ignoreComments = ignoreComments;
 138  12
     }
 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  23
         this.illegalPattern = illegalPattern;
 146  23
     }
 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  3
         this.errorLimit = errorLimit;
 154  3
     }
 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  5
         this.duplicateLimit = duplicateLimit;
 163  5
         checkForDuplicates = duplicateLimit > DEFAULT_DUPLICATE_LIMIT;
 164  5
     }
 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  36
         format = CommonUtils.createPattern(pattern.pattern(), Pattern.MULTILINE);
 173  36
     }
 174  
 
 175  
     @Override
 176  
     public int[] getDefaultTokens() {
 177  42
         return getAcceptableTokens();
 178  
     }
 179  
 
 180  
     @Override
 181  
     public int[] getAcceptableTokens() {
 182  89
         return CommonUtils.EMPTY_INT_ARRAY;
 183  
     }
 184  
 
 185  
     @Override
 186  
     public int[] getRequiredTokens() {
 187  43
         return getAcceptableTokens();
 188  
     }
 189  
 
 190  
     @Override
 191  
     public void beginTree(DetailAST rootAST) {
 192  26
         matcher = format.matcher(getFileContents().getText().getFullText());
 193  26
         matchCount = 0;
 194  26
         errorCount = 0;
 195  26
         findMatch();
 196  26
     }
 197  
 
 198  
     /** Recursive method that finds the matches. */
 199  
     private void findMatch() {
 200  
 
 201  48
         final boolean foundMatch = matcher.find();
 202  48
         if (foundMatch) {
 203  27
             final FileText text = getFileContents().getText();
 204  27
             final LineColumn start = text.lineColumn(matcher.start());
 205  27
             final int startLine = start.getLine();
 206  
 
 207  27
             final boolean ignore = isIgnore(startLine, text, start);
 208  
 
 209  27
             if (!ignore) {
 210  20
                 matchCount++;
 211  20
                 if (illegalPattern || checkForDuplicates
 212  
                         && matchCount - 1 > duplicateLimit) {
 213  14
                     errorCount++;
 214  14
                     logMessage(startLine);
 215  
                 }
 216  
             }
 217  27
             if (canContinueValidation(ignore)) {
 218  22
                 findMatch();
 219  
             }
 220  27
         }
 221  21
         else if (!illegalPattern && matchCount == 0) {
 222  2
             logMessage(0);
 223  
         }
 224  
 
 225  48
     }
 226  
 
 227  
     /**
 228  
      * Check if we can stop validation.
 229  
      * @param ignore flag
 230  
      * @return true is we can continue
 231  
      */
 232  
     private boolean canContinueValidation(boolean ignore) {
 233  27
         return errorCount <= errorLimit - 1
 234  
                 && (ignore || illegalPattern || checkForDuplicates);
 235  
     }
 236  
 
 237  
     /**
 238  
      * Detect ignore situation.
 239  
      * @param startLine position of line
 240  
      * @param text file text
 241  
      * @param start line column
 242  
      * @return true is that need to be ignored
 243  
      */
 244  
     private boolean isIgnore(int startLine, FileText text, LineColumn start) {
 245  
         final LineColumn end;
 246  27
         if (matcher.end() == 0) {
 247  2
             end = text.lineColumn(0);
 248  
         }
 249  
         else {
 250  25
             end = text.lineColumn(matcher.end() - 1);
 251  
         }
 252  27
         boolean ignore = false;
 253  27
         if (ignoreComments) {
 254  9
             final FileContents theFileContents = getFileContents();
 255  9
             final int startColumn = start.getColumn();
 256  9
             final int endLine = end.getLine();
 257  9
             final int endColumn = end.getColumn();
 258  9
             ignore = theFileContents.hasIntersectionWithComment(startLine,
 259  
                 startColumn, endLine, endColumn);
 260  
         }
 261  27
         return ignore;
 262  
     }
 263  
 
 264  
     /**
 265  
      * Displays the right message.
 266  
      * @param lineNumber the line number the message relates to.
 267  
      */
 268  
     private void logMessage(int lineNumber) {
 269  
         String msg;
 270  
 
 271  16
         if (message.isEmpty()) {
 272  15
             msg = format.pattern();
 273  
         }
 274  
         else {
 275  1
             msg = message;
 276  
         }
 277  
 
 278  16
         if (errorCount >= errorLimit) {
 279  1
             msg = ERROR_LIMIT_EXCEEDED_MESSAGE + msg;
 280  
         }
 281  
 
 282  16
         if (illegalPattern) {
 283  13
             log(lineNumber, MSG_ILLEGAL_REGEXP, msg);
 284  
         }
 285  
         else {
 286  3
             if (lineNumber > 0) {
 287  1
                 log(lineNumber, MSG_DUPLICATE_REGEXP, msg);
 288  
             }
 289  
             else {
 290  2
                 log(lineNumber, MSG_REQUIRED_REGEXP, msg);
 291  
             }
 292  
         }
 293  16
     }
 294  
 }