001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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 * @see XMLLogger
042 * @noinspection ClassWithTooManyConstructors
043 */
044public class DefaultLogger extends AutomaticBean implements AuditListener {
045
046    /**
047     * A key pointing to the add exception
048     * message in the "messages.properties" file.
049     */
050    public static final String ADD_EXCEPTION_MESSAGE = "DefaultLogger.addException";
051    /**
052     * A key pointing to the started audit
053     * message in the "messages.properties" file.
054     */
055    public static final String AUDIT_STARTED_MESSAGE = "DefaultLogger.auditStarted";
056    /**
057     * A key pointing to the finished audit
058     * message in the "messages.properties" file.
059     */
060    public static final String AUDIT_FINISHED_MESSAGE = "DefaultLogger.auditFinished";
061
062    /** Where to write info messages. **/
063    private final PrintWriter infoWriter;
064    /** Close info stream after use. */
065    private final boolean closeInfo;
066
067    /** Where to write error messages. **/
068    private final PrintWriter errorWriter;
069    /** Close error stream after use. */
070    private final boolean closeError;
071
072    /** Formatter for the log message. */
073    private final AuditEventFormatter formatter;
074
075    /**
076     * Creates a new {@code DefaultLogger} instance.
077     * @param outputStream where to log infos and errors
078     * @param closeStreamsAfterUse if oS should be closed in auditFinished()
079     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
080     * @noinspection BooleanParameter
081     */
082    @Deprecated
083    public DefaultLogger(OutputStream outputStream, boolean closeStreamsAfterUse) {
084        // no need to close oS twice
085        this(outputStream, closeStreamsAfterUse, outputStream, false);
086    }
087
088    /**
089     * Creates a new {@code DefaultLogger} instance.
090     * @param infoStream the {@code OutputStream} for info messages.
091     * @param closeInfoAfterUse auditFinished should close infoStream.
092     * @param errorStream the {@code OutputStream} for error messages.
093     * @param closeErrorAfterUse auditFinished should close errorStream
094     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
095     * @noinspection BooleanParameter
096     */
097    @Deprecated
098    public DefaultLogger(OutputStream infoStream,
099                         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 audit events
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}