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