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