aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java
diff options
context:
space:
mode:
authorAurimas Liutikas <aurimas@google.com>2017-11-28 20:29:59 -0800
committerAurimas Liutikas <aurimas@google.com>2017-11-28 20:29:59 -0800
commitb4aec831a096b89efed151c9b2c5754d9491e6ea (patch)
treef8c04870544dda72e99139fcc713a66b04514dcc /src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java
parentb8aa773e4886dabd0ffc5026420edd061069f044 (diff)
parentdbbd00f58fd36c7c23e24e6652076b735b8e7c0c (diff)
downloadcheckstyle-b4aec831a096b89efed151c9b2c5754d9491e6ea.tar.gz
Merge Checkstyle 8.5 into aosp/master
Test: None
Diffstat (limited to 'src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java')
-rw-r--r--src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java203
1 files changed, 169 insertions, 34 deletions
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java b/src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java
index f3316f249..0a0095418 100644
--- a/src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/XMLLogger.java
@@ -24,8 +24,13 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.ResourceBundle;
+import java.util.concurrent.ConcurrentHashMap;
import com.puppycrawl.tools.checkstyle.api.AuditEvent;
import com.puppycrawl.tools.checkstyle.api.AuditListener;
@@ -59,27 +64,41 @@ public class XMLLogger
/** Close output stream in auditFinished. */
private final boolean closeStream;
- /** Helper writer that allows easy encoding and printing. */
- private PrintWriter writer;
+ /** The writer lock object. */
+ private final Object writerLock = new Object();
+
+ /** Holds all messages for the given file. */
+ private final Map<String, FileMessages> fileMessages =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Helper writer that allows easy encoding and printing.
+ */
+ private final PrintWriter writer;
/**
* Creates a new {@code XMLLogger} instance.
* Sets the output to a defined stream.
* @param outputStream the stream to write logs to.
* @param closeStream close oS in auditFinished
+ * @deprecated in order to fullfil demands of BooleanParameter IDEA check.
+ * @noinspection BooleanParameter
*/
+ @Deprecated
public XMLLogger(OutputStream outputStream, boolean closeStream) {
- setOutputStream(outputStream);
+ writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
this.closeStream = closeStream;
}
/**
- * Sets the OutputStream.
- * @param outputStream the OutputStream to use
- **/
- private void setOutputStream(OutputStream outputStream) {
- final OutputStreamWriter osw = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
- writer = new PrintWriter(osw);
+ * Creates a new {@code XMLLogger} instance.
+ * Sets the output to a defined stream.
+ * @param outputStream the stream to write logs to.
+ * @param outputStreamOptions if {@code CLOSE} stream should be closed in auditFinished()
+ */
+ public XMLLogger(OutputStream outputStream, OutputStreamOptions outputStreamOptions) {
+ writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
+ closeStream = outputStreamOptions == OutputStreamOptions.CLOSE;
}
@Override
@@ -96,6 +115,8 @@ public class XMLLogger
@Override
public void auditFinished(AuditEvent event) {
+ fileMessages.forEach(this::writeFileMessages);
+
writer.println("</checkstyle>");
if (closeStream) {
writer.close();
@@ -107,35 +128,116 @@ public class XMLLogger
@Override
public void fileStarted(AuditEvent event) {
- writer.println("<file name=\"" + encode(event.getFileName()) + "\">");
+ fileMessages.put(event.getFileName(), new FileMessages());
}
@Override
public void fileFinished(AuditEvent event) {
+ final String fileName = event.getFileName();
+ final FileMessages messages = fileMessages.get(fileName);
+
+ synchronized (writerLock) {
+ writeFileMessages(fileName, messages);
+ }
+
+ fileMessages.remove(fileName);
+ }
+
+ /**
+ * Prints the file section with all file errors and exceptions.
+ * @param fileName The file name, as should be printed in the opening file tag.
+ * @param messages The file messages.
+ */
+ private void writeFileMessages(String fileName, FileMessages messages) {
+ writeFileOpeningTag(fileName);
+ if (messages != null) {
+ for (AuditEvent errorEvent : messages.getErrors()) {
+ writeFileError(errorEvent);
+ }
+ for (Throwable exception : messages.getExceptions()) {
+ writeException(exception);
+ }
+ }
+ writeFileClosingTag();
+ }
+
+ /**
+ * Prints the "file" opening tag with the given filename.
+ * @param fileName The filename to output.
+ */
+ private void writeFileOpeningTag(String fileName) {
+ writer.println("<file name=\"" + encode(fileName) + "\">");
+ }
+
+ /**
+ * Prints the "file" closing tag.
+ */
+ private void writeFileClosingTag() {
writer.println("</file>");
}
@Override
public void addError(AuditEvent event) {
if (event.getSeverityLevel() != SeverityLevel.IGNORE) {
- writer.print("<error" + " line=\"" + event.getLine() + "\"");
- if (event.getColumn() > 0) {
- writer.print(" column=\"" + event.getColumn() + "\"");
+ final String fileName = event.getFileName();
+ if (fileName == null) {
+ synchronized (writerLock) {
+ writeFileError(event);
+ }
+ }
+ else {
+ final FileMessages messages = fileMessages.computeIfAbsent(
+ fileName, name -> new FileMessages());
+ messages.addError(event);
}
- writer.print(" severity=\""
+ }
+ }
+
+ /**
+ * Outputs the given envet to the writer.
+ * @param event An event to print.
+ */
+ private void writeFileError(AuditEvent event) {
+ writer.print("<error" + " line=\"" + event.getLine() + "\"");
+ if (event.getColumn() > 0) {
+ writer.print(" column=\"" + event.getColumn() + "\"");
+ }
+ writer.print(" severity=\""
+ event.getSeverityLevel().getName()
+ "\"");
- writer.print(" message=\""
+ writer.print(" message=\""
+ encode(event.getMessage())
+ "\"");
- writer.println(" source=\""
- + encode(event.getSourceName())
- + "\"/>");
+ writer.print(" source=\"");
+ if (event.getModuleId() == null) {
+ writer.print(encode(event.getSourceName()));
+ }
+ else {
+ writer.print(encode(event.getModuleId()));
}
+ writer.println("\"/>");
}
@Override
public void addException(AuditEvent event, Throwable throwable) {
+ final String fileName = event.getFileName();
+ if (fileName == null) {
+ synchronized (writerLock) {
+ writeException(throwable);
+ }
+ }
+ else {
+ final FileMessages messages = fileMessages.computeIfAbsent(
+ fileName, name -> new FileMessages());
+ messages.addException(throwable);
+ }
+ }
+
+ /**
+ * Writes the exception event to the print writer.
+ * @param throwable The
+ */
+ private void writeException(Throwable throwable) {
final StringWriter stringWriter = new StringWriter();
final PrintWriter printer = new PrintWriter(stringWriter);
printer.println("<exception>");
@@ -152,7 +254,7 @@ public class XMLLogger
* @return the escaped value if necessary.
*/
public static String encode(String value) {
- final StringBuilder sb = new StringBuilder();
+ final StringBuilder sb = new StringBuilder(256);
for (int i = 0; i < value.length(); i++) {
final char chr = value.charAt(i);
switch (chr) {
@@ -169,7 +271,7 @@ public class XMLLogger
sb.append("&quot;");
break;
case '&':
- sb.append(encodeAmpersand(value, i));
+ sb.append("&amp;");
break;
case '\r':
break;
@@ -177,7 +279,16 @@ public class XMLLogger
sb.append("&#10;");
break;
default:
- sb.append(chr);
+ if (Character.isISOControl(chr)) {
+ // true escape characters need '&' before but it also requires XML 1.1
+ // until https://github.com/checkstyle/checkstyle/issues/5168
+ sb.append("#x");
+ sb.append(Integer.toHexString(chr));
+ sb.append(';');
+ }
+ else {
+ sb.append(chr);
+ }
break;
}
}
@@ -226,21 +337,45 @@ public class XMLLogger
}
/**
- * Encodes ampersand in value at required position.
- * @param value string value, which contains ampersand
- * @param ampPosition position of ampersand in value
- * @return encoded ampersand which should be used in xml
+ * The registered file messages.
*/
- private static String encodeAmpersand(String value, int ampPosition) {
- final int nextSemi = value.indexOf(';', ampPosition);
- final String result;
- if (nextSemi == -1
- || !isReference(value.substring(ampPosition, nextSemi + 1))) {
- result = "&amp;";
+ private static class FileMessages {
+ /** The file error events. */
+ private final List<AuditEvent> errors = Collections.synchronizedList(new ArrayList<>());
+
+ /** The file exceptions. */
+ private final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
+
+ /**
+ * Returns the file error events.
+ * @return the file error events.
+ */
+ public List<AuditEvent> getErrors() {
+ return Collections.unmodifiableList(errors);
}
- else {
- result = "&";
+
+ /**
+ * Adds the given error event to the messages.
+ * @param event the error event.
+ */
+ public void addError(AuditEvent event) {
+ errors.add(event);
+ }
+
+ /**
+ * Returns the file exceptions.
+ * @return the file exceptions.
+ */
+ public List<Throwable> getExceptions() {
+ return Collections.unmodifiableList(exceptions);
+ }
+
+ /**
+ * Adds the given exception to the messages.
+ * @param throwable the file exception
+ */
+ public void addException(Throwable throwable) {
+ exceptions.add(throwable);
}
- return result;
}
}