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}