package hudson.plugins.violations;
import java.lang.ref.WeakReference;
import java.io.IOException;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import hudson.model.HealthReport;
import hudson.model.AbstractBuild;
import hudson.model.Result;
import hudson.model.AbstractProject;
import hudson.model.Project;
/** cannot be used in a slave
import hudson.maven.MavenModule;
/**/
import hudson.plugins.violations.parse.ParseXML;
import hudson.plugins.violations.parse.BuildModelParser;
import hudson.plugins.violations.render.FileModelProxy;
import hudson.plugins.violations.render.NoViolationsFile;
import hudson.plugins.violations.model.BuildModel;
import hudson.plugins.violations.model.FileModel;
import hudson.plugins.violations.model.Suppression;
import hudson.plugins.violations.util.RecurDynamic;
import hudson.plugins.violations.util.HelpHudson;
import hudson.plugins.violations.hudson.AbstractViolationsBuildAction;
/** cannot be used in a slave
import hudson.plugins.violations.hudson.maven.*;
/**/
/**
* This contains the report for the violations
* of a particular build.
*/
public class ViolationsReport
implements Serializable {
private static final Logger LOG
= Logger.getLogger(ViolationsReport.class.getName());
private AbstractBuild<?, ?> build;
private ViolationsConfig config;
private Map<String, Integer> violations = new TreeMap<String, Integer>();
private Map<String, TypeSummary> typeSummaries
= new TreeMap<String, TypeSummary>();
private transient WeakReference<BuildModel> modelReference;
/**
* Set the build.
* @param build the current build.
*/
public void setBuild(AbstractBuild<?, ?> build) {
this.build = build;
}
/**
* Get the build.
* @return the build.
*/
public AbstractBuild<?, ?> getBuild() {
return build;
}
/**
* Set the config.
* @param config the config.
*/
public void setConfig(ViolationsConfig config) {
this.config = config;
}
/**
* Get the config.
* @return the config.
*/
public ViolationsConfig getConfig() {
return config;
}
/**
* Get the violation counts for the build.
* @return a map of type to count.
*/
public Map<String, Integer> getViolations() {
return violations;
}
/**
* Get the overall health for the build.
* @return the health report, null if there are no counts.
*/
public HealthReport getBuildHealth() {
List<HealthReport> reports = getBuildHealths();
HealthReport ret = null;
for (HealthReport report: reports) {
ret = HealthReport.min(ret, report);
}
return ret;
}
/**
* Get a health report for each type.
* @return a list of health reports.
*/
public List<HealthReport> getBuildHealths() {
List<HealthReport> ret = new ArrayList<HealthReport>();
for (String type: config.getTypeConfigs().keySet()) {
HealthReport health = getHealthReportFor(type);
if (health != null) {
ret.add(health);
}
}
return ret;
}
/**
* Get the health for a particulat type.
* @param type the type to get the health for.
* @return the health report.
*/
public HealthReport getHealthReportFor(String type) {
Integer count = violations.get(type);
if (count == null || config.getTypeConfigs() == null) {
return null;
}
int h =
config.getTypeConfigs().get(type).getHealthFor(count);
if (h < 0) {
return new HealthReport(
0, Messages._ViolationsReport_NoReport(type));
} else {
return new HealthReport(
h, Messages._ViolationsReport_ViolationsCount(type, count));
}
}
/**
* Get the detailed model for the build.
* This is lazily build from an xml created during publisher action.
* @return the build model.
*/
public BuildModel getModel() {
BuildModel model = null;
if (modelReference != null) {
model = modelReference.get();
if (model != null) {
return model;
}
}
File xmlFile = new File(
build.getRootDir(),
MagicNames.VIOLATIONS + "/" + MagicNames.VIOLATIONS + ".xml");
try {
model = new BuildModel(xmlFile);
ParseXML.parse(
xmlFile, new BuildModelParser().buildModel(model));
} catch (Exception ex) {
LOG.log(Level.WARNING, "Unable to parse " + xmlFile, ex);
return null;
}
modelReference = new WeakReference<BuildModel>(model);
return model;
}
/**
* Get the file model proxt for a file name.
* @param name the name to use.
* @return the file model proxy.
*/
public FileModelProxy getFileModelProxy(
String name) {
BuildModel model = getModel();
if (model == null) {
return null;
}
return model.getFileModelMap().get(name);
}
/**
* This gets called to display a particular violation file report.
* @param token the current token in the path being parsed.
* @param req the http/stapler request.
* @param rsp the http/stapler response.
* @return an object to handle the token.
*/
public Object getDynamic(
String token, StaplerRequest req, StaplerResponse rsp) {
//System.out.println("LOOKING for " + req.getRestOfPath());
String name = req.getRestOfPath();
if (name.equals("")) {
return null;
}
if (name.startsWith("/")) {
name = name.substring(1);
}
FileModelProxy proxy = getFileModelProxy(name);
if (proxy != null) {
return new RecurDynamic(
"", name, proxy.build(build).contextPath(req.getContextPath()));
} else {
return new RecurDynamic(
"", name, new NoViolationsFile(name, build));
}
}
/**
* get the configuration for this job.
* @return the configuration of the job.
*/
public ViolationsConfig getLiveConfig() {
AbstractProject<?, ?> abstractProject = build.getProject();
AbstractProject notQuestion = (AbstractProject) abstractProject;
if (abstractProject instanceof Project) {
Project project = (Project) abstractProject;
ViolationsPublisher publisher
= (ViolationsPublisher) project.getPublisher(
ViolationsPublisher.DESCRIPTOR);
return publisher == null
? null
: publisher.getConfig();
/** Remove the following as it cannot be uesed in a slave
} else if (notQuestion instanceof MavenModule) {
MavenModule mavenModule = (MavenModule) notQuestion;
ViolationsMavenReporter reporter
= (ViolationsMavenReporter) mavenModule.getReporters().
get(ViolationsMavenReporter.DESCRIPTOR);
return reporter == null
? null
: reporter.getConfig();
/**/
}
return null;
}
/**
* Add a suppression to the set of suppressions.
* @param suppression the suppression to add.
*/
public void addSuppression(Suppression suppression)
throws IOException {
ViolationsConfig config = getLiveConfig();
if (config != null) {
config.getSuppressions().add(suppression);
((AbstractProject)build.getParent()).save();
}
}
/**
* Remove a suppression to the set of suppressions.
* @param suppression the suppression to remove.
*/
public void removeSuppression(Suppression suppression)
throws IOException {
ViolationsConfig config = getLiveConfig();
if (config != null) {
config.getSuppressions().remove(suppression);
((AbstractProject)build.getParent()).save();
}
}
/**
* Get a map of type to type summary report.
* @return a map.
*/
public Map<String, TypeSummary> getTypeSummaries() {
return typeSummaries;
}
/**
* Get a type summary for a particular type.
* @param type the violation type.
* @return the type summary.
*/
public TypeSummary getTypeSummary(String type) {
TypeSummary ret = typeSummaries.get(type);
if (ret == null) {
ret = new TypeSummary();
typeSummaries.put(type, ret);
}
return ret;
}
/**
* Get a map of type to type reports.
* @return a map of type to type reports.
*/
public Map<String, TypeReport> getTypeReports() {
Map<String, TypeReport> ret =
new TreeMap<String, TypeReport>();
for (String t: violations.keySet()) {
int c = violations.get(t);
HealthReport health = getHealthReportFor(t);
ret.put(
t,
new TypeReport(t, health.getIconUrl(), c));
}
return ret;
}
/**
* Graph this report.
* Note that for some reason, yet unknown, hudson seems
* to pick an in memory ViolationsReport object and
* not the report for the build.
* (Reason may be related to the fact that serialized builds may not be
* the same as in-memory builds).
* Need to find the correct build from the URI.
* @param req the request paramters
* @param rsp the response.
* @throws IOException if there is an error writing the graph.
*/
public void doGraph(StaplerRequest req, StaplerResponse rsp)
throws IOException {
AbstractBuild<?, ?> tBuild = build;
int buildNumber = HelpHudson.findBuildNumber(req);
if (buildNumber != 0) {
tBuild = (AbstractBuild<?, ?>) build.getParent().getBuildByNumber(buildNumber);
if (tBuild == null) {
tBuild = build;
}
}
AbstractViolationsBuildAction r
= tBuild.getAction(AbstractViolationsBuildAction.class);
if (r == null) {
return;
}
r.doGraph(req, rsp);
}
/**
* Get the string number for a particular type.
* @param t the type
* @return the string - a number for a value type,
* "" for not found and "No reports" for
* < 0.
*/
public String getNumberString(String t) {
Integer v = violations.get(t);
if (v == null) {
return "";
}
if (v < 0) {
return "<span style='color:red'>No reports</span>";
}
return "" + v;
}
/**
* Get the icon for a type.
* @param t the type
* @return the icon name.
*/
public String getIcon(String t) {
Integer v = violations.get(t);
HealthReport h = getHealthReportFor(t);
if (h == null) {
return null;
}
return h.getIconUrl();
}
/**
* Report class for a particular type.
*/
public class TypeReport {
private final String type;
private final String icon;
private final int number;
/**
* Create the report class for a type.
* @param type the violation type.
* @param icon the health icon to display.
* @param number the number of violations.
*/
public TypeReport(String type, String icon, int number) {
this.type = type;
this.icon = icon;
this.number = number;
}
/**
* Get the violation type.
* @return the violation type.
*/
public String getType() {
return type;
}
/**
* Get the health icon to display.
* @return the health icon.
*/
public String getIcon() {
return icon;
}
/**
* Get the number of violations.
* @return the number.
*/
public int getNumber() {
return number;
}
/**
* Get the number of violations as a string.
* @return the number if >= 0 othersise an error string.
*/
public String getNumberString() {
if (number >= 0) {
return "" + number;
} else {
return "<span style='color:red'>No reports</span>";
}
}
}
/**
* Get the previous ViolationsReport
* @return the previous report if present, null otherwise.
*/
public ViolationsReport previous() {
return findViolationsReport(build.getPreviousBuild());
}
/**
* Get the number of violations for a particular type.
* @param type the violation type.
* @return the number of violations.
*/
public int typeCount(String type) {
if (getModel() == null) {
return 0;
}
return getModel().getTypeCountMap().get(type).getCount();
}
/**
* Get the number of files in violation for a particular type.
* @param type the violation type.
* @return the number of files.
*/
public int fileCount(String type) {
if (getModel() == null) {
return 0;
}
return getModel().getTypeCountMap().get(type).getNumberFiles();
}
/**
* Get the number of violations of a type for a file.
* @param type the type in question.
* @param filename the name of the file.
* @return the number found (0 for none and for failures).
*/
public int violationCount(String type, String filename) {
FileModelProxy proxy = getFileModelProxy(filename);
if (proxy == null) {
return 0;
}
FileModel fileModel = proxy.getFileModel();
if (fileModel == null) {
return 0;
}
FileModel.LimitType limit = fileModel.getLimitTypeMap().get(type);
if (limit == null) {
return 0;
}
return limit.getNumber();
}
/**
* Get the unstable status for this report.
* @return true if one of the violations equals or exceed the
* unstable threshold for that violations type.
*/
private boolean isUnstable() {
for (String t: violations.keySet()) {
int count = violations.get(t);
Integer unstableLimit
= config.getTypeConfigs().get(t).getUnstable();
if (unstableLimit == null) {
continue;
}
if (count >= unstableLimit) {
return true;
}
}
return false;
}
/**
* Get the failed status for this report.
* @return true if one of the violations equals or exceed the
* failed threshold of that violations type.
*/
private boolean isFailed() {
for (String t: violations.keySet()) {
int count = violations.get(t);
Integer failCount = config.getTypeConfigs().get(t).getFail();
if (failCount == null) {
continue;
}
if (count >= failCount) {
return true;
}
}
return false;
}
/**
* Set the unstable/failed status of a build based
* on this violations report.
*/
public void setBuildResult() {
if (isFailed()) {
build.setResult(Result.FAILURE);
return;
}
if (isUnstable()) {
build.setResult(Result.UNSTABLE);
}
}
private static final long serialVersionUID = 1L;
public static ViolationsReport findViolationsReport(
AbstractBuild<?, ?> b) {
for (; b != null; b = b.getPreviousBuild()) {
if (b.getResult().isWorseOrEqualTo(Result.FAILURE)) {
continue;
}
AbstractViolationsBuildAction action
= b.getAction(AbstractViolationsBuildAction.class);
if (action == null || action.getReport() == null) {
continue;
}
ViolationsReport ret = action.getReport();
ret.setBuild(b);
return ret;
}
return null;
}
public static ViolationsReportIterator iteration(
AbstractBuild<?, ?> build) {
return new ViolationsReportIterator(build);
}
public static class ViolationsReportIterator
implements Iterator<ViolationsReport>, Iterable<ViolationsReport> {
private AbstractBuild<?, ?> curr;
public ViolationsReportIterator(AbstractBuild<?, ?> curr) {
this.curr = curr;
}
@Override
public Iterator<ViolationsReport> iterator() {
return this;
}
public boolean hasNext() {
return findViolationsReport(curr) != null;
}
public ViolationsReport next() {
ViolationsReport ret = findViolationsReport(curr);
if (ret != null) {
curr = ret.getBuild().getPreviousBuild();
}
return ret;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}