| /* |
| * 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 com.android.tools.lint.detector.api.Project; |
| import com.android.tools.lint.detector.api.Severity; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.io.Closeables; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * "Multiplexing" reporter which allows output to be split up into a separate |
| * report for each separate project. It also adds an overview index. |
| */ |
| public class MultiProjectHtmlReporter extends HtmlReporter { |
| private static final String INDEX_NAME = "index.html"; //$NON-NLS-1$ |
| private final File mDir; |
| |
| public MultiProjectHtmlReporter(LintCliClient client, File dir) throws IOException { |
| super(client, new File(dir, INDEX_NAME)); |
| mDir = dir; |
| } |
| |
| @Override |
| public void write(int errorCount, int warningCount, List<Warning> allIssues) throws IOException { |
| Map<Project, List<Warning>> projectToWarnings = new HashMap<Project, List<Warning>>(); |
| for (Warning warning : allIssues) { |
| List<Warning> list = projectToWarnings.get(warning.project); |
| if (list == null) { |
| list = new ArrayList<Warning>(); |
| projectToWarnings.put(warning.project, list); |
| } |
| list.add(warning); |
| } |
| |
| |
| // Set of unique file names: lowercase names to avoid case conflicts in web environment |
| Set<String> unique = Sets.newHashSet(); |
| unique.add(INDEX_NAME.toLowerCase(Locale.US)); |
| List<ProjectEntry> projects = Lists.newArrayList(); |
| |
| for (Project project : projectToWarnings.keySet()) { |
| // TODO: Can I get the project name from the Android manifest file instead? |
| String projectName = project.getName(); |
| |
| // Produce file names of the form Project.html, Project1.html, Project2.html, etc |
| int number = 1; |
| String fileName; |
| while (true) { |
| String numberString = number > 1 ? Integer.toString(number) : ""; |
| fileName = String.format("%1$s%2$s.html", projectName, numberString); //$NON-NLS-1$ |
| String lowercase = fileName.toLowerCase(Locale.US); |
| if (!unique.contains(lowercase)) { |
| unique.add(lowercase); |
| break; |
| } |
| number++; |
| } |
| |
| File output = new File(mDir, fileName); |
| if (output.exists()) { |
| boolean deleted = output.delete(); |
| if (!deleted) { |
| mClient.log(null, "Could not delete old file %1$s", output); |
| continue; |
| } |
| } |
| if (!output.getParentFile().canWrite()) { |
| mClient.log(null, "Cannot write output file %1$s", output); |
| continue; |
| } |
| HtmlReporter reporter = new HtmlReporter(mClient, output); |
| reporter.setBundleResources(mBundleResources); |
| reporter.setSimpleFormat(mSimpleFormat); |
| reporter.setUrlMap(mUrlMap); |
| |
| List<Warning> issues = projectToWarnings.get(project); |
| int projectErrorCount = 0; |
| int projectWarningCount = 0; |
| for (Warning warning: issues) { |
| if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) { |
| projectErrorCount++; |
| } else if (warning.severity == Severity.WARNING) { |
| projectWarningCount++; |
| } |
| } |
| |
| String prefix = project.getReferenceDir().getPath(); |
| String path = project.getDir().getPath(); |
| String relative; |
| if (path.startsWith(prefix) && path.length() > prefix.length()) { |
| int i = prefix.length(); |
| if (path.charAt(i) == File.separatorChar) { |
| i++; |
| } |
| relative = path.substring(i); |
| } else { |
| relative = projectName; |
| } |
| reporter.setTitle(String.format("Lint Report for %1$s", relative)); |
| reporter.setStripPrefix(relative); |
| reporter.write(projectErrorCount, projectWarningCount, issues); |
| |
| projects.add(new ProjectEntry(fileName, projectErrorCount, projectWarningCount, |
| relative)); |
| } |
| |
| // Write overview index? |
| writeOverview(errorCount, warningCount, projects); |
| Closeables.closeQuietly(mWriter); |
| |
| File index = new File(mDir, INDEX_NAME); |
| System.out.println(); |
| System.out.println(String.format("Wrote overview index to %1$s", index)); |
| } |
| |
| private void writeOverview(int errorCount, int warningCount, List<ProjectEntry> projects) |
| throws IOException { |
| mWriter.write( |
| "<html>\n" + //$NON-NLS-1$ |
| "<head>\n" + //$NON-NLS-1$ |
| "<title>" + mTitle + "</title>\n"); //$NON-NLS-1$//$NON-NLS-2$ |
| writeStyleSheet(); |
| mWriter.write( |
| "</head>\n" + //$NON-NLS-1$ |
| "<body>\n" + //$NON-NLS-1$ |
| "<h1>" + //$NON-NLS-1$ |
| mTitle + |
| "<div class=\"titleSeparator\"></div>\n" + //$NON-NLS-1$ |
| "</h1>"); //$NON-NLS-1$ |
| |
| |
| // Sort project list in decreasing order of errors, warnings and names |
| Collections.sort(projects); |
| |
| mWriter.write(String.format("Check performed at %1$s.", |
| new Date().toString())); |
| mWriter.write("<br/>"); //$NON-NLS-1$ |
| mWriter.write(String.format("%1$d errors and %2$d warnings found:\n", |
| errorCount, warningCount)); |
| |
| mWriter.write("<br/><br/>"); //$NON-NLS-1$ |
| |
| if (errorCount == 0 && warningCount == 0) { |
| mWriter.write("Congratulations!"); |
| return; |
| } |
| |
| String errorUrl = null; |
| String warningUrl = null; |
| if (!mSimpleFormat) { |
| errorUrl = addLocalResources(HtmlReporter.getErrorIconUrl()); |
| warningUrl = addLocalResources(HtmlReporter.getWarningIconUrl()); |
| } |
| |
| mWriter.write("<table class=\"overview\">\n"); //$NON-NLS-1$ |
| mWriter.write("<tr><th>"); //$NON-NLS-1$ |
| mWriter.write("Project"); |
| mWriter.write("</th><th class=\"countColumn\">"); //$NON-NLS-1$ |
| |
| if (errorUrl != null) { |
| mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$ |
| mWriter.write(errorUrl); |
| mWriter.write("\" />\n"); //$NON-NLS-1$ |
| } |
| mWriter.write("Errors"); |
| mWriter.write("</th><th class=\"countColumn\">"); //$NON-NLS-1$ |
| |
| if (warningUrl != null) { |
| mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$ |
| mWriter.write(warningUrl); |
| mWriter.write("\" />\n"); //$NON-NLS-1$ |
| } |
| mWriter.write("Warnings"); |
| mWriter.write("</th></tr>\n"); //$NON-NLS-1$ |
| |
| for (ProjectEntry entry : projects) { |
| mWriter.write("<tr><td>"); //$NON-NLS-1$ |
| mWriter.write("<a href=\""); |
| mWriter.write(entry.fileName); // TODO: Escape? |
| mWriter.write("\">"); //$NON-NLS-1$ |
| mWriter.write(entry.path); |
| mWriter.write("</a></td><td class=\"countColumn\">"); //$NON-NLS-1$ |
| mWriter.write(Integer.toString(entry.errorCount)); |
| mWriter.write("</td><td class=\"countColumn\">"); //$NON-NLS-1$ |
| mWriter.write(Integer.toString(entry.warningCount)); |
| mWriter.write("</td></tr>\n"); //$NON-NLS-1$ |
| } |
| mWriter.write("</table>\n"); //$NON-NLS-1$ |
| } |
| |
| private static class ProjectEntry implements Comparable<ProjectEntry> { |
| public final int errorCount; |
| public final int warningCount; |
| public final String fileName; |
| public final String path; |
| |
| |
| public ProjectEntry(String fileName, int errorCount, int warningCount, String path) { |
| this.fileName = fileName; |
| this.errorCount = errorCount; |
| this.warningCount = warningCount; |
| this.path = path; |
| } |
| |
| @Override |
| public int compareTo(ProjectEntry other) { |
| int delta = other.errorCount - errorCount; |
| if (delta != 0) { |
| return delta; |
| } |
| |
| delta = other.warningCount - warningCount; |
| if (delta != 0) { |
| return delta; |
| } |
| |
| return path.compareTo(other.path); |
| } |
| } |
| } |