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