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.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     * Called to process a file that matches the specified file extensions.
070     *
071     * @param file the file to be processed
072     * @param fileText the contents of the file.
073     * @throws CheckstyleException if error condition within Checkstyle occurs.
074     */
075    protected abstract void processFiltered(File file, FileText fileText)
076            throws CheckstyleException;
077
078    @Override
079    public void init() {
080        // No code by default, should be overridden only by demand at subclasses
081    }
082
083    @Override
084    public void destroy() {
085        context.remove();
086    }
087
088    @Override
089    public void beginProcessing(String charset) {
090        // No code by default, should be overridden only by demand at subclasses
091    }
092
093    @Override
094    public final SortedSet<Violation> process(File file, FileText fileText)
095            throws CheckstyleException {
096        final FileContext fileContext = context.get();
097        fileContext.fileContents = new FileContents(fileText);
098        fileContext.violations.clear();
099        // Process only what interested in
100        if (CommonUtil.matchesFileExtension(file, fileExtensions)) {
101            processFiltered(file, fileText);
102        }
103        final SortedSet<Violation> result = new TreeSet<>(fileContext.violations);
104        fileContext.violations.clear();
105        return result;
106    }
107
108    @Override
109    public void finishProcessing() {
110        // No code by default, should be overridden only by demand at subclasses
111    }
112
113    @Override
114    public final void setMessageDispatcher(MessageDispatcher messageDispatcher) {
115        this.messageDispatcher = messageDispatcher;
116    }
117
118    /**
119     * A message dispatcher is used to fire violations to
120     * interested audit listeners.
121     *
122     * @return the current MessageDispatcher.
123     */
124    protected final MessageDispatcher getMessageDispatcher() {
125        return messageDispatcher;
126    }
127
128    /**
129     * Returns the sorted set of {@link Violation}.
130     *
131     * @return the sorted set of {@link Violation}.
132     */
133    public SortedSet<Violation> getViolations() {
134        return new TreeSet<>(context.get().violations);
135    }
136
137    /**
138     * Set the file contents associated with the tree.
139     *
140     * @param contents the manager
141     */
142    public final void setFileContents(FileContents contents) {
143        context.get().fileContents = contents;
144    }
145
146    /**
147     * Returns the file contents associated with the file.
148     *
149     * @return the file contents
150     */
151    protected final FileContents getFileContents() {
152        return context.get().fileContents;
153    }
154
155    /**
156     * Makes copy of file extensions and returns them.
157     *
158     * @return file extensions that identify the files that pass the
159     *     filter of this FileSetCheck.
160     */
161    public String[] getFileExtensions() {
162        return Arrays.copyOf(fileExtensions, fileExtensions.length);
163    }
164
165    /**
166     * Setter to specify the file extensions of the files to process.
167     *
168     * @param extensions the set of file extensions. A missing
169     *         initial '.' character of an extension is automatically added.
170     * @throws IllegalArgumentException is argument is null
171     */
172    public final void setFileExtensions(String... extensions) {
173        if (extensions == null) {
174            throw new IllegalArgumentException("Extensions array can not be null");
175        }
176
177        fileExtensions = new String[extensions.length];
178        for (int i = 0; i < extensions.length; i++) {
179            final String extension = extensions[i];
180            if (extension.startsWith(EXTENSION_SEPARATOR)) {
181                fileExtensions[i] = extension;
182            }
183            else {
184                fileExtensions[i] = EXTENSION_SEPARATOR + extension;
185            }
186        }
187    }
188
189    /**
190     * Get tab width to report audit events with.
191     *
192     * @return the tab width to report audit events with
193     */
194    protected final int getTabWidth() {
195        return tabWidth;
196    }
197
198    /**
199     * Set the tab width to report audit events with.
200     *
201     * @param tabWidth an {@code int} value
202     */
203    public final void setTabWidth(int tabWidth) {
204        this.tabWidth = tabWidth;
205    }
206
207    /**
208     * Adds the sorted set of {@link Violation} to the message collector.
209     *
210     * @param violations the sorted set of {@link Violation}.
211     */
212    protected void addViolations(SortedSet<Violation> violations) {
213        context.get().violations.addAll(violations);
214    }
215
216    @Override
217    public final void log(int line, String key, Object... args) {
218        context.get().violations.add(
219                new Violation(line,
220                        getMessageBundle(),
221                        key,
222                        args,
223                        getSeverityLevel(),
224                        getId(),
225                        getClass(),
226                        getCustomMessages().get(key)));
227    }
228
229    @Override
230    public final void log(int lineNo, int colNo, String key,
231            Object... args) {
232        final FileContext fileContext = context.get();
233        final int col = 1 + CommonUtil.lengthExpandedTabs(
234                fileContext.fileContents.getLine(lineNo - 1), colNo, tabWidth);
235        fileContext.violations.add(
236                new Violation(lineNo,
237                        col,
238                        getMessageBundle(),
239                        key,
240                        args,
241                        getSeverityLevel(),
242                        getId(),
243                        getClass(),
244                        getCustomMessages().get(key)));
245    }
246
247    /**
248     * Notify all listeners about the errors in a file.
249     * Calls {@code MessageDispatcher.fireErrors()} with
250     * all logged errors and then clears errors' list.
251     *
252     * @param fileName the audited file
253     */
254    protected final void fireErrors(String fileName) {
255        final FileContext fileContext = context.get();
256        final SortedSet<Violation> errors = new TreeSet<>(fileContext.violations);
257        fileContext.violations.clear();
258        messageDispatcher.fireErrors(fileName, errors);
259    }
260
261    /**
262     * The actual context holder.
263     */
264    private static final class FileContext {
265
266        /** The sorted set for collecting violations. */
267        private final SortedSet<Violation> violations = new TreeSet<>();
268
269        /** The current file contents. */
270        private FileContents fileContents;
271
272    }
273
274}