001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 */
043public class DefaultLogger extends AutomaticBean implements AuditListener {
044
045    /**
046     * A key pointing to the add exception
047     * message in the "messages.properties" file.
048     */
049    public static final String ADD_EXCEPTION_MESSAGE = "DefaultLogger.addException";
050    /**
051     * A key pointing to the started audit
052     * message in the "messages.properties" file.
053     */
054    public static final String AUDIT_STARTED_MESSAGE = "DefaultLogger.auditStarted";
055    /**
056     * A key pointing to the finished audit
057     * message in the "messages.properties" file.
058     */
059    public static final String AUDIT_FINISHED_MESSAGE = "DefaultLogger.auditFinished";
060
061    /** Where to write info messages. **/
062    private final PrintWriter infoWriter;
063    /** Close info stream after use. */
064    private final boolean closeInfo;
065
066    /** Where to write error messages. **/
067    private final PrintWriter errorWriter;
068    /** Close error stream after use. */
069    private final boolean closeError;
070
071    /** Formatter for the log message. */
072    private final AuditEventFormatter formatter;
073
074    /**
075     * Creates a new {@code DefaultLogger} instance.
076     * @param outputStream where to log audit events
077     * @param outputStreamOptions if {@code CLOSE} that should be closed in auditFinished()
078     */
079    public DefaultLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
080        // no need to close oS twice
081        this(outputStream, outputStreamOptions, outputStream, OutputStreamOptions.NONE);
082    }
083
084    /**
085     * Creates a new {@code DefaultLogger} instance.
086     * @param infoStream the {@code OutputStream} for info messages.
087     * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
088     * @param errorStream the {@code OutputStream} for error messages.
089     * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
090     */
091    public DefaultLogger(OutputStream infoStream,
092                         OutputStreamOptions infoStreamOptions,
093                         OutputStream errorStream,
094                         OutputStreamOptions errorStreamOptions) {
095        this(infoStream, infoStreamOptions, errorStream, errorStreamOptions,
096                new AuditEventDefaultFormatter());
097    }
098
099    /**
100     * Creates a new {@code DefaultLogger} instance.
101     *
102     * @param infoStream the {@code OutputStream} for info messages
103     * @param infoStreamOptions if {@code CLOSE} info should be closed in auditFinished()
104     * @param errorStream the {@code OutputStream} for error messages
105     * @param errorStreamOptions if {@code CLOSE} error should be closed in auditFinished()
106     * @param messageFormatter formatter for the log message.
107     * @throws IllegalArgumentException if stream options are null
108     * @noinspection WeakerAccess
109     */
110    public DefaultLogger(OutputStream infoStream,
111                         OutputStreamOptions infoStreamOptions,
112                         OutputStream errorStream,
113                         OutputStreamOptions errorStreamOptions,
114                         AuditEventFormatter messageFormatter) {
115        if (infoStreamOptions == null) {
116            throw new IllegalArgumentException("Parameter infoStreamOptions can not be null");
117        }
118        closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE;
119        if (errorStreamOptions == null) {
120            throw new IllegalArgumentException("Parameter errorStreamOptions can not be null");
121        }
122        closeError = errorStreamOptions == OutputStreamOptions.CLOSE;
123        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
124        infoWriter = new PrintWriter(infoStreamWriter);
125
126        if (infoStream == errorStream) {
127            errorWriter = infoWriter;
128        }
129        else {
130            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
131                    StandardCharsets.UTF_8);
132            errorWriter = new PrintWriter(errorStreamWriter);
133        }
134        formatter = messageFormatter;
135    }
136
137    @Override
138    protected void finishLocalSetup() {
139        // No code by default
140    }
141
142    /**
143     * Print an Emacs compliant line on the error stream.
144     * If the column number is non zero, then also display it.
145     * @see AuditListener
146     **/
147    @Override
148    public void addError(AuditEvent event) {
149        final SeverityLevel severityLevel = event.getSeverityLevel();
150        if (severityLevel != SeverityLevel.IGNORE) {
151            final String errorMessage = formatter.format(event);
152            errorWriter.println(errorMessage);
153        }
154    }
155
156    @Override
157    public void addException(AuditEvent event, Throwable throwable) {
158        synchronized (errorWriter) {
159            final LocalizedMessage addExceptionMessage = new LocalizedMessage(1,
160                Definitions.CHECKSTYLE_BUNDLE, ADD_EXCEPTION_MESSAGE,
161                new String[] {event.getFileName()}, null,
162                LocalizedMessage.class, null);
163            errorWriter.println(addExceptionMessage.getMessage());
164            throwable.printStackTrace(errorWriter);
165        }
166    }
167
168    @Override
169    public void auditStarted(AuditEvent event) {
170        final LocalizedMessage auditStartMessage = new LocalizedMessage(1,
171            Definitions.CHECKSTYLE_BUNDLE, AUDIT_STARTED_MESSAGE, null, null,
172            LocalizedMessage.class, null);
173        infoWriter.println(auditStartMessage.getMessage());
174        infoWriter.flush();
175    }
176
177    @Override
178    public void auditFinished(AuditEvent event) {
179        final LocalizedMessage auditFinishMessage = new LocalizedMessage(1,
180            Definitions.CHECKSTYLE_BUNDLE, AUDIT_FINISHED_MESSAGE, null, null,
181            LocalizedMessage.class, null);
182        infoWriter.println(auditFinishMessage.getMessage());
183        closeStreams();
184    }
185
186    @Override
187    public void fileStarted(AuditEvent event) {
188        // No need to implement this method in this class
189    }
190
191    @Override
192    public void fileFinished(AuditEvent event) {
193        infoWriter.flush();
194    }
195
196    /**
197     * Flushes the output streams and closes them if needed.
198     */
199    private void closeStreams() {
200        infoWriter.flush();
201        if (closeInfo) {
202            infoWriter.close();
203        }
204
205        errorWriter.flush();
206        if (closeError) {
207            errorWriter.close();
208        }
209    }
210
211}