001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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}