1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.api;
21
22 import java.io.File;
23 import java.util.Arrays;
24 import java.util.SortedSet;
25 import java.util.TreeSet;
26
27 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
28
29 /**
30 * Provides common functionality for many FileSetChecks.
31 *
32 * @noinspection NoopMethodInAbstractClass
33 * @noinspectionreason NoopMethodInAbstractClass - we allow each
34 * check to define these methods, as needed. They
35 * should be overridden only by demand in subclasses
36 */
37 public abstract class AbstractFileSetCheck
38 extends AbstractViolationReporter
39 implements FileSetCheck {
40
41 /** The extension separator. */
42 private static final String EXTENSION_SEPARATOR = ".";
43
44 /**
45 * The check context.
46 *
47 * @noinspection ThreadLocalNotStaticFinal
48 * @noinspectionreason ThreadLocalNotStaticFinal - static context is
49 * problematic for multithreading
50 */
51 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
52
53 /** The dispatcher errors are fired to. */
54 private MessageDispatcher messageDispatcher;
55
56 /**
57 * Specify the file extensions of the files to process.
58 * Default is uninitialized as the value is inherited from the parent module.
59 */
60 private String[] fileExtensions;
61
62 /**
63 * The tab width for column reporting.
64 * Default is uninitialized as the value is inherited from the parent module.
65 */
66 private int tabWidth;
67
68 /**
69 * Creates a new {@code AbstractFileSetCheck} instance.
70 */
71 protected AbstractFileSetCheck() {
72 // no code by default
73 }
74
75 /**
76 * Called to process a file that matches the specified file extensions.
77 *
78 * @param file the file to be processed
79 * @param fileText the contents of the file.
80 * @throws CheckstyleException if error condition within Checkstyle occurs.
81 */
82 protected abstract void processFiltered(File file, FileText fileText)
83 throws CheckstyleException;
84
85 @Override
86 public void init() {
87 // No code by default, should be overridden only by demand at subclasses
88 }
89
90 @Override
91 public void destroy() {
92 context.remove();
93 }
94
95 @Override
96 public void beginProcessing(String charset) {
97 // No code by default, should be overridden only by demand at subclasses
98 }
99
100 @Override
101 public final SortedSet<Violation> process(File file, FileText fileText)
102 throws CheckstyleException {
103 final FileContext fileContext = context.get();
104 fileContext.fileContents = new FileContents(fileText);
105 fileContext.violations.clear();
106 // Process only what interested in
107 if (CommonUtil.matchesFileExtension(file, fileExtensions)) {
108 processFiltered(file, fileText);
109 }
110 final SortedSet<Violation> result = new TreeSet<>(fileContext.violations);
111 fileContext.violations.clear();
112 return result;
113 }
114
115 @Override
116 public void finishProcessing() {
117 // No code by default, should be overridden only by demand at subclasses
118 }
119
120 @Override
121 public final void setMessageDispatcher(MessageDispatcher messageDispatcher) {
122 this.messageDispatcher = messageDispatcher;
123 }
124
125 /**
126 * A message dispatcher is used to fire violations to
127 * interested audit listeners.
128 *
129 * @return the current MessageDispatcher.
130 */
131 protected final MessageDispatcher getMessageDispatcher() {
132 return messageDispatcher;
133 }
134
135 /**
136 * Returns the sorted set of {@link Violation}.
137 *
138 * @return the sorted set of {@link Violation}.
139 */
140 public SortedSet<Violation> getViolations() {
141 return new TreeSet<>(context.get().violations);
142 }
143
144 /**
145 * Set the file contents associated with the tree.
146 *
147 * @param contents the manager
148 */
149 public final void setFileContents(FileContents contents) {
150 context.get().fileContents = contents;
151 }
152
153 /**
154 * Returns the file contents associated with the file.
155 *
156 * @return the file contents
157 */
158 protected final FileContents getFileContents() {
159 return context.get().fileContents;
160 }
161
162 /**
163 * Makes copy of file extensions and returns them.
164 *
165 * @return file extensions that identify the files that pass the
166 * filter of this FileSetCheck.
167 */
168 public String[] getFileExtensions() {
169 return Arrays.copyOf(fileExtensions, fileExtensions.length);
170 }
171
172 /**
173 * Setter to specify the file extensions of the files to process.
174 *
175 * @param extensions the set of file extensions. A missing
176 * initial '.' character of an extension is automatically added.
177 * @throws IllegalArgumentException is argument is null
178 */
179 public void setFileExtensions(String... extensions) {
180 if (extensions == null) {
181 throw new IllegalArgumentException("Extensions array can not be null");
182 }
183
184 fileExtensions = new String[extensions.length];
185 for (int i = 0; i < extensions.length; i++) {
186 final String extension = extensions[i];
187 if (extension.startsWith(EXTENSION_SEPARATOR)) {
188 fileExtensions[i] = extension;
189 }
190 else {
191 fileExtensions[i] = EXTENSION_SEPARATOR + extension;
192 }
193 }
194 }
195
196 /**
197 * Get tab width to report audit events with.
198 *
199 * @return the tab width to report audit events with
200 */
201 protected final int getTabWidth() {
202 return tabWidth;
203 }
204
205 /**
206 * Set the tab width to report audit events with.
207 *
208 * @param tabWidth an {@code int} value
209 */
210 public final void setTabWidth(int tabWidth) {
211 this.tabWidth = tabWidth;
212 }
213
214 /**
215 * Adds the sorted set of {@link Violation} to the message collector.
216 *
217 * @param violations the sorted set of {@link Violation}.
218 */
219 protected void addViolations(SortedSet<Violation> violations) {
220 context.get().violations.addAll(violations);
221 }
222
223 @Override
224 public final void log(int line, String key, Object... args) {
225 context.get().violations.add(
226 new Violation(line,
227 getMessageBundle(),
228 key,
229 args,
230 getSeverityLevel(),
231 getId(),
232 getClass(),
233 getCustomMessages().get(key)));
234 }
235
236 @Override
237 public final void log(int lineNo, int colNo, String key,
238 Object... args) {
239 final FileContext fileContext = context.get();
240 final int col = 1 + CommonUtil.lengthExpandedTabs(
241 fileContext.fileContents.getLine(lineNo - 1), colNo, tabWidth);
242 fileContext.violations.add(
243 new Violation(lineNo,
244 col,
245 getMessageBundle(),
246 key,
247 args,
248 getSeverityLevel(),
249 getId(),
250 getClass(),
251 getCustomMessages().get(key)));
252 }
253
254 /**
255 * Notify all listeners about the errors in a file.
256 * Calls {@code MessageDispatcher.fireErrors()} with
257 * all logged errors and then clears errors' list.
258 *
259 * @param fileName the audited file
260 */
261 protected final void fireErrors(String fileName) {
262 final FileContext fileContext = context.get();
263 final SortedSet<Violation> errors = new TreeSet<>(fileContext.violations);
264 fileContext.violations.clear();
265 messageDispatcher.fireErrors(fileName, errors);
266 }
267
268 /**
269 * The actual context holder.
270 */
271 private static final class FileContext {
272 /** The sorted set for collecting violations. */
273 private final SortedSet<Violation> violations = new TreeSet<>();
274
275 /** The current file contents. */
276 private FileContents fileContents;
277
278 /**
279 * Creates a new {@code FileContext} instance.
280 */
281 private FileContext() {
282 // no code by default
283 }
284 }
285
286 }