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