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