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 * &lt;module name="RequiredRegexp"&gt;
040 *    &lt;property name="format" value="This code is copyrighted"/&gt;
041 * &lt;/module&gt;
042 * </pre>
043 * <p>
044 * And to make sure the same statement appears at the beginning of the file.
045 * </p>
046 * <pre>
047 * &lt;module name="RequiredRegexp"&gt;
048 *    &lt;property name="format" value="\AThis code is copyrighted"/&gt;
049 * &lt;/module&gt;
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