001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
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////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.regex.Pattern;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * <p>
032 * Checks specified tokens text for matching an illegal pattern.
033 * By default no tokens are specified.
034 * </p>
035 * <ul>
036 * <li>
037 * Property {@code format} - Define the RegExp for illegal pattern.
038 * Type is {@code java.lang.String}.
039 * Default value is {@code "^$" (empty)}.
040 * </li>
041 * <li>
042 * Property {@code ignoreCase} - Control whether to ignore case when matching.
043 * Type is {@code boolean}.
044 * Default value is {@code false}.
045 * </li>
046 * <li>
047 * Property {@code message} - Define the message which is used to notify about violations;
048 * if empty then the default message is used.
049 * Type is {@code java.lang.String}.
050 * Default value is {@code ""}.
051 * </li>
052 * <li>
053 * Property {@code tokens} - tokens to check
054 * Type is {@code int[]}.
055 * Default value is: empty.
056 * </li>
057 * </ul>
058 * <p>
059 * To configure the check to forbid String literals containing {@code "a href"}:
060 * </p>
061 * <pre>
062 * &lt;module name=&quot;IllegalTokenText&quot;&gt;
063 *   &lt;property name=&quot;tokens&quot; value=&quot;STRING_LITERAL&quot;/&gt;
064 *   &lt;property name=&quot;format&quot; value=&quot;a href&quot;/&gt;
065 * &lt;/module&gt;
066 * </pre>
067 * <p>Example:</p>
068 * <pre>
069 * public void myTest() {
070 *     String test = "a href"; // violation
071 *     String test2 = "A href"; // OK, case is sensitive
072 * }
073 * </pre>
074 * <p>
075 * To configure the check to forbid String literals containing {@code "a href"}
076 * for the ignoreCase mode:
077 * </p>
078 * <pre>
079 * &lt;module name=&quot;IllegalTokenText&quot;&gt;
080 *   &lt;property name=&quot;tokens&quot; value=&quot;STRING_LITERAL&quot;/&gt;
081 *   &lt;property name=&quot;format&quot; value=&quot;a href&quot;/&gt;
082 *   &lt;property name=&quot;ignoreCase&quot; value=&quot;true&quot;/&gt;
083 * &lt;/module&gt;
084 * </pre>
085 * <p>Example:</p>
086 * <pre>
087 * public void myTest() {
088 *     String test = "a href"; // violation
089 *     String test2 = "A href"; // violation, case is ignored
090 * }
091 * </pre>
092 * <p>
093 * To configure the check to forbid leading zeros in an integer literal,
094 * other than zero and a hex literal:
095 * </p>
096 * <pre>
097 * &lt;module name=&quot;IllegalTokenText&quot;&gt;
098 *   &lt;property name=&quot;tokens&quot; value=&quot;NUM_INT,NUM_LONG&quot;/&gt;
099 *   &lt;property name=&quot;format&quot; value=&quot;^0[^lx]&quot;/&gt;
100 *   &lt;property name=&quot;ignoreCase&quot; value=&quot;true&quot;/&gt;
101 * &lt;/module&gt;
102 * </pre>
103 * <p>Example:</p>
104 * <pre>
105 * public void myTest() {
106 *     int test1 = 0; // OK
107 *     int test2 = 0x111; // OK
108 *     int test3 = 0X111; // OK, case is ignored
109 *     int test4 = 010; // violation
110 *     long test5 = 0L; // OK
111 *     long test6 = 010L; // violation
112 * }
113 * </pre>
114 * <p>
115 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
116 * </p>
117 * <p>
118 * Violation Message Keys:
119 * </p>
120 * <ul>
121 * <li>
122 * {@code illegal.token.text}
123 * </li>
124 * </ul>
125 *
126 * @since 3.2
127 */
128@StatelessCheck
129public class IllegalTokenTextCheck
130    extends AbstractCheck {
131
132    /**
133     * A key is pointing to the warning message text in "messages.properties"
134     * file.
135     */
136    public static final String MSG_KEY = "illegal.token.text";
137
138    /**
139     * Define the message which is used to notify about violations;
140     * if empty then the default message is used.
141     */
142    private String message = "";
143
144    /** The format string of the regexp. */
145    private String formatString = "^$";
146
147    /** Define the RegExp for illegal pattern. */
148    private Pattern format = Pattern.compile(formatString);
149
150    /** Control whether to ignore case when matching. */
151    private boolean ignoreCase;
152
153    @Override
154    public int[] getDefaultTokens() {
155        return CommonUtil.EMPTY_INT_ARRAY;
156    }
157
158    @Override
159    public int[] getAcceptableTokens() {
160        return new int[] {
161            TokenTypes.NUM_DOUBLE,
162            TokenTypes.NUM_FLOAT,
163            TokenTypes.NUM_INT,
164            TokenTypes.NUM_LONG,
165            TokenTypes.IDENT,
166            TokenTypes.COMMENT_CONTENT,
167            TokenTypes.STRING_LITERAL,
168            TokenTypes.CHAR_LITERAL,
169        };
170    }
171
172    @Override
173    public int[] getRequiredTokens() {
174        return CommonUtil.EMPTY_INT_ARRAY;
175    }
176
177    @Override
178    public boolean isCommentNodesRequired() {
179        return true;
180    }
181
182    @Override
183    public void visitToken(DetailAST ast) {
184        final String text = ast.getText();
185        if (format.matcher(text).find()) {
186            String customMessage = message;
187            if (customMessage.isEmpty()) {
188                customMessage = MSG_KEY;
189            }
190            log(
191                ast,
192                customMessage,
193                formatString);
194        }
195    }
196
197    /**
198     * Setter to define the message which is used to notify about violations;
199     * if empty then the default message is used.
200     *
201     * @param message custom message which should be used
202     *                 to report about violations.
203     */
204    public void setMessage(String message) {
205        if (message == null) {
206            this.message = "";
207        }
208        else {
209            this.message = message;
210        }
211    }
212
213    /**
214     * Setter to define the RegExp for illegal pattern.
215     *
216     * @param format a {@code String} value
217     */
218    public void setFormat(String format) {
219        formatString = format;
220        updateRegexp();
221    }
222
223    /**
224     * Setter to control whether to ignore case when matching.
225     *
226     * @param caseInsensitive true if the match is case insensitive.
227     */
228    public void setIgnoreCase(boolean caseInsensitive) {
229        ignoreCase = caseInsensitive;
230        updateRegexp();
231    }
232
233    /**
234     * Updates the {@link #format} based on the values from {@link #formatString} and
235     * {@link #ignoreCase}.
236     */
237    private void updateRegexp() {
238        final int compileFlags;
239        if (ignoreCase) {
240            compileFlags = Pattern.CASE_INSENSITIVE;
241        }
242        else {
243            compileFlags = 0;
244        }
245        format = CommonUtil.createPattern(formatString, compileFlags);
246    }
247
248}