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.CheckstyleException;
032import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
033import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
034
035/**
036 * Simple plain logger for text output.
037 * This is maybe not very suitable for a text output into a file since it
038 * does not need all 'audit finished' and so on stuff, but it looks good on
039 * stdout anyway. If there is really a problem this is what XMLLogger is for.
040 * It gives structure.
041 *
042 * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a>
043 * @see XMLLogger
044 * @noinspection ClassWithTooManyConstructors
045 */
046public class DefaultLogger extends AutomaticBean implements AuditListener {
047
048    /**
049     * A key pointing to the add exception
050     * message in the "messages.properties" file.
051     */
052    public static final String ADD_EXCEPTION_MESSAGE = "DefaultLogger.addException";
053    /**
054     * A key pointing to the started audit
055     * message in the "messages.properties" file.
056     */
057    public static final String AUDIT_STARTED_MESSAGE = "DefaultLogger.auditStarted";
058    /**
059     * A key pointing to the finished audit
060     * message in the "messages.properties" file.
061     */
062    public static final String AUDIT_FINISHED_MESSAGE = "DefaultLogger.auditFinished";
063
064    /** Where to write info messages. **/
065    private final PrintWriter infoWriter;
066    /** Close info stream after use. */
067    private final boolean closeInfo;
068
069    /** Where to write error messages. **/
070    private final PrintWriter errorWriter;
071    /** Close error stream after use. */
072    private final boolean closeError;
073
074    /** Formatter for the log message. */
075    private final AuditEventFormatter formatter;
076
077    /**
078     * Creates a new {@code DefaultLogger} instance.
079     * @param outputStream where to log infos and errors
080     * @param closeStreamsAfterUse if oS should be closed in auditFinished()
081     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
082     * @noinspection BooleanParameter
083     */
084    @Deprecated
085    public DefaultLogger(OutputStream outputStream, boolean closeStreamsAfterUse) {
086        // no need to close oS twice
087        this(outputStream, closeStreamsAfterUse, outputStream, false);
088    }
089
090    /**
091     * Creates a new {@code DefaultLogger} instance.
092     * @param infoStream the {@code OutputStream} for info messages.
093     * @param closeInfoAfterUse auditFinished should close infoStream.
094     * @param errorStream the {@code OutputStream} for error messages.
095     * @param closeErrorAfterUse auditFinished should close errorStream
096     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
097     * @noinspection BooleanParameter
098     */
099    @Deprecated
100    public DefaultLogger(OutputStream infoStream,
101                         boolean closeInfoAfterUse,
102                         OutputStream errorStream,
103                         boolean closeErrorAfterUse) {
104        this(infoStream, closeInfoAfterUse, errorStream, closeErrorAfterUse,
105            new AuditEventDefaultFormatter());
106    }
107
108    /**
109     * Creates a new {@code DefaultLogger} instance.
110     *
111     * @param infoStream the {@code OutputStream} for info messages
112     * @param closeInfoAfterUse auditFinished should close infoStream
113     * @param errorStream the {@code OutputStream} for error messages
114     * @param closeErrorAfterUse auditFinished should close errorStream
115     * @param messageFormatter formatter for the log message.
116     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
117     * @noinspection BooleanParameter, WeakerAccess
118     */
119    @Deprecated
120    public DefaultLogger(OutputStream infoStream,
121                         boolean closeInfoAfterUse,
122                         OutputStream errorStream,
123                         boolean closeErrorAfterUse,
124                         AuditEventFormatter messageFormatter) {
125        closeInfo = closeInfoAfterUse;
126        closeError = closeErrorAfterUse;
127        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
128        infoWriter = new PrintWriter(infoStreamWriter);
129
130        if (infoStream == errorStream) {
131            errorWriter = infoWriter;
132        }
133        else {
134            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
135                    StandardCharsets.UTF_8);
136            errorWriter = new PrintWriter(errorStreamWriter);
137        }
138        formatter = messageFormatter;
139    }
140
141    /**
142     * Creates a new {@code DefaultLogger} instance.
143     * @param outputStream where to log infos and errors
144     * @param outputStreamOptions if {@code CLOSE} that should be closed in auditFinished()
145     */
146    public DefaultLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
147        // no need to close oS twice
148        this(outputStream, outputStreamOptions, outputStream, OutputStreamOptions.NONE);
149    }
150
151    /**
152     * Creates a new {@code DefaultLogger} instance.
153     * @param infoStream the {@code OutputStream} for info messages.
154     * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
155     * @param errorStream the {@code OutputStream} for error messages.
156     * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
157     */
158    public DefaultLogger(OutputStream infoStream,
159                         OutputStreamOptions infoStreamOptions,
160                         OutputStream errorStream,
161                         OutputStreamOptions errorStreamOptions) {
162        this(infoStream, infoStreamOptions, errorStream, errorStreamOptions,
163                new AuditEventDefaultFormatter());
164    }
165
166    /**
167     * Creates a new {@code DefaultLogger} instance.
168     *
169     * @param infoStream the {@code OutputStream} for info messages
170     * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
171     * @param errorStream the {@code OutputStream} for error messages
172     * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
173     * @param messageFormatter formatter for the log message.
174     * @noinspection WeakerAccess
175     */
176    public DefaultLogger(OutputStream infoStream,
177                         OutputStreamOptions infoStreamOptions,
178                         OutputStream errorStream,
179                         OutputStreamOptions errorStreamOptions,
180                         AuditEventFormatter messageFormatter) {
181        closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
182        closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
183        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
184        infoWriter = new PrintWriter(infoStreamWriter);
185
186        if (infoStream == errorStream) {
187            errorWriter = infoWriter;
188        }
189        else {
190            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
191                    StandardCharsets.UTF_8);
192            errorWriter = new PrintWriter(errorStreamWriter);
193        }
194        formatter = messageFormatter;
195    }
196
197    @Override
198    protected void finishLocalSetup() throws CheckstyleException {
199        // No code by default
200    }
201
202    /**
203     * Print an Emacs compliant line on the error stream.
204     * If the column number is non zero, then also display it.
205     * @see AuditListener
206     **/
207    @Override
208    public void addError(AuditEvent event) {
209        final SeverityLevel severityLevel = event.getSeverityLevel();
210        if (severityLevel != SeverityLevel.IGNORE) {
211            final String errorMessage = formatter.format(event);
212            errorWriter.println(errorMessage);
213        }
214    }
215
216    @Override
217    public void addException(AuditEvent event, Throwable throwable) {
218        synchronized (errorWriter) {
219            final LocalizedMessage addExceptionMessage = new LocalizedMessage(0,
220                Definitions.CHECKSTYLE_BUNDLE, ADD_EXCEPTION_MESSAGE,
221                new String[] {event.getFileName()}, null,
222                LocalizedMessage.class, null);
223            errorWriter.println(addExceptionMessage.getMessage());
224            throwable.printStackTrace(errorWriter);
225        }
226    }
227
228    @Override
229    public void auditStarted(AuditEvent event) {
230        final LocalizedMessage auditStartMessage = new LocalizedMessage(0,
231            Definitions.CHECKSTYLE_BUNDLE, AUDIT_STARTED_MESSAGE, null, null,
232            LocalizedMessage.class, null);
233        infoWriter.println(auditStartMessage.getMessage());
234        infoWriter.flush();
235    }
236
237    @Override
238    public void auditFinished(AuditEvent event) {
239        final LocalizedMessage auditFinishMessage = new LocalizedMessage(0,
240            Definitions.CHECKSTYLE_BUNDLE, AUDIT_FINISHED_MESSAGE, null, null,
241            LocalizedMessage.class, null);
242        infoWriter.println(auditFinishMessage.getMessage());
243        closeStreams();
244    }
245
246    @Override
247    public void fileStarted(AuditEvent event) {
248        // No need to implement this method in this class
249    }
250
251    @Override
252    public void fileFinished(AuditEvent event) {
253        infoWriter.flush();
254    }
255
256    /**
257     * Flushes the output streams and closes them if needed.
258     */
259    private void closeStreams() {
260        infoWriter.flush();
261        if (closeInfo) {
262            infoWriter.close();
263        }
264
265        errorWriter.flush();
266        if (closeError) {
267            errorWriter.close();
268        }
269    }
270
271}