001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.api;
021
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.Set;
025import java.util.SortedSet;
026import java.util.TreeSet;
027
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * The base class for checks.
032 *
033 * @see <a href="{@docRoot}/../writingchecks.html" target="_top">Writing
034 * your own checks</a>
035 * @noinspection NoopMethodInAbstractClass
036 */
037public abstract class AbstractCheck extends AbstractViolationReporter {
038
039    /**
040     * The check context.
041     * @noinspection ThreadLocalNotStaticFinal
042     */
043    private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
044
045    /** The tokens the check is interested in. */
046    private final Set<String> tokens = new HashSet<>();
047
048    /** The tab width for column reporting. */
049    private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH;
050
051    /**
052     * The class loader to load external classes. Not initialized as this must
053     * be set by my creator.
054     */
055    private ClassLoader classLoader;
056
057    /**
058     * Returns the default token a check is interested in. Only used if the
059     * configuration for a check does not define the tokens.
060     * @return the default tokens
061     * @see TokenTypes
062     */
063    public abstract int[] getDefaultTokens();
064
065    /**
066     * The configurable token set.
067     * Used to protect Checks against malicious users who specify an
068     * unacceptable token set in the configuration file.
069     * The default implementation returns the check's default tokens.
070     * @return the token set this check is designed for.
071     * @see TokenTypes
072     */
073    public abstract int[] getAcceptableTokens();
074
075    /**
076     * The tokens that this check must be registered for.
077     * @return the token set this must be registered for.
078     * @see TokenTypes
079     */
080    public abstract int[] getRequiredTokens();
081
082    /**
083     * Whether comment nodes are required or not.
084     * @return false as a default value.
085     */
086    public boolean isCommentNodesRequired() {
087        return false;
088    }
089
090    /**
091     * Adds a set of tokens the check is interested in.
092     * @param strRep the string representation of the tokens interested in
093     * @noinspection WeakerAccess
094     */
095    public final void setTokens(String... strRep) {
096        Collections.addAll(tokens, strRep);
097    }
098
099    /**
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}