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