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