| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tools.lint; |
| |
| import static com.android.tools.lint.detector.api.TextFormat.RAW; |
| |
| import com.android.tools.lint.checks.BuiltinIssueRegistry; |
| import com.android.tools.lint.detector.api.Issue; |
| import com.android.tools.lint.detector.api.Location; |
| import com.android.tools.lint.detector.api.Position; |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.Charsets; |
| import com.google.common.base.Joiner; |
| import com.google.common.io.Files; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.List; |
| |
| /** |
| * A reporter which emits lint results into an XML report. |
| * <p> |
| * <b>NOTE: This is not a public or final API; if you rely on this be prepared |
| * to adjust your code for the next tools release.</b> |
| */ |
| @Beta |
| public class XmlReporter extends Reporter { |
| private final Writer mWriter; |
| |
| /** |
| * Constructs a new {@link XmlReporter} |
| * |
| * @param client the client |
| * @param output the output file |
| * @throws IOException if an error occurs |
| */ |
| public XmlReporter(LintCliClient client, File output) throws IOException { |
| super(client, output); |
| mWriter = new BufferedWriter(Files.newWriter(output, Charsets.UTF_8)); |
| } |
| |
| @Override |
| public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException { |
| mWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //$NON-NLS-1$ |
| // Format 4: added urls= attribute with all more info links, comma separated |
| mWriter.write("<issues format=\"4\""); //$NON-NLS-1$ |
| String revision = mClient.getRevision(); |
| if (revision != null) { |
| mWriter.write(String.format(" by=\"lint %1$s\"", revision)); //$NON-NLS-1$ |
| } |
| mWriter.write(">\n"); //$NON-NLS-1$ |
| |
| if (!issues.isEmpty()) { |
| for (Warning warning : issues) { |
| mWriter.write('\n'); |
| indent(mWriter, 1); |
| mWriter.write("<issue"); //$NON-NLS-1$ |
| Issue issue = warning.issue; |
| writeAttribute(mWriter, 2, "id", issue.getId()); //$NON-NLS-1$ |
| writeAttribute(mWriter, 2, "severity", |
| warning.severity.getDescription()); |
| writeAttribute(mWriter, 2, "message", warning.message); //$NON-NLS-1$ |
| |
| writeAttribute(mWriter, 2, "category", //$NON-NLS-1$ |
| issue.getCategory().getFullName()); |
| writeAttribute(mWriter, 2, "priority", //$NON-NLS-1$ |
| Integer.toString(issue.getPriority())); |
| writeAttribute(mWriter, 2, "summary", issue.getBriefDescription(RAW));//$NON-NLS-1$ |
| writeAttribute(mWriter, 2, "explanation", issue.getExplanation(RAW)); //$NON-NLS-1$ |
| List<String> moreInfo = issue.getMoreInfo(); |
| if (!moreInfo.isEmpty()) { |
| // Compatibility with old format: list first URL |
| writeAttribute(mWriter, 2, "url", moreInfo.get(0)); //$NON-NLS-1$ |
| writeAttribute(mWriter, 2, "urls", //$NON-NLS-1$ |
| Joiner.on(',').join(issue.getMoreInfo())); |
| } |
| if (warning.errorLine != null && !warning.errorLine.isEmpty()) { |
| String line = warning.errorLine; |
| int index1 = line.indexOf('\n'); |
| if (index1 != -1) { |
| int index2 = line.indexOf('\n', index1 + 1); |
| if (index2 != -1) { |
| String line1 = line.substring(0, index1); |
| String line2 = line.substring(index1 + 1, index2); |
| writeAttribute(mWriter, 2, "errorLine1", line1); //$NON-NLS-1$ |
| writeAttribute(mWriter, 2, "errorLine2", line2); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| if (warning.isVariantSpecific()) { |
| writeAttribute(mWriter, 2, "includedVariants", Joiner.on(',').join(warning.getIncludedVariantNames())); |
| writeAttribute(mWriter, 2, "excludedVariants", Joiner.on(',').join(warning.getExcludedVariantNames())); |
| } |
| |
| if (mClient.getRegistry() instanceof BuiltinIssueRegistry) { |
| boolean adt = QuickfixHandler.ADT.hasAutoFix(issue); |
| boolean studio = QuickfixHandler.STUDIO.hasAutoFix(issue); |
| if (adt || studio) { //$NON-NLS-1$ |
| String value = adt && studio ? "studio,adt" : studio ? "studio" : "adt"; |
| writeAttribute(mWriter, 2, "quickfix", value); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| assert (warning.file != null) == (warning.location != null); |
| |
| if (warning.file != null) { |
| assert warning.location.getFile() == warning.file; |
| } |
| |
| Location location = warning.location; |
| if (location != null) { |
| mWriter.write(">\n"); //$NON-NLS-1$ |
| while (location != null) { |
| indent(mWriter, 2); |
| mWriter.write("<location"); //$NON-NLS-1$ |
| String path = mClient.getDisplayPath(warning.project, location.getFile()); |
| writeAttribute(mWriter, 3, "file", path); //$NON-NLS-1$ |
| Position start = location.getStart(); |
| if (start != null) { |
| int line = start.getLine(); |
| int column = start.getColumn(); |
| if (line >= 0) { |
| // +1: Line numbers internally are 0-based, report should be |
| // 1-based. |
| writeAttribute(mWriter, 3, "line", //$NON-NLS-1$ |
| Integer.toString(line + 1)); |
| if (column >= 0) { |
| writeAttribute(mWriter, 3, "column", //$NON-NLS-1$ |
| Integer.toString(column + 1)); |
| } |
| } |
| } |
| |
| mWriter.write("/>\n"); //$NON-NLS-1$ |
| location = location.getSecondary(); |
| } |
| indent(mWriter, 1); |
| mWriter.write("</issue>\n"); //$NON-NLS-1$ |
| } else { |
| mWriter.write('\n'); |
| indent(mWriter, 1); |
| mWriter.write("/>\n"); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| mWriter.write("\n</issues>\n"); //$NON-NLS-1$ |
| mWriter.close(); |
| |
| if (!mClient.getFlags().isQuiet() |
| && (mDisplayEmpty || errorCount > 0 || warningCount > 0)) { |
| String path = mOutput.getAbsolutePath(); |
| System.out.println(String.format("Wrote XML report to %1$s", path)); |
| } |
| } |
| |
| private static void writeAttribute(Writer writer, int indent, String name, String value) |
| throws IOException { |
| writer.write('\n'); |
| indent(writer, indent); |
| writer.write(name); |
| writer.write('='); |
| writer.write('"'); |
| for (int i = 0, n = value.length(); i < n; i++) { |
| char c = value.charAt(i); |
| switch (c) { |
| case '"': |
| writer.write("""); //$NON-NLS-1$ |
| break; |
| case '\'': |
| writer.write("'"); //$NON-NLS-1$ |
| break; |
| case '&': |
| writer.write("&"); //$NON-NLS-1$ |
| break; |
| case '<': |
| writer.write("<"); //$NON-NLS-1$ |
| break; |
| default: |
| writer.write(c); |
| break; |
| } |
| } |
| writer.write('"'); |
| } |
| |
| private static void indent(Writer writer, int indent) throws IOException { |
| for (int level = 0; level < indent; level++) { |
| writer.write(" "); //$NON-NLS-1$ |
| } |
| } |
| } |