1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle;
21
22 import java.io.OutputStream;
23 import java.io.OutputStreamWriter;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
34 import com.puppycrawl.tools.checkstyle.api.AuditListener;
35 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
36 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
37
38
39
40
41
42
43
44
45
46 public class XMLLogger
47 extends AbstractAutomaticBean
48 implements AuditListener {
49
50
51 private static final int BASE_10 = 10;
52
53
54 private static final int BASE_16 = 16;
55
56
57 private static final int SOURCE_BUILDER_CAPACITY = 128;
58
59
60 private static final String[] ENTITIES = {"gt", "amp", "lt", "apos",
61 "quot", };
62
63
64 private final boolean closeStream;
65
66
67 private final Map<String, FileMessages> fileMessages =
68 new HashMap<>();
69
70
71
72
73 private final PrintWriter writer;
74
75
76
77
78
79
80
81
82
83
84
85
86 public XMLLogger(OutputStream outputStream,
87 AutomaticBean.OutputStreamOptions outputStreamOptions) {
88 this(outputStream, OutputStreamOptions.valueOf(outputStreamOptions.name()));
89 }
90
91
92
93
94
95
96
97
98
99 public XMLLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
100 writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
101 if (outputStreamOptions == null) {
102 throw new IllegalArgumentException("Parameter outputStreamOptions can not be null");
103 }
104 closeStream = outputStreamOptions == OutputStreamOptions.CLOSE;
105 }
106
107 @Override
108 protected void finishLocalSetup() {
109
110 }
111
112
113
114
115
116 private void printVersionString() {
117 final String version = XMLLogger.class.getPackage().getImplementationVersion();
118 writer.println("<checkstyle version=\"" + version + "\">");
119 }
120
121 @Override
122 public void auditStarted(AuditEvent event) {
123 writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
124
125 printVersionString();
126 }
127
128 @Override
129 public void auditFinished(AuditEvent event) {
130 writer.println("</checkstyle>");
131 if (closeStream) {
132 writer.close();
133 }
134 else {
135 writer.flush();
136 }
137 }
138
139 @Override
140 public void fileStarted(AuditEvent event) {
141 fileMessages.put(event.getFileName(), new FileMessages());
142 }
143
144 @Override
145 public void fileFinished(AuditEvent event) {
146 final String fileName = event.getFileName();
147 final FileMessages messages = fileMessages.remove(fileName);
148 writeFileMessages(fileName, messages);
149 }
150
151
152
153
154
155
156
157 private void writeFileMessages(String fileName, FileMessages messages) {
158 writeFileOpeningTag(fileName);
159 if (messages != null) {
160 for (AuditEvent errorEvent : messages.getErrors()) {
161 writeFileError(errorEvent);
162 }
163 for (Throwable exception : messages.getExceptions()) {
164 writeException(exception);
165 }
166 }
167 writeFileClosingTag();
168 }
169
170
171
172
173
174
175 private void writeFileOpeningTag(String fileName) {
176 writer.println("<file name=\"" + encode(fileName) + "\">");
177 }
178
179
180
181
182 private void writeFileClosingTag() {
183 writer.println("</file>");
184 }
185
186 @Override
187 public void addError(AuditEvent event) {
188 if (event.getSeverityLevel() != SeverityLevel.IGNORE) {
189 final String fileName = event.getFileName();
190 final FileMessages messages = fileMessages.get(fileName);
191 if (messages != null) {
192 messages.addError(event);
193 }
194 else {
195 writeFileError(event);
196 }
197 }
198 }
199
200
201
202
203
204
205 private void writeFileError(AuditEvent event) {
206 writer.print("<error" + " line=\"" + event.getLine() + "\"");
207 if (event.getColumn() > 0) {
208 writer.print(" column=\"" + event.getColumn() + "\"");
209 }
210 writer.print(" severity=\""
211 + event.getSeverityLevel().getName()
212 + "\"");
213 writer.print(" message=\""
214 + encode(event.getMessage())
215 + "\"");
216 writer.print(" source=\"");
217 final StringBuilder sourceValueBuilder = new StringBuilder(SOURCE_BUILDER_CAPACITY);
218 sourceValueBuilder.append(event.getSourceName());
219 final String moduleId = event.getModuleId();
220 if (moduleId != null && !moduleId.isBlank()) {
221 sourceValueBuilder
222 .append('#')
223 .append(moduleId);
224 }
225 writer.print(encode(sourceValueBuilder.toString()));
226 writer.println("\"/>");
227 }
228
229 @Override
230 public void addException(AuditEvent event, Throwable throwable) {
231 final String fileName = event.getFileName();
232 final FileMessages messages = fileMessages.get(fileName);
233 if (messages != null) {
234 messages.addException(throwable);
235 }
236 else {
237 writeException(throwable);
238 }
239 }
240
241
242
243
244
245
246 private void writeException(Throwable throwable) {
247 writer.println("<exception>");
248 writer.println("<![CDATA[");
249
250 final StringWriter stringWriter = new StringWriter();
251 final PrintWriter printer = new PrintWriter(stringWriter);
252 throwable.printStackTrace(printer);
253 writer.println(encode(stringWriter.toString()));
254
255 writer.println("]]>");
256 writer.println("</exception>");
257 }
258
259
260
261
262
263
264
265 public static String encode(String value) {
266 final StringBuilder sb = new StringBuilder(256);
267 for (int i = 0; i < value.length(); i++) {
268 final char chr = value.charAt(i);
269 switch (chr) {
270 case '<':
271 sb.append("<");
272 break;
273 case '>':
274 sb.append(">");
275 break;
276 case '\'':
277 sb.append("'");
278 break;
279 case '\"':
280 sb.append(""");
281 break;
282 case '&':
283 sb.append("&");
284 break;
285 case '\r':
286 break;
287 case '\n':
288 sb.append(" ");
289 break;
290 default:
291 if (Character.isISOControl(chr)) {
292
293
294 sb.append("#x");
295 sb.append(Integer.toHexString(chr));
296 sb.append(';');
297 }
298 else {
299 sb.append(chr);
300 }
301 break;
302 }
303 }
304 return sb.toString();
305 }
306
307
308
309
310
311
312
313 public static boolean isReference(String ent) {
314 boolean reference = false;
315
316 if (ent.charAt(0) == '&' && ent.endsWith(";")) {
317 if (ent.charAt(1) == '#') {
318
319 int prefixLength = 2;
320
321 int radix = BASE_10;
322 if (ent.charAt(2) == 'x') {
323 prefixLength++;
324 radix = BASE_16;
325 }
326 try {
327 Integer.parseInt(
328 ent.substring(prefixLength, ent.length() - 1), radix);
329 reference = true;
330 }
331 catch (final NumberFormatException ignored) {
332 reference = false;
333 }
334 }
335 else {
336 final String name = ent.substring(1, ent.length() - 1);
337 for (String element : ENTITIES) {
338 if (name.equals(element)) {
339 reference = true;
340 break;
341 }
342 }
343 }
344 }
345
346 return reference;
347 }
348
349
350
351
352 private static final class FileMessages {
353
354
355 private final List<AuditEvent> errors = new ArrayList<>();
356
357
358 private final List<Throwable> exceptions = new ArrayList<>();
359
360
361
362
363
364
365 public List<AuditEvent> getErrors() {
366 return Collections.unmodifiableList(errors);
367 }
368
369
370
371
372
373
374 public void addError(AuditEvent event) {
375 errors.add(event);
376 }
377
378
379
380
381
382
383 public List<Throwable> getExceptions() {
384 return Collections.unmodifiableList(exceptions);
385 }
386
387
388
389
390
391
392 public void addException(Throwable throwable) {
393 exceptions.add(throwable);
394 }
395
396 }
397
398 }