001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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(getLocalizedMessage( 133 DefaultLogger.class, 134 "DefaultLogger.infoStreamException")); 135 } 136 closeInfo = infoStreamOptions == OutputStreamOptions.CLOSE; 137 if (errorStreamOptions == null) { 138 throw new IllegalArgumentException(getLocalizedMessage( 139 DefaultLogger.class, 140 "DefaultLogger.errorStreamException")); 141 } 142 closeError = errorStreamOptions == OutputStreamOptions.CLOSE; 143 final Writer infoStreamWriter = new OutputStreamWriter(infoStream, StandardCharsets.UTF_8); 144 infoWriter = new PrintWriter(infoStreamWriter); 145 146 if (infoStream == errorStream) { 147 errorWriter = infoWriter; 148 } 149 else { 150 final Writer errorStreamWriter = new OutputStreamWriter(errorStream, 151 StandardCharsets.UTF_8); 152 errorWriter = new PrintWriter(errorStreamWriter); 153 } 154 formatter = messageFormatter; 155 } 156 157 @Override 158 protected void finishLocalSetup() { 159 // No code by default 160 } 161 162 /** 163 * Print an Emacs compliant line on the error stream. 164 * If the column number is non-zero, then also display it. 165 * 166 * @see AuditListener 167 **/ 168 @Override 169 public void addError(AuditEvent event) { 170 final SeverityLevel severityLevel = event.getSeverityLevel(); 171 if (severityLevel != SeverityLevel.IGNORE) { 172 final String errorMessage = formatter.format(event); 173 errorWriter.println(errorMessage); 174 } 175 } 176 177 @Override 178 public void addException(AuditEvent event, Throwable throwable) { 179 errorWriter.println(getLocalizedMessage( 180 DefaultLogger.class, 181 ADD_EXCEPTION_MESSAGE, 182 event.getFileName())); 183 throwable.printStackTrace(errorWriter); 184 } 185 186 @Override 187 public void auditStarted(AuditEvent event) { 188 infoWriter.println(getLocalizedMessage( 189 DefaultLogger.class, 190 AUDIT_STARTED_MESSAGE)); 191 infoWriter.flush(); 192 } 193 194 @Override 195 public void auditFinished(AuditEvent event) { 196 infoWriter.println(getLocalizedMessage( 197 DefaultLogger.class, 198 AUDIT_FINISHED_MESSAGE)); 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 227 /** 228 * Extracts localized messages from properties files. 229 * 230 * @param caller the {@link Class} used to resolve the resource bundle 231 * @param messageKey the key pointing to localized message in respective properties file. 232 * @param args the arguments of message in respective properties file. 233 * @return a string containing extracted localized message 234 */ 235 private static String getLocalizedMessage(Class<?> caller, 236 String messageKey, Object... args) { 237 final LocalizedMessage localizedMessage = new LocalizedMessage( 238 Definitions.CHECKSTYLE_BUNDLE, caller, 239 messageKey, args); 240 241 return localizedMessage.getMessage(); 242 } 243}