View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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  
40      /** Default tab width for column reporting. */
41      private static final int DEFAULT_TAB_WIDTH = 8;
42  
43      /**
44       * The check context.
45       * @noinspection ThreadLocalNotStaticFinal
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      /** The tab width for column reporting. */
53      private int tabWidth = DEFAULT_TAB_WIDTH;
54  
55      /**
56       * The class loader to load external classes. Not initialized as this must
57       * be set by my creator.
58       */
59      private ClassLoader classLoader;
60  
61      /**
62       * Returns the default token a check is interested in. Only used if the
63       * configuration for a check does not define the tokens.
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       * @return the token set this check is designed for.
75       * @see TokenTypes
76       */
77      public abstract int[] getAcceptableTokens();
78  
79      /**
80       * The tokens that this check must be registered for.
81       * @return the token set this must be registered for.
82       * @see TokenTypes
83       */
84      public abstract int[] getRequiredTokens();
85  
86      /**
87       * Whether comment nodes are required or not.
88       * @return false as a default value.
89       */
90      public boolean isCommentNodesRequired() {
91          return false;
92      }
93  
94      /**
95       * Adds a set of tokens the check is interested in.
96       * @param strRep the string representation of the tokens interested in
97       * @noinspection WeakerAccess
98       */
99      public final void setTokens(String... strRep) {
100         Collections.addAll(tokens, strRep);
101     }
102 
103     /**
104      * Returns the tokens registered for the check.
105      * @return the set of token names
106      */
107     public final Set<String> getTokenNames() {
108         return Collections.unmodifiableSet(tokens);
109     }
110 
111     /**
112      * Returns the sorted set of {@link LocalizedMessage}.
113      * @return the sorted set of {@link LocalizedMessage}.
114      */
115     public SortedSet<LocalizedMessage> getMessages() {
116         return new TreeSet<>(context.get().messages);
117     }
118 
119     /**
120      * Clears the sorted set of {@link LocalizedMessage} of the check.
121      */
122     public final void clearMessages() {
123         context.get().messages.clear();
124     }
125 
126     /**
127      * Initialize the check. This is the time to verify that the check has
128      * everything required to perform it job.
129      */
130     public void init() {
131         // No code by default, should be overridden only by demand at subclasses
132     }
133 
134     /**
135      * Destroy the check. It is being retired from service.
136      */
137     public void destroy() {
138         // No code by default, should be overridden only by demand at subclasses
139     }
140 
141     /**
142      * Called before the starting to process a tree. Ideal place to initialize
143      * information that is to be collected whilst processing a tree.
144      * @param rootAST the root of the tree
145      */
146     public void beginTree(DetailAST rootAST) {
147         // No code by default, should be overridden only by demand at subclasses
148     }
149 
150     /**
151      * Called after finished processing a tree. Ideal place to report on
152      * information collected whilst processing a tree.
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      * @param ast the token to process
162      */
163     public void visitToken(DetailAST ast) {
164         // No code by default, should be overridden only by demand at subclasses
165     }
166 
167     /**
168      * Called after all the child nodes have been process.
169      * @param ast the token leaving
170      */
171     public void leaveToken(DetailAST ast) {
172         // No code by default, should be overridden only by demand at subclasses
173     }
174 
175     /**
176      * Returns the lines associated with the tree.
177      * @return the file contents
178      */
179     public final String[] getLines() {
180         return context.get().fileContents.getLines();
181     }
182 
183     /**
184      * Returns the line associated with the tree.
185      * @param index index of the line
186      * @return the line from the file contents
187      */
188     public final String getLine(int index) {
189         return context.get().fileContents.getLine(index);
190     }
191 
192     /**
193      * Set the file contents associated with the tree.
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      * @return the file contents
203      * @noinspection WeakerAccess
204      */
205     public final FileContents getFileContents() {
206         return context.get().fileContents;
207     }
208 
209     /**
210      * Set the class loader associated with the tree.
211      * @param classLoader the class loader
212      */
213     public final void setClassLoader(ClassLoader classLoader) {
214         this.classLoader = classLoader;
215     }
216 
217     /**
218      * Returns the class loader associated with the tree.
219      * @return the class loader
220      */
221     public final ClassLoader getClassLoader() {
222         return classLoader;
223     }
224 
225     /**
226      * Get tab width to report errors with.
227      * @return the tab width to report errors with
228      */
229     protected final int getTabWidth() {
230         return tabWidth;
231     }
232 
233     /**
234      * Set the tab width to report errors with.
235      * @param tabWidth an {@code int} value
236      */
237     public final void setTabWidth(int tabWidth) {
238         this.tabWidth = tabWidth;
239     }
240 
241     /**
242      * Helper method to log a LocalizedMessage.
243      *
244      * @param ast a node to get line id column numbers associated
245      *             with the message
246      * @param key key to locale message format
247      * @param args arguments to format
248      */
249     public final void log(DetailAST ast, String key, Object... args) {
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 
310         /** The sorted set for collecting messages. */
311         private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
312 
313         /** The current file contents. */
314         private FileContents fileContents;
315 
316     }
317 
318 }