View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.CommonUtils;
29  
30  /**
31   * The base class for checks.
32   *
33   * @author Oliver Burn
34   * @see <a href="{@docRoot}/../writingchecks.html" target="_top">Writing
35   * your own checks</a>
36   * @noinspection NoopMethodInAbstractClass
37   */
38  public abstract class AbstractCheck extends AbstractViolationReporter {
39      /** Default tab width for column reporting. */
40      private static final int DEFAULT_TAB_WIDTH = 8;
41  
42      /**
43       * The check context.
44       * @noinspection ThreadLocalNotStaticFinal
45       */
46      private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
47  
48      /** The tokens the check is interested in. */
49      private final Set<String> tokens = new HashSet<>();
50  
51      /** The tab width for column reporting. */
52      private int tabWidth = DEFAULT_TAB_WIDTH;
53  
54      /**
55       * The class loader to load external classes. Not initialized as this must
56       * be set by my creator.
57       */
58      private ClassLoader classLoader;
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       * @return the default tokens
64       * @see TokenTypes
65       */
66      public abstract int[] getDefaultTokens();
67  
68      /**
69       * The configurable token set.
70       * Used to protect Checks against malicious users who specify an
71       * unacceptable token set in the configuration file.
72       * The default implementation returns the check's default tokens.
73       * @return the token set this check is designed for.
74       * @see TokenTypes
75       */
76      public abstract int[] getAcceptableTokens();
77  
78      /**
79       * The tokens that this check must be registered for.
80       * @return the token set this must be registered for.
81       * @see TokenTypes
82       */
83      public abstract int[] getRequiredTokens();
84  
85      /**
86       * Whether comment nodes are required or not.
87       * @return false as a default value.
88       */
89      public boolean isCommentNodesRequired() {
90          return false;
91      }
92  
93      /**
94       * Adds a set of tokens the check is interested in.
95       * @param strRep the string representation of the tokens interested in
96       * @noinspection WeakerAccess
97       */
98      public final void setTokens(String... strRep) {
99          Collections.addAll(tokens, strRep);
100     }
101 
102     /**
103      * Returns the tokens registered for the check.
104      * @return the set of token names
105      */
106     public final Set<String> getTokenNames() {
107         return Collections.unmodifiableSet(tokens);
108     }
109 
110     /**
111      * Returns the sorted set of {@link LocalizedMessage}.
112      * @return the sorted set of {@link LocalizedMessage}.
113      */
114     public SortedSet<LocalizedMessage> getMessages() {
115         return new TreeSet<>(context.get().messages);
116     }
117 
118     /**
119      * Clears the sorted set of {@link LocalizedMessage} of the check.
120      */
121     public final void clearMessages() {
122         context.get().messages.clear();
123     }
124 
125     /**
126      * Initialize the check. This is the time to verify that the check has
127      * everything required to perform it job.
128      */
129     public void init() {
130         // No code by default, should be overridden only by demand at subclasses
131     }
132 
133     /**
134      * Destroy the check. It is being retired from service.
135      */
136     public void destroy() {
137         // No code by default, should be overridden only by demand at subclasses
138     }
139 
140     /**
141      * Called before the starting to process a tree. Ideal place to initialize
142      * information that is to be collected whilst processing a tree.
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      * @param rootAST the root of the tree
153      */
154     public void finishTree(DetailAST rootAST) {
155         // No code by default, should be overridden only by demand at subclasses
156     }
157 
158     /**
159      * Called to process a token.
160      * @param ast the token to process
161      */
162     public void visitToken(DetailAST ast) {
163         // No code by default, should be overridden only by demand at subclasses
164     }
165 
166     /**
167      * Called after all the child nodes have been process.
168      * @param ast the token leaving
169      */
170     public void leaveToken(DetailAST ast) {
171         // No code by default, should be overridden only by demand at subclasses
172     }
173 
174     /**
175      * Returns the lines associated with the tree.
176      * @return the file contents
177      */
178     public final String[] getLines() {
179         return context.get().fileContents.getLines();
180     }
181 
182     /**
183      * Returns the line associated with the tree.
184      * @param index index of the line
185      * @return the line from the file contents
186      */
187     public final String getLine(int index) {
188         return context.get().fileContents.getLine(index);
189     }
190 
191     /**
192      * Set the file contents associated with the tree.
193      * @param contents the manager
194      */
195     public final void setFileContents(FileContents contents) {
196         context.get().fileContents = contents;
197     }
198 
199     /**
200      * Returns the file contents associated with the tree.
201      * @return the file contents
202      * @noinspection WeakerAccess
203      */
204     public final FileContents getFileContents() {
205         return context.get().fileContents;
206     }
207 
208     /**
209      * Set the class loader associated with the tree.
210      * @param classLoader the class loader
211      */
212     public final void setClassLoader(ClassLoader classLoader) {
213         this.classLoader = classLoader;
214     }
215 
216     /**
217      * Returns the class loader associated with the tree.
218      * @return the class loader
219      */
220     public final ClassLoader getClassLoader() {
221         return classLoader;
222     }
223 
224     /**
225      * Get tab width to report errors with.
226      * @return the tab width to report errors with
227      */
228     protected final int getTabWidth() {
229         return tabWidth;
230     }
231 
232     /**
233      * Set the tab width to report errors with.
234      * @param tabWidth an {@code int} value
235      */
236     public final void setTabWidth(int tabWidth) {
237         this.tabWidth = tabWidth;
238     }
239 
240     /**
241      * Helper method to log a LocalizedMessage.
242      *
243      * @param ast a node to get line id column numbers associated
244      *             with the message
245      * @param key key to locale message format
246      * @param args arguments to format
247      */
248     public final void log(DetailAST ast, String key, Object... args) {
249 
250         // CommonUtils.lengthExpandedTabs returns column number considering tabulation
251         // characters, it takes line from the file by line number, ast column number and tab
252         // width as arguments. Returned value is 0-based, but user must see column number starting
253         // from 1, that is why result of the method CommonUtils.lengthExpandedTabs
254         // is increased by one.
255 
256         final int col = 1 + CommonUtils.lengthExpandedTabs(
257                 getLines()[ast.getLineNo() - 1], ast.getColumnNo(), tabWidth);
258         context.get().messages.add(
259                 new LocalizedMessage(
260                         ast.getLineNo(),
261                         col,
262                         ast.getColumnNo(),
263                         ast.getType(),
264                         getMessageBundle(),
265                         key,
266                         args,
267                         getSeverityLevel(),
268                         getId(),
269                         getClass(),
270                         getCustomMessages().get(key)));
271     }
272 
273     @Override
274     public final void log(int line, String key, Object... args) {
275         context.get().messages.add(
276             new LocalizedMessage(
277                 line,
278                 getMessageBundle(),
279                 key,
280                 args,
281                 getSeverityLevel(),
282                 getId(),
283                 getClass(),
284                 getCustomMessages().get(key)));
285     }
286 
287     @Override
288     public final void log(int lineNo, int colNo, String key,
289             Object... args) {
290         final int col = 1 + CommonUtils.lengthExpandedTabs(
291             getLines()[lineNo - 1], colNo, tabWidth);
292         context.get().messages.add(
293             new LocalizedMessage(
294                 lineNo,
295                 col,
296                 getMessageBundle(),
297                 key,
298                 args,
299                 getSeverityLevel(),
300                 getId(),
301                 getClass(),
302                 getCustomMessages().get(key)));
303     }
304 
305     /**
306      * The actual context holder.
307      */
308     private static class FileContext {
309         /** The sorted set for collecting messages. */
310         private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
311 
312         /** The current file contents. */
313         private FileContents fileContents;
314     }
315 }