View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2020 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   */
37  public abstract class AbstractCheck extends AbstractViolationReporter {
38  
39      /**
40       * The check context.
41       *
42       * @noinspection ThreadLocalNotStaticFinal
43       */
44      private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
45  
46      /** The tokens the check is interested in. */
47      private final Set<String> tokens = new HashSet<>();
48  
49      /** The tab width for column reporting. */
50      private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH;
51  
52      /**
53       * Returns the default token a check is interested in. Only used if the
54       * configuration for a check does not define the tokens.
55       *
56       * @return the default tokens
57       * @see TokenTypes
58       */
59      public abstract int[] getDefaultTokens();
60  
61      /**
62       * The configurable token set.
63       * Used to protect Checks against malicious users who specify an
64       * unacceptable token set in the configuration file.
65       * The default implementation returns the check's default tokens.
66       *
67       * @return the token set this check is designed for.
68       * @see TokenTypes
69       */
70      public abstract int[] getAcceptableTokens();
71  
72      /**
73       * The tokens that this check must be registered for.
74       *
75       * @return the token set this must be registered for.
76       * @see TokenTypes
77       */
78      public abstract int[] getRequiredTokens();
79  
80      /**
81       * Whether comment nodes are required or not.
82       *
83       * @return false as a default value.
84       */
85      public boolean isCommentNodesRequired() {
86          return false;
87      }
88  
89      /**
90       * Adds a set of tokens the check is interested in.
91       *
92       * @param strRep the string representation of the tokens interested in
93       * @noinspection WeakerAccess
94       */
95      public final void setTokens(String... strRep) {
96          Collections.addAll(tokens, strRep);
97      }
98  
99      /**
100      * Returns the tokens registered for the check.
101      *
102      * @return the set of token names
103      */
104     public final Set<String> getTokenNames() {
105         return Collections.unmodifiableSet(tokens);
106     }
107 
108     /**
109      * Returns the sorted set of {@link LocalizedMessage}.
110      *
111      * @return the sorted set of {@link LocalizedMessage}.
112      */
113     public SortedSet<LocalizedMessage> getMessages() {
114         return new TreeSet<>(context.get().messages);
115     }
116 
117     /**
118      * Clears the sorted set of {@link LocalizedMessage} of the check.
119      */
120     public final void clearMessages() {
121         context.get().messages.clear();
122     }
123 
124     /**
125      * Initialize the check. This is the time to verify that the check has
126      * everything required to perform it job.
127      */
128     public void init() {
129         // No code by default, should be overridden only by demand at subclasses
130     }
131 
132     /**
133      * Destroy the check. It is being retired from service.
134      */
135     public void destroy() {
136         context.remove();
137     }
138 
139     /**
140      * Called before the starting to process a tree. Ideal place to initialize
141      * information that is to be collected whilst processing a tree.
142      *
143      * @param rootAST the root of the tree
144      */
145     public void beginTree(DetailAST rootAST) {
146         // No code by default, should be overridden only by demand at subclasses
147     }
148 
149     /**
150      * Called after finished processing a tree. Ideal place to report on
151      * information collected whilst processing a tree.
152      *
153      * @param rootAST the root of the tree
154      */
155     public void finishTree(DetailAST rootAST) {
156         // No code by default, should be overridden only by demand at subclasses
157     }
158 
159     /**
160      * Called to process a token.
161      *
162      * @param ast the token to process
163      */
164     public void visitToken(DetailAST ast) {
165         // No code by default, should be overridden only by demand at subclasses
166     }
167 
168     /**
169      * Called after all the child nodes have been process.
170      *
171      * @param ast the token leaving
172      */
173     public void leaveToken(DetailAST ast) {
174         // No code by default, should be overridden only by demand at subclasses
175     }
176 
177     /**
178      * Set the file contents associated with the tree.
179      *
180      * @param contents the manager
181      */
182     public final void setFileContents(FileContents contents) {
183         context.get().fileContents = contents;
184     }
185 
186     /**
187      * Returns the file contents associated with the tree.
188      *
189      * @return the file contents
190      * @noinspection WeakerAccess
191      */
192     public final FileContents getFileContents() {
193         return context.get().fileContents;
194     }
195 
196     /**
197      * Get tab width to report audit events with.
198      *
199      * @return the tab width to audit events with
200      */
201     protected final int getTabWidth() {
202         return tabWidth;
203     }
204 
205     /**
206      * Set the tab width to report audit events with.
207      *
208      * @param tabWidth an {@code int} value
209      */
210     public final void setTabWidth(int tabWidth) {
211         this.tabWidth = tabWidth;
212     }
213 
214     @Override
215     public final void log(int line, String key, Object... args) {
216         context.get().messages.add(
217             new LocalizedMessage(
218                 line,
219                 getMessageBundle(),
220                 key,
221                 args,
222                 getSeverityLevel(),
223                 getId(),
224                 getClass(),
225                 getCustomMessages().get(key)));
226     }
227 
228     @Override
229     public final void log(int lineNo, int colNo, String key,
230             Object... args) {
231         final int col = 1 + CommonUtil.lengthExpandedTabs(
232             getLines()[lineNo - 1], colNo, tabWidth);
233         context.get().messages.add(
234             new LocalizedMessage(
235                 lineNo,
236                 col,
237                 getMessageBundle(),
238                 key,
239                 args,
240                 getSeverityLevel(),
241                 getId(),
242                 getClass(),
243                 getCustomMessages().get(key)));
244     }
245 
246     /**
247      * Helper method to log a LocalizedMessage.
248      *
249      * @param ast a node to get line id column numbers associated
250      *             with the message
251      * @param key key to locale message 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().messages.add(
264                 new LocalizedMessage(
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     /**
279      * Returns the lines associated with the tree.
280      *
281      * @return the file contents
282      */
283     public final String[] getLines() {
284         return context.get().fileContents.getLines();
285     }
286 
287     /**
288      * Returns the line associated with the tree.
289      *
290      * @param index index of the line
291      * @return the line from the file contents
292      */
293     public final String getLine(int index) {
294         return context.get().fileContents.getLine(index);
295     }
296 
297     /**
298      * The actual context holder.
299      */
300     private static class FileContext {
301 
302         /** The sorted set for collecting messages. */
303         private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
304 
305         /** The current file contents. */
306         private FileContents fileContents;
307 
308     }
309 
310 }