View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.api;
21  
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.Set;
25  import java.util.SortedSet;
26  import java.util.TreeSet;
27  
28  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
29  
30  /**
31   * The base class for checks.
32   *
33   * @see <a href="{@docRoot}/../writing-checks.html" target="_top">Writing your own checks</a>
34   * @noinspection NoopMethodInAbstractClass
35   * @noinspectionreason NoopMethodInAbstractClass - we allow each check to
36   *      define these methods, as needed. They should be overridden only by demand in subclasses
37   */
38  public abstract class AbstractCheck extends AbstractViolationReporter {
39  
40      /**
41       * The check context.
42       *
43       * @noinspection ThreadLocalNotStaticFinal
44       * @noinspectionreason ThreadLocalNotStaticFinal - static context
45       *      is problematic for multithreading
46       */
47      private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
48  
49      /** The tokens the check is interested in. */
50      private final Set<String> tokens = new HashSet<>();
51  
52      /**
53       * The tab width for column reporting. Default is uninitialized as the value is inherited from
54       * the parent module.
55       */
56      private int tabWidth;
57  
58      /**
59       * Creates a new {@code AbstractCheck} instance.
60       */
61      protected AbstractCheck() {
62          // no code by default
63      }
64  
65      /**
66       * Returns the default token a check is interested in. Only used if the
67       * configuration for a check does not define the tokens.
68       *
69       * @return the default tokens
70       * @see TokenTypes
71       */
72      public abstract int[] getDefaultTokens();
73  
74      /**
75       * The configurable token set.
76       * Used to protect Checks against malicious users who specify an
77       * unacceptable token set in the configuration file.
78       * The default implementation returns the check's default tokens.
79       *
80       * @return the token set this check is designed for.
81       * @see TokenTypes
82       */
83      public abstract int[] getAcceptableTokens();
84  
85      /**
86       * The tokens that this check must be registered for.
87       *
88       * @return the token set this must be registered for.
89       * @see TokenTypes
90       */
91      public abstract int[] getRequiredTokens();
92  
93      /**
94       * Whether comment nodes are required or not.
95       *
96       * @return false as a default value.
97       */
98      public boolean isCommentNodesRequired() {
99          return false;
100     }
101 
102     /**
103      * Adds a set of tokens the check is interested in.
104      *
105      * @param strRep the string representation of the tokens interested in
106      * @noinspection WeakerAccess
107      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
108      */
109     public final void setTokens(String... strRep) {
110         Collections.addAll(tokens, strRep);
111     }
112 
113     /**
114      * Returns the tokens registered for the check.
115      *
116      * @return the set of token names
117      */
118     public final Set<String> getTokenNames() {
119         return Collections.unmodifiableSet(tokens);
120     }
121 
122     /**
123      * Returns the sorted set of {@link Violation}.
124      *
125      * @return the sorted set of {@code Violation}.
126      */
127     public SortedSet<Violation> getViolations() {
128         return new TreeSet<>(context.get().violations);
129     }
130 
131     /**
132      * Clears the sorted set of {@link Violation} of the check.
133      */
134     public final void clearViolations() {
135         context.get().violations.clear();
136     }
137 
138     /**
139      * Initialize the check. This is the time to verify that the check has
140      * everything required to perform its job.
141      */
142     public void init() {
143         // No code by default, should be overridden only by demand at subclasses
144     }
145 
146     /**
147      * Destroy the check. It is being retired from service.
148      */
149     public void destroy() {
150         context.remove();
151     }
152 
153     /**
154      * Called before the starting to process a tree. Ideal place to initialize
155      * information that is to be collected whilst processing a tree.
156      *
157      * @param rootAST the root of the tree
158      */
159     public void beginTree(DetailAST rootAST) {
160         // No code by default, should be overridden only by demand at subclasses
161     }
162 
163     /**
164      * Called after finished processing a tree. Ideal place to report on
165      * information collected whilst processing a tree.
166      *
167      * @param rootAST the root of the tree
168      */
169     public void finishTree(DetailAST rootAST) {
170         // No code by default, should be overridden only by demand at subclasses
171     }
172 
173     /**
174      * Called to process a token.
175      *
176      * @param ast the token to process
177      */
178     public void visitToken(DetailAST ast) {
179         // No code by default, should be overridden only by demand at subclasses
180     }
181 
182     /**
183      * Called after all the child nodes have been process.
184      *
185      * @param ast the token leaving
186      */
187     public void leaveToken(DetailAST ast) {
188         // No code by default, should be overridden only by demand at subclasses
189     }
190 
191     /**
192      * Set the file contents associated with the tree.
193      *
194      * @param contents the manager
195      */
196     public final void setFileContents(FileContents contents) {
197         context.get().fileContents = contents;
198     }
199 
200     /**
201      * Returns the file contents associated with the tree.
202      *
203      * @return the file contents
204      * @deprecated
205      *      Usage of this method is no longer accepted.
206      *      Please use AST based methods instead.
207      * @noinspection WeakerAccess
208      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
209      */
210     @Deprecated(since = "9.3")
211     public final FileContents getFileContents() {
212         return context.get().fileContents;
213     }
214 
215     /**
216      * Get tab width to report audit events with.
217      *
218      * @return the tab width to audit events with
219      */
220     protected final int getTabWidth() {
221         return tabWidth;
222     }
223 
224     /**
225      * Set the tab width to report audit events with.
226      *
227      * @param tabWidth an {@code int} value
228      */
229     public final void setTabWidth(int tabWidth) {
230         this.tabWidth = tabWidth;
231     }
232 
233     @Override
234     public final void log(int line, String key, Object... args) {
235         context.get().violations.add(
236             new Violation(
237                 line,
238                 getMessageBundle(),
239                 key,
240                 args,
241                 getSeverityLevel(),
242                 getId(),
243                 getClass(),
244                 getCustomMessages().get(key)));
245     }
246 
247     /**
248      * Helper method to log a Violation.
249      *
250      * @param ast a node to get line id column numbers associated with the violation
251      * @param key key to locale violation format
252      * @param args arguments to format
253      */
254     public final void log(DetailAST ast, String key, Object... args) {
255         // CommonUtil.lengthExpandedTabs returns column number considering tabulation
256         // characters, it takes line from the file by line number, ast column number and tab
257         // width as arguments. Returned value is 0-based, but user must see column number starting
258         // from 1, that is why result of the method CommonUtil.lengthExpandedTabs
259         // is increased by one.
260 
261         final int col = 1 + CommonUtil.lengthExpandedTabs(
262                 getLines()[ast.getLineNo() - 1], ast.getColumnNo(), tabWidth);
263         context.get().violations.add(
264                 new Violation(
265                         ast.getLineNo(),
266                         col,
267                         ast.getColumnNo(),
268                         ast.getType(),
269                         getMessageBundle(),
270                         key,
271                         args,
272                         getSeverityLevel(),
273                         getId(),
274                         getClass(),
275                         getCustomMessages().get(key)));
276     }
277 
278     @Override
279     public final void log(int lineNo, int colNo, String key,
280             Object... args) {
281         final int col = 1 + CommonUtil.lengthExpandedTabs(
282             getLines()[lineNo - 1], colNo, tabWidth);
283         context.get().violations.add(
284             new Violation(
285                 lineNo,
286                 col,
287                 getMessageBundle(),
288                 key,
289                 args,
290                 getSeverityLevel(),
291                 getId(),
292                 getClass(),
293                 getCustomMessages().get(key)));
294     }
295 
296     /**
297      * Returns the lines associated with the tree.
298      *
299      * @return the file contents
300      */
301     public final String[] getLines() {
302         return context.get().fileContents.getLines();
303     }
304 
305     /**
306      * Returns the line associated with the tree.
307      *
308      * @param index index of the line
309      * @return the line from the file contents
310      */
311     public final String getLine(int index) {
312         return context.get().fileContents.getLine(index);
313     }
314 
315     /**
316      * Returns full path to the file.
317      *
318      * @return full path to file.
319      */
320     public final String getFilePath() {
321         return context.get().fileContents.getFileName();
322     }
323 
324     /**
325      * Returns code point representation of file text from given line number.
326      *
327      * @param index index of the line
328      * @return the array of Unicode code points
329      */
330     public final int[] getLineCodePoints(int index) {
331         return getLine(index).codePoints().toArray();
332     }
333 
334     /**
335      * The actual context holder.
336      */
337     private static final class FileContext {
338 
339         /** The sorted set for collecting violations. */
340         private final SortedSet<Violation> violations = new TreeSet<>();
341 
342         /** The current file contents. */
343         private FileContents fileContents;
344 
345         /**
346          * Creates a new {@code FileContext} instance.
347          */
348         private FileContext() {
349             // no code by default
350         }
351 
352     }
353 
354 }