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 */
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     */
080    public DefaultLogger(OutputStream outputStream, boolean closeStreamsAfterUse) {
081        // no need to close oS twice
082        this(outputStream, closeStreamsAfterUse, outputStream, false);
083    }
084
085    /**
086     * Creates a new <code>DefaultLogger</code> instance.
087     * @param infoStream the {@code OutputStream} for info messages.
088     * @param closeInfoAfterUse auditFinished should close infoStream.
089     * @param errorStream the {@code OutputStream} for error messages.
090     * @param closeErrorAfterUse auditFinished should close errorStream
091     */
092    public DefaultLogger(OutputStream infoStream,
093                         boolean closeInfoAfterUse,
094                         OutputStream errorStream,
095                         boolean closeErrorAfterUse) {
096        this(infoStream, closeInfoAfterUse, errorStream, closeErrorAfterUse,
097            new AuditEventDefaultFormatter());
098    }
099
100    /**
101     * Creates a new {@code DefaultLogger} instance.
102     *
103     * @param infoStream the {@code OutputStream} for info messages
104     * @param closeInfoAfterUse auditFinished should close infoStream
105     * @param errorStream the {@code OutputStream} for error messages
106     * @param closeErrorAfterUse auditFinished should close errorStream
107     * @param messageFormatter formatter for the log message.
108     */
109    public DefaultLogger(OutputStream infoStream,
110                         boolean closeInfoAfterUse,
111                         OutputStream errorStream,
112                         boolean closeErrorAfterUse,
113                         AuditEventFormatter messageFormatter) {
114        closeInfo = closeInfoAfterUse;
115        closeError = closeErrorAfterUse;
116        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
117        infoWriter = new PrintWriter(infoStreamWriter);
118
119        if (infoStream == errorStream) {
120            errorWriter = infoWriter;
121        }
122        else {
123            final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
124                    StandardCharsets.UTF_8);
125            errorWriter = new PrintWriter(errorStreamWriter);
126        }
127        formatter = messageFormatter;
128    }
129
130    /**
131     * Print an Emacs compliant line on the error stream.
132     * If the column number is non zero, then also display it.
133     * @see AuditListener
134     **/
135    @Override
136    public void addError(AuditEvent event) {
137        final SeverityLevel severityLevel = event.getSeverityLevel();
138        if (severityLevel != SeverityLevel.IGNORE) {
139            final String errorMessage = formatter.format(event);
140            errorWriter.println(errorMessage);
141        }
142    }
143
144    @Override
145    public void addException(AuditEvent event, Throwable throwable) {
146        synchronized (errorWriter) {
147            final LocalizedMessage addExceptionMessage = new LocalizedMessage(0,
148                Definitions.CHECKSTYLE_BUNDLE, ADD_EXCEPTION_MESSAGE,
149                new String[] {event.getFileName()}, null,
150                LocalizedMessage.class, null);
151            errorWriter.println(addExceptionMessage.getMessage());
152            throwable.printStackTrace(errorWriter);
153        }
154    }
155
156    @Override
157    public void auditStarted(AuditEvent event) {
158        final LocalizedMessage auditStartMessage = new LocalizedMessage(0,
159            Definitions.CHECKSTYLE_BUNDLE, AUDIT_STARTED_MESSAGE, null, null,
160            LocalizedMessage.class, null);
161        infoWriter.println(auditStartMessage.getMessage());
162        infoWriter.flush();
163    }
164
165    @Override
166    public void auditFinished(AuditEvent event) {
167        final LocalizedMessage auditFinishMessage = new LocalizedMessage(0,
168            Definitions.CHECKSTYLE_BUNDLE, AUDIT_FINISHED_MESSAGE, null, null,
169            LocalizedMessage.class, null);
170        infoWriter.println(auditFinishMessage.getMessage());
171        closeStreams();
172    }
173
174    @Override
175    public void fileStarted(AuditEvent event) {
176        // No need to implement this method in this class
177    }
178
179    @Override
180    public void fileFinished(AuditEvent event) {
181        infoWriter.flush();
182    }
183
184    /**
185     * Flushes the output streams and closes them if needed.
186     */
187    private void closeStreams() {
188        infoWriter.flush();
189        if (closeInfo) {
190            infoWriter.close();
191        }
192
193        errorWriter.flush();
194        if (closeError) {
195            errorWriter.close();
196        }
197    }
198}