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