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 }