View Javadoc
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 }