001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.CommonUtils;
029
030/**
031 * The base class for checks.
032 *
033 * @author Oliver Burn
034 * @see <a href="{@docRoot}/../writingchecks.html" target="_top">Writing
035 * your own checks</a>
036 * @noinspection NoopMethodInAbstractClass
037 */
038public abstract class AbstractCheck extends AbstractViolationReporter {
039
040    /** Default tab width for column reporting. */
041    private static final int DEFAULT_TAB_WIDTH = 8;
042
043    /**
044     * The check context.
045     * @noinspection ThreadLocalNotStaticFinal
046     */
047    private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
048
049    /** The tokens the check is interested in. */
050    private final Set<String> tokens = new HashSet<>();
051
052    /** The tab width for column reporting. */
053    private int tabWidth = DEFAULT_TAB_WIDTH;
054
055    /**
056     * The class loader to load external classes. Not initialized as this must
057     * be set by my creator.
058     */
059    private ClassLoader classLoader;
060
061    /**
062     * Returns the default token a check is interested in. Only used if the
063     * configuration for a check does not define the tokens.
064     * @return the default tokens
065     * @see TokenTypes
066     */
067    public abstract int[] getDefaultTokens();
068
069    /**
070     * The configurable token set.
071     * Used to protect Checks against malicious users who specify an
072     * unacceptable token set in the configuration file.
073     * The default implementation returns the check's default tokens.
074     * @return the token set this check is designed for.
075     * @see TokenTypes
076     */
077    public abstract int[] getAcceptableTokens();
078
079    /**
080     * The tokens that this check must be registered for.
081     * @return the token set this must be registered for.
082     * @see TokenTypes
083     */
084    public abstract int[] getRequiredTokens();
085
086    /**
087     * Whether comment nodes are required or not.
088     * @return false as a default value.
089     */
090    public boolean isCommentNodesRequired() {
091        return false;
092    }
093
094    /**
095     * Adds a set of tokens the check is interested in.
096     * @param strRep the string representation of the tokens interested in
097     * @noinspection WeakerAccess
098     */
099    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}