001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 * @noinspectionreason NoopMethodInAbstractClass - we allow each check to
037 *      define these methods, as needed. They should be overridden only
038 *      by demand in subclasses
039 */
040public abstract class AbstractCheck extends AbstractViolationReporter {
041
042    /**
043     * The check context.
044     *
045     * @noinspection ThreadLocalNotStaticFinal
046     * @noinspectionreason ThreadLocalNotStaticFinal - static context
047     *      is problematic for multithreading
048     */
049    private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
050
051    /** The tokens the check is interested in. */
052    private final Set<String> tokens = new HashSet<>();
053
054    /**
055     * The tab width for column reporting. Default is uninitialized as the value is inherited from
056     * the parent module.
057     */
058    private int tabWidth;
059
060    /**
061     * Returns the default token a check is interested in. Only used if the
062     * configuration for a check does not define the tokens.
063     *
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     *
075     * @return the token set this check is designed for.
076     * @see TokenTypes
077     */
078    public abstract int[] getAcceptableTokens();
079
080    /**
081     * The tokens that this check must be registered for.
082     *
083     * @return the token set this must be registered for.
084     * @see TokenTypes
085     */
086    public abstract int[] getRequiredTokens();
087
088    /**
089     * Whether comment nodes are required or not.
090     *
091     * @return false as a default value.
092     */
093    public boolean isCommentNodesRequired() {
094        return false;
095    }
096
097    /**
098     * Adds a set of tokens the check is interested in.
099     *
100     * @param strRep the string representation of the tokens interested in
101     * @noinspection WeakerAccess
102     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
103     */
104    public final void setTokens(String... strRep) {
105        Collections.addAll(tokens, strRep);
106    }
107
108    /**
109     * Returns the tokens registered for the check.
110     *
111     * @return the set of token names
112     */
113    public final Set<String> getTokenNames() {
114        return Collections.unmodifiableSet(tokens);
115    }
116
117    /**
118     * Returns the sorted set of {@link Violation}.
119     *
120     * @return the sorted set of {@link Violation}.
121     */
122    public SortedSet<Violation> getViolations() {
123        return new TreeSet<>(context.get().violations);
124    }
125
126    /**
127     * Clears the sorted set of {@link Violation} of the check.
128     */
129    public final void clearViolations() {
130        context.get().violations.clear();
131    }
132
133    /**
134     * Initialize the check. This is the time to verify that the check has
135     * everything required to perform its job.
136     */
137    public void init() {
138        // No code by default, should be overridden only by demand at subclasses
139    }
140
141    /**
142     * Destroy the check. It is being retired from service.
143     */
144    public void destroy() {
145        context.remove();
146    }
147
148    /**
149     * Called before the starting to process a tree. Ideal place to initialize
150     * information that is to be collected whilst processing a tree.
151     *
152     * @param rootAST the root of the tree
153     */
154    public void beginTree(DetailAST rootAST) {
155        // No code by default, should be overridden only by demand at subclasses
156    }
157
158    /**
159     * Called after finished processing a tree. Ideal place to report on
160     * information collected whilst processing a tree.
161     *
162     * @param rootAST the root of the tree
163     */
164    public void finishTree(DetailAST rootAST) {
165        // No code by default, should be overridden only by demand at subclasses
166    }
167
168    /**
169     * Called to process a token.
170     *
171     * @param ast the token to process
172     */
173    public void visitToken(DetailAST ast) {
174        // No code by default, should be overridden only by demand at subclasses
175    }
176
177    /**
178     * Called after all the child nodes have been process.
179     *
180     * @param ast the token leaving
181     */
182    public void leaveToken(DetailAST ast) {
183        // No code by default, should be overridden only by demand at subclasses
184    }
185
186    /**
187     * Set the file contents associated with the tree.
188     *
189     * @param contents the manager
190     */
191    public final void setFileContents(FileContents contents) {
192        context.get().fileContents = contents;
193    }
194
195    /**
196     * Returns the file contents associated with the tree.
197     *
198     * @return the file contents
199     * @deprecated
200     *      Usage of this method is no longer accepted.
201     *      Please use AST based methods instead.
202     * @noinspection WeakerAccess
203     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
204     */
205    @Deprecated(since = "9.3")
206    public final FileContents getFileContents() {
207        return context.get().fileContents;
208    }
209
210    /**
211     * Get tab width to report audit events with.
212     *
213     * @return the tab width to audit events with
214     */
215    protected final int getTabWidth() {
216        return tabWidth;
217    }
218
219    /**
220     * Set the tab width to report audit events with.
221     *
222     * @param tabWidth an {@code int} value
223     */
224    public final void setTabWidth(int tabWidth) {
225        this.tabWidth = tabWidth;
226    }
227
228    @Override
229    public final void log(int line, String key, Object... args) {
230        context.get().violations.add(
231            new Violation(
232                line,
233                getMessageBundle(),
234                key,
235                args,
236                getSeverityLevel(),
237                getId(),
238                getClass(),
239                getCustomMessages().get(key)));
240    }
241
242    @Override
243    public final void log(int lineNo, int colNo, String key,
244            Object... args) {
245        final int col = 1 + CommonUtil.lengthExpandedTabs(
246            getLines()[lineNo - 1], colNo, tabWidth);
247        context.get().violations.add(
248            new Violation(
249                lineNo,
250                col,
251                getMessageBundle(),
252                key,
253                args,
254                getSeverityLevel(),
255                getId(),
256                getClass(),
257                getCustomMessages().get(key)));
258    }
259
260    /**
261     * Helper method to log a Violation.
262     *
263     * @param ast a node to get line id column numbers associated
264     *             with the violation
265     * @param key key to locale violation format
266     * @param args arguments to format
267     */
268    public final void log(DetailAST ast, String key, Object... args) {
269        // CommonUtil.lengthExpandedTabs returns column number considering tabulation
270        // characters, it takes line from the file by line number, ast column number and tab
271        // width as arguments. Returned value is 0-based, but user must see column number starting
272        // from 1, that is why result of the method CommonUtil.lengthExpandedTabs
273        // is increased by one.
274
275        final int col = 1 + CommonUtil.lengthExpandedTabs(
276                getLines()[ast.getLineNo() - 1], ast.getColumnNo(), tabWidth);
277        context.get().violations.add(
278                new Violation(
279                        ast.getLineNo(),
280                        col,
281                        ast.getColumnNo(),
282                        ast.getType(),
283                        getMessageBundle(),
284                        key,
285                        args,
286                        getSeverityLevel(),
287                        getId(),
288                        getClass(),
289                        getCustomMessages().get(key)));
290    }
291
292    /**
293     * Returns the lines associated with the tree.
294     *
295     * @return the file contents
296     */
297    public final String[] getLines() {
298        return context.get().fileContents.getLines();
299    }
300
301    /**
302     * Returns the line associated with the tree.
303     *
304     * @param index index of the line
305     * @return the line from the file contents
306     */
307    public final String getLine(int index) {
308        return context.get().fileContents.getLine(index);
309    }
310
311    /**
312     * Returns full path to the file.
313     *
314     * @return full path to file.
315     */
316    public final String getFilePath() {
317        return context.get().fileContents.getFileName();
318    }
319
320    /**
321     * Returns code point representation of file text from given line number.
322     *
323     * @param index index of the line
324     * @return the array of Unicode code points
325     */
326    public final int[] getLineCodePoints(int index) {
327        return getLine(index).codePoints().toArray();
328    }
329
330    /**
331     * The actual context holder.
332     */
333    private static final class FileContext {
334
335        /** The sorted set for collecting violations. */
336        private final SortedSet<Violation> violations = new TreeSet<>();
337
338        /** The current file contents. */
339        private FileContents fileContents;
340
341    }
342
343}