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.List;
30 import java.util.Map;
31 import java.util.concurrent.ConcurrentHashMap;
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 String[] ENTITIES = {"gt", "amp", "lt", "apos",
58 "quot", };
59
60
61 private final boolean closeStream;
62
63
64 private final Object writerLock = new Object();
65
66
67 private final Map<String, FileMessages> fileMessages =
68 new ConcurrentHashMap<>();
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 @Override
113 public void auditStarted(AuditEvent event) {
114 writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
115
116 final String version = XMLLogger.class.getPackage().getImplementationVersion();
117
118 writer.println("<checkstyle version=\"" + version + "\">");
119 }
120
121 @Override
122 public void auditFinished(AuditEvent event) {
123 writer.println("</checkstyle>");
124 if (closeStream) {
125 writer.close();
126 }
127 else {
128 writer.flush();
129 }
130 }
131
132 @Override
133 public void fileStarted(AuditEvent event) {
134 fileMessages.put(event.getFileName(), new FileMessages());
135 }
136
137 @Override
138 public void fileFinished(AuditEvent event) {
139 final String fileName = event.getFileName();
140 final FileMessages messages = fileMessages.get(fileName);
141
142 synchronized (writerLock) {
143 writeFileMessages(fileName, messages);
144 }
145
146 fileMessages.remove(fileName);
147 }
148
149
150
151
152
153
154
155 private void writeFileMessages(String fileName, FileMessages messages) {
156 writeFileOpeningTag(fileName);
157 if (messages != null) {
158 for (AuditEvent errorEvent : messages.getErrors()) {
159 writeFileError(errorEvent);
160 }
161 for (Throwable exception : messages.getExceptions()) {
162 writeException(exception);
163 }
164 }
165 writeFileClosingTag();
166 }
167
168
169
170
171
172
173 private void writeFileOpeningTag(String fileName) {
174 writer.println("<file name=\"" + encode(fileName) + "\">");
175 }
176
177
178
179
180 private void writeFileClosingTag() {
181 writer.println("</file>");
182 }
183
184 @Override
185 public void addError(AuditEvent event) {
186 if (event.getSeverityLevel() != SeverityLevel.IGNORE) {
187 final String fileName = event.getFileName();
188 if (fileName == null || !fileMessages.containsKey(fileName)) {
189 synchronized (writerLock) {
190 writeFileError(event);
191 }
192 }
193 else {
194 final FileMessages messages = fileMessages.get(fileName);
195 messages.addError(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 if (event.getModuleId() == null) {
218 writer.print(encode(event.getSourceName()));
219 }
220 else {
221 writer.print(encode(event.getModuleId()));
222 }
223 writer.println("\"/>");
224 }
225
226 @Override
227 public void addException(AuditEvent event, Throwable throwable) {
228 final String fileName = event.getFileName();
229 if (fileName == null || !fileMessages.containsKey(fileName)) {
230 synchronized (writerLock) {
231 writeException(throwable);
232 }
233 }
234 else {
235 final FileMessages messages = fileMessages.get(fileName);
236 messages.addException(throwable);
237 }
238 }
239
240
241
242
243
244
245 private void writeException(Throwable throwable) {
246 writer.println("<exception>");
247 writer.println("<![CDATA[");
248
249 final StringWriter stringWriter = new StringWriter();
250 final PrintWriter printer = new PrintWriter(stringWriter);
251 throwable.printStackTrace(printer);
252 writer.println(encode(stringWriter.toString()));
253
254 writer.println("]]>");
255 writer.println("</exception>");
256 }
257
258
259
260
261
262
263
264 public static String encode(String value) {
265 final StringBuilder sb = new StringBuilder(256);
266 for (int i = 0; i < value.length(); i++) {
267 final char chr = value.charAt(i);
268 switch (chr) {
269 case '<':
270 sb.append("<");
271 break;
272 case '>':
273 sb.append(">");
274 break;
275 case '\'':
276 sb.append("'");
277 break;
278 case '\"':
279 sb.append(""");
280 break;
281 case '&':
282 sb.append("&");
283 break;
284 case '\r':
285 break;
286 case '\n':
287 sb.append(" ");
288 break;
289 default:
290 if (Character.isISOControl(chr)) {
291
292
293 sb.append("#x");
294 sb.append(Integer.toHexString(chr));
295 sb.append(';');
296 }
297 else {
298 sb.append(chr);
299 }
300 break;
301 }
302 }
303 return sb.toString();
304 }
305
306
307
308
309
310
311
312 public static boolean isReference(String ent) {
313 boolean reference = false;
314
315 if (ent.charAt(0) == '&' && ent.endsWith(";")) {
316 if (ent.charAt(1) == '#') {
317
318 int prefixLength = 2;
319
320 int radix = BASE_10;
321 if (ent.charAt(2) == 'x') {
322 prefixLength++;
323 radix = BASE_16;
324 }
325 try {
326 Integer.parseInt(
327 ent.substring(prefixLength, ent.length() - 1), radix);
328 reference = true;
329 }
330 catch (final NumberFormatException ignored) {
331 reference = false;
332 }
333 }
334 else {
335 final String name = ent.substring(1, ent.length() - 1);
336 for (String element : ENTITIES) {
337 if (name.equals(element)) {
338 reference = true;
339 break;
340 }
341 }
342 }
343 }
344
345 return reference;
346 }
347
348
349
350
351 private static final class FileMessages {
352
353
354 private final List<AuditEvent> errors = Collections.synchronizedList(new ArrayList<>());
355
356
357 private final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
358
359
360
361
362
363
364 public List<AuditEvent> getErrors() {
365 return Collections.unmodifiableList(errors);
366 }
367
368
369
370
371
372
373 public void addError(AuditEvent event) {
374 errors.add(event);
375 }
376
377
378
379
380
381
382 public List<Throwable> getExceptions() {
383 return Collections.unmodifiableList(exceptions);
384 }
385
386
387
388
389
390
391 public void addException(Throwable throwable) {
392 exceptions.add(throwable);
393 }
394
395 }
396
397 }