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;
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.SeverityLevel;
32  
33  /**
34   * Simple plain logger for text output.
35   * This is maybe not very suitable for a text output into a file since it
36   * does not need all 'audit finished' and so on stuff, but it looks good on
37   * stdout anyway. If there is really a problem this is what XMLLogger is for.
38   * It gives structure.
39   *
40   * @see XMLLogger
41   */
42  public class DefaultLogger extends AbstractAutomaticBean implements AuditListener {
43  
44      /**
45       * A key pointing to the add exception
46       * message in the "messages.properties" file.
47       */
48      public static final String ADD_EXCEPTION_MESSAGE = "DefaultLogger.addException";
49      /**
50       * A key pointing to the started audit
51       * message in the "messages.properties" file.
52       */
53      public static final String AUDIT_STARTED_MESSAGE = "DefaultLogger.auditStarted";
54      /**
55       * A key pointing to the finished audit
56       * message in the "messages.properties" file.
57       */
58      public static final String AUDIT_FINISHED_MESSAGE = "DefaultLogger.auditFinished";
59  
60      /** Where to write info messages. **/
61      private final PrintWriter infoWriter;
62      /** Close info stream after use. */
63      private final boolean closeInfo;
64  
65      /** Where to write error messages. **/
66      private final PrintWriter errorWriter;
67      /** Close error stream after use. */
68      private final boolean closeError;
69  
70      /** Formatter for the log message. */
71      private final AuditEventFormatter formatter;
72  
73      /**
74       * Creates a new {@code DefaultLogger} instance.
75       *
76       * @param outputStream where to log audit events
77       * @param outputStreamOptions if {@code CLOSE} that should be closed in auditFinished()
78       * @noinspection deprecation
79       * @noinspectionreason We are forced to keep AutomaticBean compatability
80       *     because of maven-checkstyle-plugin. Until #12873.
81       */
82      public DefaultLogger(OutputStream outputStream,
83                           AutomaticBean.OutputStreamOptions outputStreamOptions) {
84          this(outputStream, OutputStreamOptions.valueOf(outputStreamOptions.name()));
85      }
86  
87      /**
88       * Creates a new {@code DefaultLogger} instance.
89       *
90       * @param outputStream where to log audit events
91       * @param outputStreamOptions if {@code CLOSE} that should be closed in auditFinished()
92       */
93      public DefaultLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
94          // no need to close oS twice
95          this(outputStream, outputStreamOptions, outputStream, OutputStreamOptions.NONE);
96      }
97  
98      /**
99       * Creates a new {@code DefaultLogger} instance.
100      *
101      * @param infoStream the {@code OutputStream} for info messages.
102      * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
103      * @param errorStream the {@code OutputStream} for error messages.
104      * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
105      */
106     public DefaultLogger(OutputStream infoStream,
107                          OutputStreamOptions infoStreamOptions,
108                          OutputStream errorStream,
109                          OutputStreamOptions errorStreamOptions) {
110         this(infoStream, infoStreamOptions, errorStream, errorStreamOptions,
111                 new AuditEventDefaultFormatter());
112     }
113 
114     /**
115      * Creates a new {@code DefaultLogger} instance.
116      *
117      * @param infoStream the {@code OutputStream} for info messages
118      * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
119      * @param errorStream the {@code OutputStream} for error messages
120      * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
121      * @param messageFormatter formatter for the log message.
122      * @throws IllegalArgumentException if stream options are null
123      * @noinspection WeakerAccess
124      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
125      */
126     public DefaultLogger(OutputStream infoStream,
127                          OutputStreamOptions infoStreamOptions,
128                          OutputStream errorStream,
129                          OutputStreamOptions errorStreamOptions,
130                          AuditEventFormatter messageFormatter) {
131         if (infoStreamOptions == null) {
132             throw new IllegalArgumentException("Parameter infoStreamOptions can not be null");
133         }
134         closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
135         if (errorStreamOptions == null) {
136             throw new IllegalArgumentException("Parameter errorStreamOptions can not be null");
137         }
138         closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
139         final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
140         infoWriter = new PrintWriter(infoStreamWriter);
141 
142         if (infoStream == errorStream) {
143             errorWriter = infoWriter;
144         }
145         else {
146             final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
147                     StandardCharsets.UTF_8);
148             errorWriter = new PrintWriter(errorStreamWriter);
149         }
150         formatter = messageFormatter;
151     }
152 
153     @Override
154     protected void finishLocalSetup() {
155         // No code by default
156     }
157 
158     /**
159      * Print an Emacs compliant line on the error stream.
160      * If the column number is non-zero, then also display it.
161      *
162      * @see AuditListener
163      **/
164     @Override
165     public void addError(AuditEvent event) {
166         final SeverityLevel severityLevel = event.getSeverityLevel();
167         if (severityLevel != SeverityLevel.IGNORE) {
168             final String errorMessage = formatter.format(event);
169             errorWriter.println(errorMessage);
170         }
171     }
172 
173     @Override
174     public void addException(AuditEvent event, Throwable throwable) {
175         synchronized (errorWriter) {
176             final LocalizedMessage exceptionMessage = new LocalizedMessage(
177                     Definitions.CHECKSTYLE_BUNDLE, DefaultLogger.class,
178                     ADD_EXCEPTION_MESSAGE, event.getFileName());
179             errorWriter.println(exceptionMessage.getMessage());
180             throwable.printStackTrace(errorWriter);
181         }
182     }
183 
184     @Override
185     public void auditStarted(AuditEvent event) {
186         final LocalizedMessage auditStartMessage = new LocalizedMessage(
187                 Definitions.CHECKSTYLE_BUNDLE, DefaultLogger.class,
188                 AUDIT_STARTED_MESSAGE);
189         infoWriter.println(auditStartMessage.getMessage());
190         infoWriter.flush();
191     }
192 
193     @Override
194     public void auditFinished(AuditEvent event) {
195         final LocalizedMessage auditFinishMessage = new LocalizedMessage(
196                 Definitions.CHECKSTYLE_BUNDLE, DefaultLogger.class,
197                 AUDIT_FINISHED_MESSAGE);
198         infoWriter.println(auditFinishMessage.getMessage());
199         closeStreams();
200     }
201 
202     @Override
203     public void fileStarted(AuditEvent event) {
204         // No need to implement this method in this class
205     }
206 
207     @Override
208     public void fileFinished(AuditEvent event) {
209         infoWriter.flush();
210     }
211 
212     /**
213      * Flushes the output streams and closes them if needed.
214      */
215     private void closeStreams() {
216         infoWriter.flush();
217         if (closeInfo) {
218             infoWriter.close();
219         }
220 
221         errorWriter.flush();
222         if (closeError) {
223             errorWriter.close();
224         }
225     }
226 }