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