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