package hudson.plugins.emma;
import hudson.model.AbstractBuild;
import hudson.model.Api;
import hudson.util.ChartUtil;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.ShiftedCategoryAxis;
import hudson.util.ChartUtil.NumberOnlyBuildLabel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import java.awt.BasicStroke;
import java.awt.Color;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Calendar;
/**
* Base class of all coverage objects.
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public abstract class CoverageObject<SELF extends CoverageObject<SELF>> {
Ratio clazz = new Ratio();
Ratio method = new Ratio();
Ratio block = new Ratio();
Ratio line = new Ratio();
private volatile boolean failed = false;
public boolean isFailed() {
return failed;
}
/**
* Marks this coverage object as failed.
* @see Rule
*/
public void setFailed() {
failed = true;
}
@Exported(inline=true)
public Ratio getClassCoverage() {
return clazz;
}
@Exported(inline=true)
public Ratio getMethodCoverage() {
return method;
}
@Exported(inline=true)
public Ratio getBlockCoverage() {
return block;
}
/**
* Line coverage. Can be null if this information is not collected.
*/
@Exported(inline=true)
public Ratio getLineCoverage() {
return line;
}
/**
* Gets the build object that owns the whole coverage report tree.
*/
public abstract AbstractBuild<?,?> getBuild();
/**
* Gets the corresponding coverage report object in the previous
* run that has the record.
*
* @return
* null if no earlier record was found.
*/
@Exported
public abstract SELF getPreviousResult();
/**
* Used in the view to print out four table columns with the coverage info.
*/
public String printFourCoverageColumns() {
StringBuilder buf = new StringBuilder();
printRatioCell(isFailed(), clazz, buf);
printRatioCell(isFailed(), method, buf);
printRatioCell(isFailed(), block, buf);
printRatioCell(isFailed(), line, buf);
return buf.toString();
}
public boolean hasLineCoverage() {
return line.isInitialized();
}
public boolean hasClassCoverage() {
return clazz.isInitialized();
}
static NumberFormat dataFormat = new DecimalFormat("000.00");
static NumberFormat percentFormat = new DecimalFormat("0.0");
static NumberFormat intFormat = new DecimalFormat("0");
protected static void printRatioCell(boolean failed, Ratio ratio, StringBuilder buf) {
if (ratio != null && ratio.isInitialized()) {
String className = "nowrap" + (failed ? " red" : "");
buf.append("<td class='").append(className).append("'");
buf.append(" data='").append(dataFormat.format(ratio.getPercentageFloat()));
buf.append("'>\n");
printRatioTable(ratio, buf);
buf.append("</td>\n");
}
}
protected static void printRatioTable(Ratio ratio, StringBuilder buf){
String data = dataFormat.format(ratio.getPercentageFloat());
String percent = percentFormat.format(ratio.getPercentageFloat());
String numerator = intFormat.format(ratio.getNumerator());
String denominator = intFormat.format(ratio.getDenominator());
buf.append("<table class='percentgraph'><tr class='percentgraph'>")
.append("<td width='44' data='").append(data).append("'>").append(percent).append("%</td>")
.append("<td class='percentgraph' data='").append(data).append("'>")
.append("<div class='percentgraph'><div class='greenbar' style='width: ").append(ratio.getPercentageFloat()).append("px;'>")
.append("<span class='text'>").append(numerator).append("/").append(denominator)
.append("</span></div></div></td></tr></table>") ;
}
/**
* Generates the graph that shows the coverage trend up to this report.
*/
public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException {
if(ChartUtil.awtProblemCause != null) {
// not available. send out error message
rsp.sendRedirect2(req.getContextPath()+"/images/headless.png");
return;
}
AbstractBuild<?,?> build = getBuild();
Calendar t = build.getTimestamp();
if(req.checkIfModified(t,rsp))
return; // up to date
DataSetBuilder<String,NumberOnlyBuildLabel> dsb = new DataSetBuilder<String,NumberOnlyBuildLabel>();
for( CoverageObject<SELF> a=this; a!=null; a=a.getPreviousResult() ) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(a.getBuild());
dsb.add( a.clazz.getPercentageFloat(), "class", label);
dsb.add( a.block.getPercentageFloat(), "block", label);
dsb.add( a.method.getPercentageFloat(), "method", label);
if(a.line!=null)
dsb.add( a.line.getPercentageFloat(), "line", label);
}
ChartUtil.generateGraph(req,rsp,createChart(dsb.build()),400,200);
}
public Api getApi() {
return new Api(this);
}
private JFreeChart createChart(CategoryDataset dataset) {
final JFreeChart chart = ChartFactory.createLineChart(
null, // chart title
null, // unused
"%", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
final LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.RIGHT);
chart.setBackgroundPaint(Color.white);
final CategoryPlot plot = chart.getCategoryPlot();
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlinePaint(null);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.black);
CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
plot.setDomainAxis(domainAxis);
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setCategoryMargin(0.0);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setUpperBound(100);
rangeAxis.setLowerBound(0);
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setStroke(new BasicStroke(4.0f));
ColorPalette.apply(renderer);
// crop extra space around the graph
plot.setInsets(new RectangleInsets(5.0,0,0,5.0));
return chart;
}
}