blob: cded0f2a3da8bd0bd160fb78cd0168fabaa30b99 [file] [log] [blame]
/*
* 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.annotations.NonNull;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
import com.android.utils.SdkUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
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 Reporter {
private static final String INDEX_NAME = "index.html";
private final File dir;
protected final LintCliFlags flags;
public MultiProjectHtmlReporter(
@NonNull LintCliClient client, @NonNull File dir, @NonNull LintCliFlags flags) {
super(client, new File(dir, INDEX_NAME));
this.dir = dir;
this.flags = flags;
}
@Override
public void write(@NonNull LintStats stats, List<Warning> allIssues) throws IOException {
Map<Project, List<Warning>> projectToWarnings = new HashMap<>();
for (Warning warning : allIssues) {
List<Warning> list =
projectToWarnings.computeIfAbsent(warning.project, k -> new ArrayList<>());
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);
String lowercase = fileName.toLowerCase(Locale.US);
if (!unique.contains(lowercase)) {
unique.add(lowercase);
break;
}
number++;
}
File output = new File(dir, fileName);
if (output.exists()) {
boolean deleted = output.delete();
if (!deleted) {
client.log(null, "Could not delete old file %1$s", output);
continue;
}
}
if (!output.getParentFile().canWrite()) {
client.log(null, "Cannot write output file %1$s", output);
continue;
}
Reporter reporter = Reporter.createHtmlReporter(client, output, flags);
reporter.setUrlMap(urlMap);
List<Warning> issues = projectToWarnings.get(project);
int projectErrorCount = 0;
int projectWarningCount = 0;
for (Warning warning : issues) {
if (warning.severity.isError()) {
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(stats, issues);
projects.add(
new ProjectEntry(fileName, projectErrorCount, projectWarningCount, relative));
}
// Sort project list in decreasing order of errors, warnings and names
Collections.sort(projects);
Reporter reporter = Reporter.createHtmlReporter(client, output, flags);
reporter.writeProjectList(stats, projects);
if (!client.getFlags().isQuiet()
&& (stats.getErrorCount() > 0 || stats.getWarningCount() > 0)) {
File index = new File(dir, INDEX_NAME);
String url = SdkUtils.fileToUrlString(index.getAbsoluteFile());
System.out.println(String.format("Wrote overview index to %1$s", url));
}
}
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(@NonNull 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);
}
}
}