001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle;
021
022import java.io.OutputStream;
023import java.io.OutputStreamWriter;
024import java.io.PrintWriter;
025import java.io.Writer;
026import java.nio.charset.StandardCharsets;
027
028import com.puppycrawl.tools.checkstyle.api.AuditEvent;
029import com.puppycrawl.tools.checkstyle.api.AuditListener;
030import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
031import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
032import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
033
034/**
035 * Simple plain logger for text output.
036 * This is maybe not very suitable for a text output into a file since it
037 * does not need all 'audit finished' and so on stuff, but it looks good on
038 * stdout anyway. If there is really a problem this is what XMLLogger is for.
039 * It gives structure.
040 *
041 * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a>
042 * @see XMLLogger
043 * @noinspection ClassWithTooManyConstructors
044 */
045public class DefaultLogger extends AutomaticBean implements AuditListener {
046
047    /**
048     * A key pointing to the add exception
049     * message in the "messages.properties" file.
050     */
051    public static final String ADD_EXCEPTION_MESSAGE = "DefaultLogger.addException";
052    /**
053     * A key pointing to the started audit
054     * message in the "messages.properties" file.
055     */
056    public static final String AUDIT_STARTED_MESSAGE = "DefaultLogger.auditStarted";
057    /**
058     * A key pointing to the finished audit
059     * message in the "messages.properties" file.
060     */
061    public static final String AUDIT_FINISHED_MESSAGE = "DefaultLogger.auditFinished";
062
063    /** Where to write info messages. **/
064    private final PrintWriter infoWriter;
065    /** Close info stream after use. */
066    private final boolean closeInfo;
067
068    /** Where to write error messages. **/
069    private final PrintWriter errorWriter;
070    /** Close error stream after use. */
071    private final boolean closeError;
072
073    /** Formatter for the log message. */
074    private final AuditEventFormatter formatter;
075
076    /**
077     * Creates a new {@code DefaultLogger} instance.
078     * @param outputStream where to log infos and errors
079     * @param closeStreamsAfterUse if oS should be closed in auditFinished()
080     * @deprecated in order to fullfil demands of BooleanParameter IDEA check.
081     * @noinspection BooleanParameter
082     */
083    @Deprecated
084    public DefaultLogger(OutputStream outputStream, boolean closeStreamsAfterUse) {
085        // no need to close oS twice
086        this(outputStream, closeStreamsAfterUse, outputStream, false);
087    }
088
089    /**
090     * Creates a new {@code DefaultLogger} instance.
091     * @param infoStream the {@code OutputStream} for info messages.
092     * @param closeInfoAfterUse auditFinished should close infoStream.
093     * @param errorStream the {@code OutputStream} for error messages.
094     * @param closeErrorAfterUse auditFinished should close errorStream
095     * @deprecated in order to fullfil demands of BooleanParameter IDEA check.
096     * @noinspection BooleanParameter
097     */
098    @Deprecated
099    public DefaultLogger(OutputStream infoStream,
100                         boolean closeInfoAfterUse,
101                         OutputStream errorStream,
102                         boolean closeErrorAfterUse) {
103        this(infoStream, closeInfoAfterUse, errorStream, closeErrorAfterUse,
104            new AuditEventDefaultFormatter());
105    }
106
107    /**
108     * Creates a new {@code DefaultLogger} instance.
109     *
110     * @param infoStream the {@code OutputStream} for info messages
111     * @param closeInfoAfterUse auditFinished should close infoStream
112     * @param errorStream the {@code OutputStream} for error messages
113     * @param closeErrorAfterUse auditFinished should close errorStream
114     * @param messageFormatter formatter for the log message.
115     * @deprecated in order to fullfil demands of BooleanParameter IDEA check.
116     * @noinspection BooleanParameter, WeakerAccess
117     */
118    @Deprecated
119    public DefaultLogger(OutputStream infoStream,
120                         boolean closeInfoAfterUse,
121                         OutputStream errorStream,
122                         boolean closeErrorAfterUse,
123                         AuditEventFormatter messageFormatter) {
124        closeInfo = closeInfoAfterUse;
125        closeError = closeErrorAfterUse;
126        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
127        infoWriter = new PrintWriter(infoStreamWriter);
128
129        if (infoStream == errorStream) {
130            errorWriter = infoWriter;
131        }
132        else {
133            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
134                    StandardCharsets.UTF_8);
135            errorWriter = new PrintWriter(errorStreamWriter);
136        }
137        formatter = messageFormatter;
138    }
139
140    /**
141     * Creates a new {@code DefaultLogger} instance.
142     * @param outputStream where to log infos and errors
143     * @param outputStreamOptions if {@code CLOSE} that should be closed in auditFinished()
144     */
145    public DefaultLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
146        // no need to close oS twice
147        this(outputStream, outputStreamOptions, outputStream, OutputStreamOptions.NONE);
148    }
149
150    /**
151     * Creates a new {@code DefaultLogger} instance.
152     * @param infoStream the {@code OutputStream} for info messages.
153     * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
154     * @param errorStream the {@code OutputStream} for error messages.
155     * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
156     */
157    public DefaultLogger(OutputStream infoStream,
158                         OutputStreamOptions infoStreamOptions,
159                         OutputStream errorStream,
160                         OutputStreamOptions errorStreamOptions) {
161        this(infoStream, infoStreamOptions, errorStream, errorStreamOptions,
162                new AuditEventDefaultFormatter());
163    }
164
165    /**
166     * Creates a new {@code DefaultLogger} instance.
167     *
168     * @param infoStream the {@code OutputStream} for info messages
169     * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
170     * @param errorStream the {@code OutputStream} for error messages
171     * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
172     * @param messageFormatter formatter for the log message.
173     * @noinspection WeakerAccess
174     */
175    public DefaultLogger(OutputStream infoStream,
176                         OutputStreamOptions infoStreamOptions,
177                         OutputStream errorStream,
178                         OutputStreamOptions errorStreamOptions,
179                         AuditEventFormatter messageFormatter) {
180        closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
181        closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
182        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
183        infoWriter = new PrintWriter(infoStreamWriter);
184
185        if (infoStream == errorStream) {
186            errorWriter = infoWriter;
187        }
188        else {
189            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
190                    StandardCharsets.UTF_8);
191            errorWriter = new PrintWriter(errorStreamWriter);
192        }
193        formatter = messageFormatter;
194    }
195
196    /**
197     * Print an Emacs compliant line on the error stream.
198     * If the column number is non zero, then also display it.
199     * @see AuditListener
200     **/
201    @Override
202    public void addError(AuditEvent event) {
203        final SeverityLevel severityLevel = event.getSeverityLevel();
204        if (severityLevel != SeverityLevel.IGNORE) {
205            final String errorMessage = formatter.format(event);
206            errorWriter.println(errorMessage);
207        }
208    }
209
210    @Override
211    public void addException(AuditEvent event, Throwable throwable) {
212        synchronized (errorWriter) {
213            final LocalizedMessage addExceptionMessage = new LocalizedMessage(0,
214                Definitions.CHECKSTYLE_BUNDLE, ADD_EXCEPTION_MESSAGE,
215                new String[] {event.getFileName()}, null,
216                LocalizedMessage.class, null);
217            errorWriter.println(addExceptionMessage.getMessage());
218            throwable.printStackTrace(errorWriter);
219        }
220    }
221
222    @Override
223    public void auditStarted(AuditEvent event) {
224        final LocalizedMessage auditStartMessage = new LocalizedMessage(0,
225            Definitions.CHECKSTYLE_BUNDLE, AUDIT_STARTED_MESSAGE, null, null,
226            LocalizedMessage.class, null);
227        infoWriter.println(auditStartMessage.getMessage());
228        infoWriter.flush();
229    }
230
231    @Override
232    public void auditFinished(AuditEvent event) {
233        final LocalizedMessage auditFinishMessage = new LocalizedMessage(0,
234            Definitions.CHECKSTYLE_BUNDLE, AUDIT_FINISHED_MESSAGE, null, null,
235            LocalizedMessage.class, null);
236        infoWriter.println(auditFinishMessage.getMessage());
237        closeStreams();
238    }
239
240    @Override
241    public void fileStarted(AuditEvent event) {
242        // No need to implement this method in this class
243    }
244
245    @Override
246    public void fileFinished(AuditEvent event) {
247        infoWriter.flush();
248    }
249
250    /**
251     * Flushes the output streams and closes them if needed.
252     */
253    private void closeStreams() {
254        infoWriter.flush();
255        if (closeInfo) {
256            infoWriter.close();
257        }
258
259        errorWriter.flush();
260        if (closeError) {
261            errorWriter.close();
262        }
263    }
264}