1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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   */
34  public abstract class AbstractFileSetCheck
35      extends AbstractViolationReporter
36      implements FileSetCheck {
37  
38      /**
39       * The check context.
40       * @noinspection ThreadLocalNotStaticFinal
41       */
42      private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
43  
44      /** The dispatcher errors are fired to. */
45      private MessageDispatcher messageDispatcher;
46  
47      /** The file extensions that are accepted by this filter. */
48      private String[] fileExtensions = CommonUtil.EMPTY_STRING_ARRAY;
49  
50      /** The tab width for column reporting. */
51      private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH;
52  
53      /**
54       * Called to process a file that matches the specified file extensions.
55       * @param file the file to be processed
56       * @param fileText the contents of the file.
57       * @throws CheckstyleException if error condition within Checkstyle occurs.
58       */
59      protected abstract void processFiltered(File file, FileText fileText)
60              throws CheckstyleException;
61  
62      @Override
63      public void init() {
64          // No code by default, should be overridden only by demand at subclasses
65      }
66  
67      @Override
68      public void destroy() {
69          // No code by default, should be overridden only by demand at subclasses
70      }
71  
72      @Override
73      public void beginProcessing(String charset) {
74          // No code by default, should be overridden only by demand at subclasses
75      }
76  
77      @Override
78      public final SortedSet<LocalizedMessage> process(File file, FileText fileText)
79              throws CheckstyleException {
80          final SortedSet<LocalizedMessage> messages = context.get().messages;
81          context.get().fileContents = new FileContents(fileText);
82          messages.clear();
83          // Process only what interested in
84          if (CommonUtil.matchesFileExtension(file, fileExtensions)) {
85              processFiltered(file, fileText);
86          }
87          final SortedSet<LocalizedMessage> result = new TreeSet<>(messages);
88          messages.clear();
89          return result;
90      }
91  
92      @Override
93      public void finishProcessing() {
94          // No code by default, should be overridden only by demand at subclasses
95      }
96  
97      @Override
98      public final void setMessageDispatcher(MessageDispatcher messageDispatcher) {
99          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 }