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