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