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.io.File;
023import java.util.Arrays;
024import java.util.SortedSet;
025import java.util.TreeSet;
026
027import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
028
029/**
030 * Provides common functionality for many FileSetChecks.
031 *
032 * @noinspection NoopMethodInAbstractClass
033 * @noinspectionreason NoopMethodInAbstractClass - we allow each
034 *      check to define these methods, as needed. They
035 *      should be overridden only by demand in subclasses
036 */
037public abstract class AbstractFileSetCheck
038    extends AbstractViolationReporter
039    implements FileSetCheck {
040
041    /** The extension separator. */
042    private static final String EXTENSION_SEPARATOR = ".";
043
044    /**
045     * The check context.
046     *
047     * @noinspection ThreadLocalNotStaticFinal
048     * @noinspectionreason ThreadLocalNotStaticFinal - static context is
049     *      problematic for multithreading
050     */
051    private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
052
053    /** The dispatcher errors are fired to. */
054    private MessageDispatcher messageDispatcher;
055
056    /**
057     * Specify the file extensions of the files to process.
058     * Default is uninitialized as the value is inherited from the parent module.
059     */
060    private String[] fileExtensions;
061
062    /**
063     * The tab width for column reporting.
064     * Default is uninitialized as the value is inherited from the parent module.
065     */
066    private int tabWidth;
067
068    /**
069     * Creates a new {@code AbstractFileSetCheck} instance.
070     */
071    protected AbstractFileSetCheck() {
072        // no code by default
073    }
074
075    /**
076     * Called to process a file that matches the specified file extensions.
077     *
078     * @param file the file to be processed
079     * @param fileText the contents of the file.
080     * @throws CheckstyleException if error condition within Checkstyle occurs.
081     */
082    protected abstract void processFiltered(File file, FileText fileText)
083            throws CheckstyleException;
084
085    @Override
086    public void init() {
087        // No code by default, should be overridden only by demand at subclasses
088    }
089
090    @Override
091    public void destroy() {
092        context.remove();
093    }
094
095    @Override
096    public void beginProcessing(String charset) {
097        // No code by default, should be overridden only by demand at subclasses
098    }
099
100    @Override
101    public final SortedSet<Violation> process(File file, FileText fileText)
102            throws CheckstyleException {
103        final FileContext fileContext = context.get();
104        fileContext.fileContents = new FileContents(fileText);
105        fileContext.violations.clear();
106        // Process only what interested in
107        if (CommonUtil.matchesFileExtension(file, fileExtensions)) {
108            processFiltered(file, fileText);
109        }
110        final SortedSet<Violation> result = new TreeSet<>(fileContext.violations);
111        fileContext.violations.clear();
112        return result;
113    }
114
115    @Override
116    public void finishProcessing() {
117        // No code by default, should be overridden only by demand at subclasses
118    }
119
120    @Override
121    public final void setMessageDispatcher(MessageDispatcher messageDispatcher) {
122        this.messageDispatcher = messageDispatcher;
123    }
124
125    /**
126     * A message dispatcher is used to fire violations to interested audit listeners.
127     *
128     * @return the current MessageDispatcher.
129     */
130    protected final MessageDispatcher getMessageDispatcher() {
131        return messageDispatcher;
132    }
133
134    /**
135     * Returns the sorted set of {@link Violation}.
136     *
137     * @return the sorted set of {@code Violation}.
138     */
139    public SortedSet<Violation> getViolations() {
140        return new TreeSet<>(context.get().violations);
141    }
142
143    /**
144     * Set the file contents associated with the tree.
145     *
146     * @param contents the manager
147     */
148    public final void setFileContents(FileContents contents) {
149        context.get().fileContents = contents;
150    }
151
152    /**
153     * Returns the file contents associated with the file.
154     *
155     * @return the file contents
156     */
157    protected final FileContents getFileContents() {
158        return context.get().fileContents;
159    }
160
161    /**
162     * Makes copy of file extensions and returns them.
163     *
164     * @return file extensions that identify the files that pass the filter of this FileSetCheck.
165     */
166    public String[] getFileExtensions() {
167        return Arrays.copyOf(fileExtensions, fileExtensions.length);
168    }
169
170    /**
171     * Setter to specify the file extensions of the files to process.
172     *
173     * @param extensions the set of file extensions. A missing
174     *         initial '.' character of an extension is automatically added.
175     * @throws IllegalArgumentException is argument is null
176     */
177    public void setFileExtensions(String... extensions) {
178        if (extensions == null) {
179            throw new IllegalArgumentException("Extensions array can not be null");
180        }
181
182        fileExtensions = new String[extensions.length];
183        for (int index = 0; index < extensions.length; index++) {
184            final String extension = extensions[index];
185            if (extension.startsWith(EXTENSION_SEPARATOR)) {
186                fileExtensions[index] = extension;
187            }
188            else {
189                fileExtensions[index] = EXTENSION_SEPARATOR + extension;
190            }
191        }
192    }
193
194    /**
195     * Get tab width to report audit events with.
196     *
197     * @return the tab width to report audit events with
198     */
199    protected final int getTabWidth() {
200        return tabWidth;
201    }
202
203    /**
204     * Set the tab width to report audit events with.
205     *
206     * @param tabWidth an {@code int} value
207     */
208    public final void setTabWidth(int tabWidth) {
209        this.tabWidth = tabWidth;
210    }
211
212    /**
213     * Adds the sorted set of {@link Violation} to the message collector.
214     *
215     * @param violations the sorted set of {@code Violation}.
216     */
217    protected void addViolations(SortedSet<Violation> violations) {
218        context.get().violations.addAll(violations);
219    }
220
221    @Override
222    public final void log(int line, String key, Object... args) {
223        context.get().violations.add(
224                new Violation(line,
225                        getMessageBundle(),
226                        key,
227                        args,
228                        getSeverityLevel(),
229                        getId(),
230                        getClass(),
231                        getCustomMessages().get(key)));
232    }
233
234    @Override
235    public final void log(int lineNo, int colNo, String key,
236            Object... args) {
237        final FileContext fileContext = context.get();
238        final int col = 1 + CommonUtil.lengthExpandedTabs(
239                fileContext.fileContents.getLine(lineNo - 1), colNo, tabWidth);
240        fileContext.violations.add(
241                new Violation(lineNo,
242                        col,
243                        getMessageBundle(),
244                        key,
245                        args,
246                        getSeverityLevel(),
247                        getId(),
248                        getClass(),
249                        getCustomMessages().get(key)));
250    }
251
252    /**
253     * Notify all listeners about the errors in a file.
254     * Calls {@code MessageDispatcher.fireErrors()} with
255     * all logged errors and then clears errors' list.
256     *
257     * @param fileName the audited file
258     */
259    protected final void fireErrors(String fileName) {
260        final FileContext fileContext = context.get();
261        final SortedSet<Violation> errors = new TreeSet<>(fileContext.violations);
262        fileContext.violations.clear();
263        messageDispatcher.fireErrors(fileName, errors);
264    }
265
266    /**
267     * The actual context holder.
268     */
269    private static final class FileContext {
270
271        /** The sorted set for collecting violations. */
272        private final SortedSet<Violation> violations = new TreeSet<>();
273
274        /** The current file contents. */
275        private FileContents fileContents;
276
277        /**
278         * Creates a new {@code FileContext} instance.
279         */
280        private FileContext() {
281            // no code by default
282        }
283
284    }
285
286}