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