001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.SeverityLevel;
032
033/**
034 * Simple plain logger for text output.
035 * This is maybe not very suitable for a text output into a file since it
036 * does not need all 'audit finished' and so on stuff, but it looks good on
037 * stdout anyway. If there is really a problem this is what XMLLogger is for.
038 * It gives structure.
039 *
040 * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a>
041 * @see XMLLogger
042 */
043public class DefaultLogger
044    extends AutomaticBean
045    implements AuditListener {
046    /** Cushion for avoiding StringBuffer.expandCapacity */
047    private static final int BUFFER_CUSHION = 12;
048
049    /** Where to write info messages. **/
050    private final PrintWriter infoWriter;
051    /** Close info stream after use. */
052    private final boolean closeInfo;
053
054    /** Where to write error messages. **/
055    private final PrintWriter errorWriter;
056    /** Close error stream after use. */
057    private final boolean closeError;
058
059    /** Print severity level. */
060    private boolean printSeverity = true;
061
062    /**
063     * Creates a new {@code DefaultLogger} instance.
064     * @param outputStream where to log infos and errors
065     * @param closeStreamsAfterUse if oS should be closed in auditFinished()
066     */
067    public DefaultLogger(OutputStream outputStream, boolean closeStreamsAfterUse) {
068        // no need to close oS twice
069        this(outputStream, closeStreamsAfterUse, outputStream, false);
070    }
071
072    /**
073     * Creates a new <code>DefaultLogger</code> instance.
074     * @param infoStream the {@code OutputStream} for info messages.
075     * @param closeInfoAfterUse auditFinished should close infoStream.
076     * @param errorStream the {@code OutputStream} for error messages.
077     * @param closeErrorAfterUse auditFinished should close errorStream
078     * @param printSeverity if severity level should be printed.
079     */
080    public DefaultLogger(OutputStream infoStream,
081                         boolean closeInfoAfterUse,
082                         OutputStream errorStream,
083                         boolean closeErrorAfterUse,
084                         boolean printSeverity) {
085        this(infoStream, closeInfoAfterUse, errorStream, closeErrorAfterUse);
086        this.printSeverity = printSeverity;
087    }
088
089    /**
090     * Creates a new {@code DefaultLogger} instance.
091     *
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     */
097    public DefaultLogger(OutputStream infoStream,
098                         boolean closeInfoAfterUse,
099                         OutputStream errorStream,
100                         boolean closeErrorAfterUse) {
101        closeInfo = closeInfoAfterUse;
102        closeError = closeErrorAfterUse;
103        final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8);
104        final Writer errorStreamWriter = new OutputStreamWriter(errorStream,
105            StandardCharsets.UTF_8);
106        infoWriter = new PrintWriter(infoStreamWriter);
107
108        if (infoStream == errorStream) {
109            errorWriter = infoWriter;
110        }
111        else {
112            errorWriter = new PrintWriter(errorStreamWriter);
113        }
114    }
115
116    /**
117     * Print an Emacs compliant line on the error stream.
118     * If the column number is non zero, then also display it.
119     * @see AuditListener
120     **/
121    @Override
122    public void addError(AuditEvent event) {
123        final SeverityLevel severityLevel = event.getSeverityLevel();
124        if (severityLevel != SeverityLevel.IGNORE) {
125
126            final String fileName = event.getFileName();
127            final String message = event.getMessage();
128
129            // avoid StringBuffer.expandCapacity
130            final int bufLen = fileName.length() + message.length()
131                + BUFFER_CUSHION;
132            final StringBuilder sb = new StringBuilder(bufLen);
133
134            sb.append(fileName).append(':').append(event.getLine());
135            if (event.getColumn() > 0) {
136                sb.append(':').append(event.getColumn());
137            }
138            final String errorMessageSeparator = ": ";
139            if (printSeverity) {
140                sb.append(errorMessageSeparator).append(severityLevel.getName());
141            }
142            sb.append(errorMessageSeparator).append(message);
143            errorWriter.println(sb);
144        }
145    }
146
147    @Override
148    public void addException(AuditEvent event, Throwable throwable) {
149        synchronized (errorWriter) {
150            errorWriter.println("Error auditing " + event.getFileName());
151            throwable.printStackTrace(errorWriter);
152        }
153    }
154
155    @Override
156    public void auditStarted(AuditEvent event) {
157        infoWriter.println("Starting audit...");
158        infoWriter.flush();
159    }
160
161    @Override
162    public void fileFinished(AuditEvent event) {
163        infoWriter.flush();
164    }
165
166    @Override
167    public void fileStarted(AuditEvent event) {
168        // No need to implement this method in this class
169    }
170
171    @Override
172    public void auditFinished(AuditEvent event) {
173        infoWriter.println("Audit done.");
174        closeStreams();
175    }
176
177    /**
178     * Flushes the output streams and closes them if needed.
179     */
180    private void closeStreams() {
181        infoWriter.flush();
182        if (closeInfo) {
183            infoWriter.close();
184        }
185
186        errorWriter.flush();
187        if (closeError) {
188            errorWriter.close();
189        }
190    }
191}