001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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 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        closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
180        closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
181        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
182        infoWriter = new PrintWriter(infoStreamWriter);
183
184        if (infoStream == errorStream) {
185            errorWriter = infoWriter;
186        }
187        else {
188            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
189                    StandardCharsets.UTF_8);
190            errorWriter = new PrintWriter(errorStreamWriter);
191        }
192        formatter = messageFormatter;
193    }
194
195    @Override
196    protected void finishLocalSetup() {
197        // No code by default
198    }
199
200    /**
201     * Print an Emacs compliant line on the error stream.
202     * If the column number is non zero, then also display it.
203     * @see AuditListener
204     **/
205    @Override
206    public void addError(AuditEvent event) {
207        final SeverityLevel severityLevel = event.getSeverityLevel();
208        if (severityLevel != SeverityLevel.IGNORE) {
209            final String errorMessage = formatter.format(event);
210            errorWriter.println(errorMessage);
211        }
212    }
213
214    @Override
215    public void addException(AuditEvent event, Throwable throwable) {
216        synchronized (errorWriter) {
217            final LocalizedMessage addExceptionMessage = new LocalizedMessage(0,
218                Definitions.CHECKSTYLE_BUNDLE, ADD_EXCEPTION_MESSAGE,
219                new String[] {event.getFileName()}, null,
220                LocalizedMessage.class, null);
221            errorWriter.println(addExceptionMessage.getMessage());
222            throwable.printStackTrace(errorWriter);
223        }
224    }
225
226    @Override
227    public void auditStarted(AuditEvent event) {
228        final LocalizedMessage auditStartMessage = new LocalizedMessage(0,
229            Definitions.CHECKSTYLE_BUNDLE, AUDIT_STARTED_MESSAGE, null, null,
230            LocalizedMessage.class, null);
231        infoWriter.println(auditStartMessage.getMessage());
232        infoWriter.flush();
233    }
234
235    @Override
236    public void auditFinished(AuditEvent event) {
237        final LocalizedMessage auditFinishMessage = new LocalizedMessage(0,
238            Definitions.CHECKSTYLE_BUNDLE, AUDIT_FINISHED_MESSAGE, null, null,
239            LocalizedMessage.class, null);
240        infoWriter.println(auditFinishMessage.getMessage());
241        closeStreams();
242    }
243
244    @Override
245    public void fileStarted(AuditEvent event) {
246        // No need to implement this method in this class
247    }
248
249    @Override
250    public void fileFinished(AuditEvent event) {
251        infoWriter.flush();
252    }
253
254    /**
255     * Flushes the output streams and closes them if needed.
256     */
257    private void closeStreams() {
258        infoWriter.flush();
259        if (closeInfo) {
260            infoWriter.close();
261        }
262
263        errorWriter.flush();
264        if (closeError) {
265            errorWriter.close();
266        }
267    }
268
269}