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;
21  
22  import java.io.OutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.PrintWriter;
25  import java.io.Writer;
26  import java.nio.charset.StandardCharsets;
27  
28  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
29  import com.puppycrawl.tools.checkstyle.api.AuditListener;
30  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
31  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
32  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
33  
34  /**
35   * Simple plain logger for text output.
36   * This is maybe not very suitable for a text output into a file since it
37   * does not need all 'audit finished' and so on stuff, but it looks good on
38   * stdout anyway. If there is really a problem this is what XMLLogger is for.
39   * It gives structure.
40   *
41   * @see XMLLogger
42   * @noinspection ClassWithTooManyConstructors
43   */
44  public class DefaultLogger extends AutomaticBean implements AuditListener {
45  
46      /**
47       * A key pointing to the add exception
48       * message in the "messages.properties" file.
49       */
50      public static final String ADD_EXCEPTION_MESSAGE = "DefaultLogger.addException";
51      /**
52       * A key pointing to the started audit
53       * message in the "messages.properties" file.
54       */
55      public static final String AUDIT_STARTED_MESSAGE = "DefaultLogger.auditStarted";
56      /**
57       * A key pointing to the finished audit
58       * message in the "messages.properties" file.
59       */
60      public static final String AUDIT_FINISHED_MESSAGE = "DefaultLogger.auditFinished";
61  
62      /** Where to write info messages. **/
63      private final PrintWriter infoWriter;
64      /** Close info stream after use. */
65      private final boolean closeInfo;
66  
67      /** Where to write error messages. **/
68      private final PrintWriter errorWriter;
69      /** Close error stream after use. */
70      private final boolean closeError;
71  
72      /** Formatter for the log message. */
73      private final AuditEventFormatter formatter;
74  
75      /**
76       * Creates a new {@code DefaultLogger} instance.
77       * @param outputStream where to log infos and errors
78       * @param closeStreamsAfterUse if oS should be closed in auditFinished()
79       * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
80       * @noinspection BooleanParameter
81       */
82      @Deprecated
83      public DefaultLogger(OutputStream outputStream, boolean closeStreamsAfterUse) {
84          // no need to close oS twice
85          this(outputStream, closeStreamsAfterUse, outputStream, false);
86      }
87  
88      /**
89       * Creates a new {@code DefaultLogger} instance.
90       * @param infoStream the {@code OutputStream} for info messages.
91       * @param closeInfoAfterUse auditFinished should close infoStream.
92       * @param errorStream the {@code OutputStream} for error messages.
93       * @param closeErrorAfterUse auditFinished should close errorStream
94       * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
95       * @noinspection BooleanParameter
96       */
97      @Deprecated
98      public DefaultLogger(OutputStream infoStream,
99                           boolean closeInfoAfterUse,
100                          OutputStream errorStream,
101                          boolean closeErrorAfterUse) {
102         this(infoStream, closeInfoAfterUse, errorStream, closeErrorAfterUse,
103             new AuditEventDefaultFormatter());
104     }
105 
106     /**
107      * Creates a new {@code DefaultLogger} instance.
108      *
109      * @param infoStream the {@code OutputStream} for info messages
110      * @param closeInfoAfterUse auditFinished should close infoStream
111      * @param errorStream the {@code OutputStream} for error messages
112      * @param closeErrorAfterUse auditFinished should close errorStream
113      * @param messageFormatter formatter for the log message.
114      * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
115      * @noinspection BooleanParameter, WeakerAccess
116      */
117     @Deprecated
118     public DefaultLogger(OutputStream infoStream,
119                          boolean closeInfoAfterUse,
120                          OutputStream errorStream,
121                          boolean closeErrorAfterUse,
122                          AuditEventFormatter messageFormatter) {
123         closeInfo = closeInfoAfterUse;
124         closeError = closeErrorAfterUse;
125         final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
126         infoWriter = new PrintWriter(infoStreamWriter);
127 
128         if (infoStream == errorStream) {
129             errorWriter = infoWriter;
130         }
131         else {
132             final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
133                     StandardCharsets.UTF_8);
134             errorWriter = new PrintWriter(errorStreamWriter);
135         }
136         formatter = messageFormatter;
137     }
138 
139     /**
140      * Creates a new {@code DefaultLogger} instance.
141      * @param outputStream where to log infos and errors
142      * @param outputStreamOptions if {@code CLOSE} that should be closed in auditFinished()
143      */
144     public DefaultLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
145         // no need to close oS twice
146         this(outputStream, outputStreamOptions, outputStream, OutputStreamOptions.NONE);
147     }
148 
149     /**
150      * Creates a new {@code DefaultLogger} instance.
151      * @param infoStream the {@code OutputStream} for info messages.
152      * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
153      * @param errorStream the {@code OutputStream} for error messages.
154      * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
155      */
156     public DefaultLogger(OutputStream infoStream,
157                          OutputStreamOptions infoStreamOptions,
158                          OutputStream errorStream,
159                          OutputStreamOptions errorStreamOptions) {
160         this(infoStream, infoStreamOptions, errorStream, errorStreamOptions,
161                 new AuditEventDefaultFormatter());
162     }
163 
164     /**
165      * Creates a new {@code DefaultLogger} instance.
166      *
167      * @param infoStream the {@code OutputStream} for info messages
168      * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
169      * @param errorStream the {@code OutputStream} for error messages
170      * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
171      * @param messageFormatter formatter for the log message.
172      * @noinspection WeakerAccess
173      */
174     public DefaultLogger(OutputStream infoStream,
175                          OutputStreamOptions infoStreamOptions,
176                          OutputStream errorStream,
177                          OutputStreamOptions errorStreamOptions,
178                          AuditEventFormatter messageFormatter) {
179         if (infoStreamOptions == null) {
180             throw new IllegalArgumentException("Parameter infoStreamOptions can not be null");
181         }
182         closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
183         if (errorStreamOptions == null) {
184             throw new IllegalArgumentException("Parameter errorStreamOptions can not be null");
185         }
186         closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
187         final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
188         infoWriter = new PrintWriter(infoStreamWriter);
189 
190         if (infoStream == errorStream) {
191             errorWriter = infoWriter;
192         }
193         else {
194             final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
195                     StandardCharsets.UTF_8);
196             errorWriter = new PrintWriter(errorStreamWriter);
197         }
198         formatter = messageFormatter;
199     }
200 
201     @Override
202     protected void finishLocalSetup() {
203         // No code by default
204     }
205 
206     /**
207      * Print an Emacs compliant line on the error stream.
208      * If the column number is non zero, then also display it.
209      * @see AuditListener
210      **/
211     @Override
212     public void addError(AuditEvent event) {
213         final SeverityLevel severityLevel = event.getSeverityLevel();
214         if (severityLevel != SeverityLevel.IGNORE) {
215             final String errorMessage = formatter.format(event);
216             errorWriter.println(errorMessage);
217         }
218     }
219 
220     @Override
221     public void addException(AuditEvent event, Throwable throwable) {
222         synchronized (errorWriter) {
223             final LocalizedMessage addExceptionMessage = new LocalizedMessage(1,
224                 Definitions.CHECKSTYLE_BUNDLE, ADD_EXCEPTION_MESSAGE,
225                 new String[] {event.getFileName()}, null,
226                 LocalizedMessage.class, null);
227             errorWriter.println(addExceptionMessage.getMessage());
228             throwable.printStackTrace(errorWriter);
229         }
230     }
231 
232     @Override
233     public void auditStarted(AuditEvent event) {
234         final LocalizedMessage auditStartMessage = new LocalizedMessage(1,
235             Definitions.CHECKSTYLE_BUNDLE, AUDIT_STARTED_MESSAGE, null, null,
236             LocalizedMessage.class, null);
237         infoWriter.println(auditStartMessage.getMessage());
238         infoWriter.flush();
239     }
240 
241     @Override
242     public void auditFinished(AuditEvent event) {
243         final LocalizedMessage auditFinishMessage = new LocalizedMessage(1,
244             Definitions.CHECKSTYLE_BUNDLE, AUDIT_FINISHED_MESSAGE, null, null,
245             LocalizedMessage.class, null);
246         infoWriter.println(auditFinishMessage.getMessage());
247         closeStreams();
248     }
249 
250     @Override
251     public void fileStarted(AuditEvent event) {
252         // No need to implement this method in this class
253     }
254 
255     @Override
256     public void fileFinished(AuditEvent event) {
257         infoWriter.flush();
258     }
259 
260     /**
261      * Flushes the output streams and closes them if needed.
262      */
263     private void closeStreams() {
264         infoWriter.flush();
265         if (closeInfo) {
266             infoWriter.close();
267         }
268 
269         errorWriter.flush();
270         if (closeError) {
271             errorWriter.close();
272         }
273     }
274 
275 }