View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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       * Called to process a file that matches the specified file extensions.
70       *
71       * @param file the file to be processed
72       * @param fileText the contents of the file.
73       * @throws CheckstyleException if error condition within Checkstyle occurs.
74       */
75      protected abstract void processFiltered(File file, FileText fileText)
76              throws CheckstyleException;
77  
78      @Override
79      public void init() {
80          // No code by default, should be overridden only by demand at subclasses
81      }
82  
83      @Override
84      public void destroy() {
85          context.remove();
86      }
87  
88      @Override
89      public void beginProcessing(String charset) {
90          // No code by default, should be overridden only by demand at subclasses
91      }
92  
93      @Override
94      public final SortedSet<Violation> process(File file, FileText fileText)
95              throws CheckstyleException {
96          final FileContext fileContext = context.get();
97          fileContext.fileContents = new FileContents(fileText);
98          fileContext.violations.clear();
99          // 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 }