1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.tools.lint; 18 19import com.android.tools.lint.checks.BuiltinIssueRegistry; 20import com.android.tools.lint.detector.api.Issue; 21import com.android.tools.lint.detector.api.Location; 22import com.android.tools.lint.detector.api.Position; 23import com.google.common.annotations.Beta; 24import com.google.common.base.Charsets; 25import com.google.common.io.Files; 26 27import java.io.BufferedWriter; 28import java.io.File; 29import java.io.IOException; 30import java.io.Writer; 31import java.util.List; 32 33/** 34 * A reporter which emits lint results into an XML report. 35 * <p> 36 * <b>NOTE: This is not a public or final API; if you rely on this be prepared 37 * to adjust your code for the next tools release.</b> 38 */ 39@Beta 40public class XmlReporter extends Reporter { 41 private final Writer mWriter; 42 43 /** 44 * Constructs a new {@link XmlReporter} 45 * 46 * @param client the client 47 * @param output the output file 48 * @throws IOException if an error occurs 49 */ 50 public XmlReporter(Main client, File output) throws IOException { 51 super(client, output); 52 mWriter = new BufferedWriter(Files.newWriter(output, Charsets.UTF_8)); 53 } 54 55 @Override 56 public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException { 57 mWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //$NON-NLS-1$ 58 mWriter.write("<issues format=\"3\""); //$NON-NLS-1$ 59 String revision = mClient.getRevision(); 60 if (revision != null) { 61 mWriter.write(String.format(" by=\"lint %1$s\"", revision)); //$NON-NLS-1$ 62 } 63 mWriter.write(">\n"); //$NON-NLS-1$ 64 65 if (issues.size() > 0) { 66 for (Warning warning : issues) { 67 mWriter.write('\n'); 68 indent(mWriter, 1); 69 mWriter.write("<issue"); //$NON-NLS-1$ 70 Issue issue = warning.issue; 71 writeAttribute(mWriter, 2, "id", issue.getId()); //$NON-NLS-1$ 72 writeAttribute(mWriter, 2, "severity", 73 warning.severity.getDescription()); 74 writeAttribute(mWriter, 2, "message", warning.message); //$NON-NLS-1$ 75 76 writeAttribute(mWriter, 2, "category", //$NON-NLS-1$ 77 issue.getCategory().getFullName()); 78 writeAttribute(mWriter, 2, "priority", //$NON-NLS-1$ 79 Integer.toString(issue.getPriority())); 80 writeAttribute(mWriter, 2, "summary", issue.getDescription()); //$NON-NLS-1$ 81 writeAttribute(mWriter, 2, "explanation", issue.getExplanation()); //$NON-NLS-1$ 82 if (issue.getMoreInfo() != null) { 83 writeAttribute(mWriter, 2, "url", issue.getMoreInfo()); //$NON-NLS-1$ 84 } 85 if (warning.errorLine != null && !warning.errorLine.isEmpty()) { 86 String line = warning.errorLine; 87 int index1 = line.indexOf('\n'); 88 if (index1 != -1) { 89 int index2 = line.indexOf('\n', index1 + 1); 90 if (index2 != -1) { 91 String line1 = line.substring(0, index1); 92 String line2 = line.substring(index1 + 1, index2); 93 writeAttribute(mWriter, 2, "errorLine1", line1); //$NON-NLS-1$ 94 writeAttribute(mWriter, 2, "errorLine2", line2); //$NON-NLS-1$ 95 } 96 } 97 } 98 if (mClient.getRegistry() instanceof BuiltinIssueRegistry && 99 ((BuiltinIssueRegistry) mClient.getRegistry()).hasAutoFix( 100 "adt", issue)) { //$NON-NLS-1$ 101 writeAttribute(mWriter, 2, "quickfix", "adt"); //$NON-NLS-1$ //$NON-NLS-2$ 102 } 103 104 assert (warning.file != null) == (warning.location != null); 105 106 if (warning.file != null) { 107 assert warning.location.getFile() == warning.file; 108 } 109 110 Location location = warning.location; 111 if (location != null) { 112 mWriter.write(">\n"); //$NON-NLS-1$ 113 while (location != null) { 114 indent(mWriter, 2); 115 mWriter.write("<location"); //$NON-NLS-1$ 116 String path = mClient.getDisplayPath(warning.project, location.getFile()); 117 writeAttribute(mWriter, 3, "file", path); //$NON-NLS-1$ 118 Position start = location.getStart(); 119 if (start != null) { 120 int line = start.getLine(); 121 int column = start.getColumn(); 122 if (line >= 0) { 123 // +1: Line numbers internally are 0-based, report should be 124 // 1-based. 125 writeAttribute(mWriter, 3, "line", //$NON-NLS-1$ 126 Integer.toString(line + 1)); 127 if (column >= 0) { 128 writeAttribute(mWriter, 3, "column", //$NON-NLS-1$ 129 Integer.toString(column + 1)); 130 } 131 } 132 } 133 134 mWriter.write("/>\n"); //$NON-NLS-1$ 135 location = location.getSecondary(); 136 } 137 indent(mWriter, 1); 138 mWriter.write("</issue>\n"); //$NON-NLS-1$ 139 } else { 140 mWriter.write('\n'); 141 indent(mWriter, 1); 142 mWriter.write("/>\n"); //$NON-NLS-1$ 143 } 144 } 145 } 146 147 mWriter.write("\n</issues>\n"); //$NON-NLS-1$ 148 mWriter.close(); 149 150 String path = mOutput.getAbsolutePath(); 151 System.out.println(String.format("Wrote XML report to %1$s", path)); 152 } 153 154 private static void writeAttribute(Writer writer, int indent, String name, String value) 155 throws IOException { 156 writer.write('\n'); 157 indent(writer, indent); 158 writer.write(name); 159 writer.write('='); 160 writer.write('"'); 161 for (int i = 0, n = value.length(); i < n; i++) { 162 char c = value.charAt(i); 163 switch (c) { 164 case '"': 165 writer.write("""); //$NON-NLS-1$ 166 break; 167 case '\'': 168 writer.write("'"); //$NON-NLS-1$ 169 break; 170 case '&': 171 writer.write("&"); //$NON-NLS-1$ 172 break; 173 case '<': 174 writer.write("<"); //$NON-NLS-1$ 175 break; 176 default: 177 writer.write(c); 178 break; 179 } 180 } 181 writer.write('"'); 182 } 183 184 private static void indent(Writer writer, int indent) throws IOException { 185 for (int level = 0; level < indent; level++) { 186 writer.write(" "); //$NON-NLS-1$ 187 } 188 } 189}