View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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(getLocalizedMessage(
133                     DefaultLogger.class,
134                     "DefaultLogger.infoStreamException"));
135         }
136         closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
137         if (errorStreamOptions == null) {
138             throw new IllegalArgumentException(getLocalizedMessage(
139                     DefaultLogger.class,
140                     "DefaultLogger.errorStreamException"));
141         }
142         closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
143         final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
144         infoWriter = new PrintWriter(infoStreamWriter);
145 
146         if (infoStream == errorStream) {
147             errorWriter = infoWriter;
148         }
149         else {
150             final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
151                     StandardCharsets.UTF_8);
152             errorWriter = new PrintWriter(errorStreamWriter);
153         }
154         formatter = messageFormatter;
155     }
156 
157     @Override
158     protected void finishLocalSetup() {
159         // No code by default
160     }
161 
162     /**
163      * Print an Emacs compliant line on the error stream.
164      * If the column number is non-zero, then also display it.
165      *
166      * @see AuditListener
167      **/
168     @Override
169     public void addError(AuditEvent event) {
170         final SeverityLevel severityLevel = event.getSeverityLevel();
171         if (severityLevel != SeverityLevel.IGNORE) {
172             final String errorMessage = formatter.format(event);
173             errorWriter.println(errorMessage);
174         }
175     }
176 
177     @Override
178     public void addException(AuditEvent event, Throwable throwable) {
179         errorWriter.println(getLocalizedMessage(
180                 DefaultLogger.class,
181                 ADD_EXCEPTION_MESSAGE,
182                 event.getFileName()));
183         throwable.printStackTrace(errorWriter);
184     }
185 
186     @Override
187     public void auditStarted(AuditEvent event) {
188         infoWriter.println(getLocalizedMessage(
189                 DefaultLogger.class,
190                 AUDIT_STARTED_MESSAGE));
191         infoWriter.flush();
192     }
193 
194     @Override
195     public void auditFinished(AuditEvent event) {
196         infoWriter.println(getLocalizedMessage(
197                 DefaultLogger.class,
198                 AUDIT_FINISHED_MESSAGE));
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 
227     /**
228      * Extracts localized messages from properties files.
229      *
230      * @param caller the {@link Class} used to resolve the resource bundle
231      * @param messageKey the key pointing to localized message in respective properties file.
232      * @param args the arguments of message in respective properties file.
233      * @return a string containing extracted localized message
234      */
235     private static String getLocalizedMessage(Class<?> caller,
236                                               String messageKey, Object... args) {
237         final LocalizedMessage localizedMessage = new LocalizedMessage(
238             Definitions.CHECKSTYLE_BUNDLE, caller,
239                     messageKey, args);
240 
241         return localizedMessage.getMessage();
242     }
243 }