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.regexp;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
026
027/**
028 * <p>
029 * Checks that a specified pattern matches a single line in Java files.
030 * </p>
031 * <p>
032 * This class is variation on
033 * <a href="https://checkstyle.org/config_regexp.html#RegexpSingleline">RegexpSingleline</a>
034 * for detecting single lines that match a supplied regular expression in Java files.
035 * It supports suppressing matches in Java comments.
036 * </p>
037 * <ul>
038 * <li>
039 * Property {@code format} - Specify the format of the regular expression to match.
040 * Type is {@code java.lang.String}.
041 * Default value is {@code "$."}.
042 * </li>
043 * <li>
044 * Property {@code message} - Specify the message which is used to notify about
045 * violations, if empty then default (hard-coded) message is used.
046 * Type is {@code java.lang.String}.
047 * Default value is {@code null}.
048 * </li>
049 * <li>
050 * Property {@code ignoreCase} - Control whether to ignore case when searching.
051 * Type is {@code boolean}.
052 * Default value is {@code false}.
053 * </li>
054 * <li>
055 * Property {@code minimum} - Specify the minimum number of matches required in each file.
056 * Type is {@code int}.
057 * Default value is {@code 0}.
058 * </li>
059 * <li>
060 * Property {@code maximum} - Specify the maximum number of matches required in each file.
061 * Type is {@code int}.
062 * Default value is {@code 0}.
063 * </li>
064 * <li>
065 * Property {@code ignoreComments} - Control whether to ignore text in comments when searching.
066 * Type is {@code boolean}.
067 * Default value is {@code false}.
068 * </li>
069 * </ul>
070 * <p>
071 *   To configure the check with default values:
072 * </p>
073 * <pre>
074 * &lt;module name=&quot;RegexpSinglelineJava&quot;/&gt;
075 * </pre>
076 * <p>
077 *   This configuration does not match to anything,
078 *   so we do not provide any code example for it
079 *   as no violation will ever be reported.
080 * </p>
081 * <p>
082 * To configure the check for calls to {@code System.out.println}, except in comments:
083 * </p>
084 * <pre>
085 * &lt;module name="RegexpSinglelineJava"&gt;
086 *   &lt;!-- . matches any character, so we need to
087 *        escape it and use \. to match dots. --&gt;
088 *   &lt;property name="format" value="System\.out\.println"/&gt;
089 *   &lt;property name="ignoreComments" value="true"/&gt;
090 * &lt;/module&gt;
091 * </pre>
092 * <p>Example:</p>
093 * <pre>
094 * System.out.println(""); // violation, instruction matches illegal pattern
095 * System.out.
096 *   println(""); // OK
097 * &#47;* System.out.println *&#47; // OK, comments are ignored
098 * </pre>
099 * <p>
100 * To configure the check to find case-insensitive occurrences of "debug":
101 * </p>
102 * <pre>
103 * &lt;module name="RegexpSinglelineJava"&gt;
104 *   &lt;property name="format" value="debug"/&gt;
105 *   &lt;property name="ignoreCase" value="true"/&gt;
106 * &lt;/module&gt;
107 * </pre>
108 * <p>Example:</p>
109 * <pre>
110 * int debug = 0; // violation, variable name matches illegal pattern
111 * public class Debug { // violation, class name matches illegal pattern
112 * &#47;* this is for de
113 *   bug only; *&#47; // OK
114 * </pre>
115 * <p>
116 * To configure the check to find occurrences of
117 * &quot;\.read(.*)|\.write(.*)&quot;
118 * and display &quot;IO found&quot; for each violation.
119 * </p>
120 * <pre>
121 * &lt;module name=&quot;RegexpSinglelineJava&quot;&gt;
122 *   &lt;property name=&quot;format&quot; value=&quot;\.read(.*)|\.write(.*)&quot;/&gt;
123 *   &lt;property name=&quot;message&quot; value=&quot;IO found&quot;/&gt;
124 * &lt;/module&gt;
125 * </pre>
126 * <p>Example:</p>
127 * <pre>
128 * FileReader in = new FileReader("path/to/input");
129 * int ch = in.read(); // violation
130 * while(ch != -1) {
131 *   System.out.print((char)ch);
132 *   ch = in.read(); // violation
133 * }
134 *
135 * FileWriter out = new FileWriter("path/to/output");
136 * out.write("something"); // violation
137 * </pre>
138 * <p>
139 * To configure the check to find occurrences of
140 * &quot;\.log(.*)&quot;. We want to allow a maximum of 2 occurrences.
141 * </p>
142 * <pre>
143 * &lt;module name=&quot;RegexpSinglelineJava&quot;&gt;
144 *   &lt;property name=&quot;format&quot; value=&quot;\.log(.*)&quot;/&gt;
145 *   &lt;property name=&quot;maximum&quot; value=&quot;2&quot;/&gt;
146 * &lt;/module&gt;
147 * </pre>
148 * <p>Example:</p>
149 * <pre>
150 * public class Foo{
151 *   public void bar(){
152 *     Logger.log("first"); // OK, first occurrence is allowed
153 *     Logger.log("second"); // OK, second occurrence is allowed
154 *     Logger.log("third"); // violation
155 *     System.out.println("fourth");
156 *     Logger.log("fifth"); // violation
157 *   }
158 * }
159 * </pre>
160 * <p>
161 * To configure the check to find all occurrences of
162 * &quot;public&quot;. We want to ignore comments,
163 * display &quot;public member found&quot; for each violation
164 * and say if less than 2 occurrences.
165 * </p>
166 * <pre>
167 * &lt;module name=&quot;RegexpSinglelineJava&quot;&gt;
168 *   &lt;property name=&quot;format&quot; value=&quot;public&quot;/&gt;
169 *   &lt;property name=&quot;minimum&quot; value=&quot;2&quot;/&gt;
170 *   &lt;property name=&quot;message&quot; value=&quot;public member found&quot;/&gt;
171 *   &lt;property name=&quot;ignoreComments&quot; value=&quot;true&quot;/&gt;
172 * &lt;/module&gt;
173 * </pre>
174 * <p>Example:</p>
175 * <pre>
176 * class Foo{ // violation, file contains less than 2 occurrences of "public"
177 *   private int a;
178 *   &#47;* public comment *&#47; // OK, comment is ignored
179 *   private void bar1() {}
180 *   public void bar2() {} // violation
181 * }
182 * </pre>
183 * <p>Example:</p>
184 * <pre>
185 * class Foo{
186 *   private int a;
187 *  &#47;* public comment *&#47; // OK, comment is ignored
188 *   public void bar1() {} // violation
189 *   public void bar2() {} // violation
190 * }
191 * </pre>
192 * <p>
193 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
194 * </p>
195 * <p>
196 * Violation Message Keys:
197 * </p>
198 * <ul>
199 * <li>
200 * {@code regexp.exceeded}
201 * </li>
202 * <li>
203 * {@code regexp.minimum}
204 * </li>
205 * </ul>
206 *
207 * @since 6.0
208 */
209@StatelessCheck
210public class RegexpSinglelineJavaCheck extends AbstractCheck {
211
212    /** Specify the format of the regular expression to match. */
213    private String format = "$.";
214    /**
215     * Specify the message which is used to notify about violations,
216     * if empty then default (hard-coded) message is used.
217     */
218    private String message;
219    /** Specify the minimum number of matches required in each file. */
220    private int minimum;
221    /** Specify the maximum number of matches required in each file. */
222    private int maximum;
223    /** Control whether to ignore case when searching. */
224    private boolean ignoreCase;
225    /** Control whether to ignore text in comments when searching. */
226    private boolean ignoreComments;
227
228    @Override
229    public int[] getDefaultTokens() {
230        return getRequiredTokens();
231    }
232
233    @Override
234    public int[] getAcceptableTokens() {
235        return getRequiredTokens();
236    }
237
238    @Override
239    public int[] getRequiredTokens() {
240        return CommonUtil.EMPTY_INT_ARRAY;
241    }
242
243    @Override
244    public void beginTree(DetailAST rootAST) {
245        MatchSuppressor suppressor = null;
246        if (ignoreComments) {
247            suppressor = new CommentSuppressor(getFileContents());
248        }
249
250        final DetectorOptions options = DetectorOptions.newBuilder()
251            .reporter(this)
252            .compileFlags(0)
253            .suppressor(suppressor)
254            .format(format)
255            .message(message)
256            .minimum(minimum)
257            .maximum(maximum)
258            .ignoreCase(ignoreCase)
259            .build();
260        final SinglelineDetector detector = new SinglelineDetector(options);
261        detector.processLines(getFileContents().getText());
262    }
263
264    /**
265     * Setter to specify the format of the regular expression to match.
266     *
267     * @param format the format of the regular expression to match.
268     */
269    public void setFormat(String format) {
270        this.format = format;
271    }
272
273    /**
274     * Setter to specify the message which is used to notify about violations,
275     * if empty then default (hard-coded) message is used.
276     *
277     * @param message the message to report for a match.
278     */
279    public void setMessage(String message) {
280        this.message = message;
281    }
282
283    /**
284     * Setter to specify the minimum number of matches required in each file.
285     *
286     * @param minimum the minimum number of matches required in each file.
287     */
288    public void setMinimum(int minimum) {
289        this.minimum = minimum;
290    }
291
292    /**
293     * Setter to specify the maximum number of matches required in each file.
294     *
295     * @param maximum the maximum number of matches required in each file.
296     */
297    public void setMaximum(int maximum) {
298        this.maximum = maximum;
299    }
300
301    /**
302     * Setter to control whether to ignore case when searching.
303     *
304     * @param ignoreCase whether to ignore case when searching.
305     */
306    public void setIgnoreCase(boolean ignoreCase) {
307        this.ignoreCase = ignoreCase;
308    }
309
310    /**
311     * Setter to control whether to ignore text in comments when searching.
312     *
313     * @param ignore whether to ignore text in comments when searching.
314     */
315    public void setIgnoreComments(boolean ignore) {
316        ignoreComments = ignore;
317    }
318
319}