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 }