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