001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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     *
042     * @noinspection ThreadLocalNotStaticFinal
043     */
044    private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
045
046    /** The tokens the check is interested in. */
047    private final Set<String> tokens = new HashSet<>();
048
049    /** The tab width for column reporting. */
050    private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH;
051
052    /**
053     * Returns the default token a check is interested in. Only used if the
054     * configuration for a check does not define the tokens.
055     *
056     * @return the default tokens
057     * @see TokenTypes
058     */
059    public abstract int[] getDefaultTokens();
060
061    /**
062     * The configurable token set.
063     * Used to protect Checks against malicious users who specify an
064     * unacceptable token set in the configuration file.
065     * The default implementation returns the check's default tokens.
066     *
067     * @return the token set this check is designed for.
068     * @see TokenTypes
069     */
070    public abstract int[] getAcceptableTokens();
071
072    /**
073     * The tokens that this check must be registered for.
074     *
075     * @return the token set this must be registered for.
076     * @see TokenTypes
077     */
078    public abstract int[] getRequiredTokens();
079
080    /**
081     * Whether comment nodes are required or not.
082     *
083     * @return false as a default value.
084     */
085    public boolean isCommentNodesRequired() {
086        return false;
087    }
088
089    /**
090     * Adds a set of tokens the check is interested in.
091     *
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     *
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}