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