From 048fc0c4c0564d4cd56315349c553de2710562ff Mon Sep 17 00:00:00 2001 From: kohsuke Date: Tue, 11 Aug 2009 00:44:30 +0000 Subject: [PATCH] introduced the Graph class to improve the OO-ness of the model classes. This should also improve testability a bit. git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@20611 71c3de6d-444a-0410-be80-ed276b4c234a --- core/src/main/java/hudson/model/Job.java | 256 ++++++++-------- .../main/java/hudson/tasks/junit/History.java | 277 ++++++++++-------- core/src/main/java/hudson/util/ChartUtil.java | 93 ++---- core/src/main/java/hudson/util/Graph.java | 162 ++++++++++ .../hudson/model/Job/buildTimeTrend.jelly | 2 +- .../hudson/tasks/junit/History/index.jelly | 8 +- 6 files changed, 460 insertions(+), 338 deletions(-) create mode 100644 core/src/main/java/hudson/util/Graph.java diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 020b0efd38..a54a5264cf 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -49,6 +49,7 @@ import hudson.util.RunList; import hudson.util.ShiftedCategoryAxis; import hudson.util.StackedAreaRenderer2; import hudson.util.TextFile; +import hudson.util.Graph; import hudson.widgets.HistoryWidget; import hudson.widgets.Widget; import hudson.widgets.HistoryWidget.Adapter; @@ -68,7 +69,6 @@ import java.util.Map; import java.util.SortedMap; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; @@ -1028,162 +1028,136 @@ public abstract class Job, RunT extends Run { + final Run run; - /** - * Returns the clickable map for the build time graph. Loaded lazily by - * AJAX. - */ - public void doBuildTimeGraphMap(StaplerRequest req, StaplerResponse rsp) - throws IOException { - if (getLastBuild() == null) { - rsp.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - if (req.checkIfModified(getLastBuild().getTimestamp(), rsp)) - return; - ChartUtil.generateClickableMap(req, rsp, createBuildTimeTrendChart(), - 500, 400); - } + public ChartLabel(Run r) { + this.run = r; + } - private JFreeChart createBuildTimeTrendChart() { - class ChartLabel implements Comparable { - final Run run; + public int compareTo(ChartLabel that) { + return this.run.number - that.run.number; + } - public ChartLabel(Run r) { - this.run = r; - } + public boolean equals(Object o) { + // HUDSON-2682 workaround for Eclipse compilation bug + // on (c instanceof ChartLabel) + if (o == null || !ChartLabel.class.isAssignableFrom( o.getClass() )) { + return false; + } + ChartLabel that = (ChartLabel) o; + return run == that.run; + } - public int compareTo(ChartLabel that) { - return this.run.number - that.run.number; - } + public Color getColor() { + // TODO: consider gradation. See + // http://www.javadrive.jp/java2d/shape/index9.html + Result r = run.getResult(); + if (r == Result.FAILURE) + return ColorPalette.RED; + else if (r == Result.UNSTABLE) + return ColorPalette.YELLOW; + else if (r == Result.ABORTED || r == Result.NOT_BUILT) + return ColorPalette.GREY; + else + return ColorPalette.BLUE; + } - public boolean equals(Object o) { - // HUDSON-2682 workaround for Eclipse compilation bug - // on (c instanceof ChartLabel) - if (o == null || !ChartLabel.class.isAssignableFrom( o.getClass() )) { - return false; - } - ChartLabel that = (ChartLabel) o; - return run == that.run; - } + public int hashCode() { + return run.hashCode(); + } - public Color getColor() { - // TODO: consider gradation. See - // http://www.javadrive.jp/java2d/shape/index9.html - Result r = run.getResult(); - if (r == Result.FAILURE) - return ColorPalette.RED; - else if (r == Result.UNSTABLE) - return ColorPalette.YELLOW; - else if (r == Result.ABORTED || r == Result.NOT_BUILT) - return ColorPalette.GREY; - else - return ColorPalette.BLUE; - } + public String toString() { + String l = run.getDisplayName(); + if (run instanceof Build) { + String s = ((Build) run).getBuiltOnStr(); + if (s != null) + l += ' ' + s; + } + return l; + } - public int hashCode() { - return run.hashCode(); - } + } - public String toString() { - String l = run.getDisplayName(); - if (run instanceof Build) { - String s = ((Build) run).getBuiltOnStr(); - if (s != null) - l += ' ' + s; + DataSetBuilder data = new DataSetBuilder(); + for (Run r : getBuilds()) { + if (r.isBuilding()) + continue; + data.add(((double) r.getDuration()) / (1000 * 60), "min", + new ChartLabel(r)); } - return l; - } - } + final CategoryDataset dataset = data.build(); + + final JFreeChart chart = ChartFactory.createStackedAreaChart(null, // chart + // title + null, // unused + Messages.Job_minutes(), // range axis label + dataset, // data + PlotOrientation.VERTICAL, // orientation + false, // include legend + true, // tooltips + false // urls + ); + + 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.setForegroundAlpha(0.8f); + // plot.setDomainGridlinesVisible(true); + // plot.setDomainGridlinePaint(Color.white); + 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(); + ChartUtil.adjustChebyshev(dataset, rangeAxis); + rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + + StackedAreaRenderer ar = new StackedAreaRenderer2() { + @Override + public Paint getItemPaint(int row, int column) { + ChartLabel key = (ChartLabel) dataset.getColumnKey(column); + return key.getColor(); + } - DataSetBuilder data = new DataSetBuilder(); - for (Run r : getBuilds()) { - if (r.isBuilding()) - continue; - data.add(((double) r.getDuration()) / (1000 * 60), "min", - new ChartLabel(r)); - } + @Override + public String generateURL(CategoryDataset dataset, int row, + int column) { + ChartLabel label = (ChartLabel) dataset.getColumnKey(column); + return String.valueOf(label.run.number); + } - final CategoryDataset dataset = data.build(); - - final JFreeChart chart = ChartFactory.createStackedAreaChart(null, // chart - // title - null, // unused - Messages.Job_minutes(), // range axis label - dataset, // data - PlotOrientation.VERTICAL, // orientation - false, // include legend - true, // tooltips - false // urls - ); - - 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.setForegroundAlpha(0.8f); - // plot.setDomainGridlinesVisible(true); - // plot.setDomainGridlinePaint(Color.white); - 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(); - ChartUtil.adjustChebyshev(dataset, rangeAxis); - rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); - - StackedAreaRenderer ar = new StackedAreaRenderer2() { - @Override - public Paint getItemPaint(int row, int column) { - ChartLabel key = (ChartLabel) dataset.getColumnKey(column); - return key.getColor(); - } + @Override + public String generateToolTip(CategoryDataset dataset, int row, + int column) { + ChartLabel label = (ChartLabel) dataset.getColumnKey(column); + return label.run.getDisplayName() + " : " + + label.run.getDurationString(); + } + }; + plot.setRenderer(ar); - @Override - public String generateURL(CategoryDataset dataset, int row, - int column) { - ChartLabel label = (ChartLabel) dataset.getColumnKey(column); - return String.valueOf(label.run.number); - } + // crop extra space around the graph + plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); - @Override - public String generateToolTip(CategoryDataset dataset, int row, - int column) { - ChartLabel label = (ChartLabel) dataset.getColumnKey(column); - return label.run.getDisplayName() + " : " - + label.run.getDurationString(); + return chart; } }; - plot.setRenderer(ar); - - // crop extra space around the graph - plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); - - return chart; } /** diff --git a/core/src/main/java/hudson/tasks/junit/History.java b/core/src/main/java/hudson/tasks/junit/History.java index 08fc2108a9..9ec07a9ec2 100644 --- a/core/src/main/java/hudson/tasks/junit/History.java +++ b/core/src/main/java/hudson/tasks/junit/History.java @@ -1,3 +1,26 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package hudson.tasks.junit; import hudson.model.AbstractBuild; @@ -6,10 +29,10 @@ import hudson.util.ColorPalette; import hudson.util.DataSetBuilder; import hudson.util.ShiftedCategoryAxis; import hudson.util.StackedAreaRenderer2; +import hudson.util.Graph; import java.awt.Color; import java.awt.Paint; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -23,11 +46,13 @@ import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.StackedAreaRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.ui.RectangleInsets; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; +/** + * History of {@link TestObject} over time. + * + * @since 1.320 + */ public class History { - private final TestObject testObject; public History(TestObject testObject) { @@ -50,128 +75,132 @@ public class History { return list; } - public void doDurationGraph(StaplerRequest req, StaplerResponse rsp) throws IOException { - if (req.checkIfModified(testObject.getOwner().getTimestamp(), rsp)) return; - ChartUtil.generateGraph(req, rsp, createGraph(getDurationData(), "seconds"), 500, 400); - } - - public void doDurationMap(StaplerRequest req, StaplerResponse rsp) throws IOException { - if (req.checkIfModified(testObject.getOwner().getTimestamp(), rsp)) return; - ChartUtil.generateClickableMap(req, rsp, createGraph(getDurationData(), ""), 500, 400); - } - - private DataSetBuilder getDurationData() { - DataSetBuilder data = new DataSetBuilder(); - for (TestObject o: getList()) { - data.add(((double) o.getDuration()) / (1000), "", new ChartLabel(o) { - @Override - public Color getColor() { - if (o.getFailCount() > 0) - return ColorPalette.RED; - else if (o.getSkipCount() > 0) - return ColorPalette.YELLOW; - else - return ColorPalette.BLUE; - } - }); - } - return data; - } - - private DataSetBuilder getCountData() { - DataSetBuilder data = new DataSetBuilder(); - - for (TestObject o: getList()) { - data.add(o.getPassCount(), "2Passed", new ChartLabel(o)); - data.add(o.getFailCount(), "1Failed", new ChartLabel(o)); - data.add(o.getSkipCount(), "0Skipped", new ChartLabel(o)); - } - return data; - } - - public void doCountGraph(StaplerRequest req, StaplerResponse rsp) throws IOException { - if (req.checkIfModified(testObject.getOwner().getTimestamp(), rsp)) return; - ChartUtil.generateGraph(req, rsp, createGraph(getCountData(), ""), 500, 400); - } - - public void doCountMap(StaplerRequest req, StaplerResponse rsp) throws IOException { - if (req.checkIfModified(testObject.getOwner().getTimestamp(), rsp)) return; - ChartUtil.generateClickableMap(req, rsp, createGraph(getCountData(), ""), 500, 400); - } - - - private JFreeChart createGraph(DataSetBuilder data, String yLabel) { - final CategoryDataset dataset = data.build(); - - final JFreeChart chart = ChartFactory.createStackedAreaChart(null, // chart - // title - null, // unused - yLabel, // range axis label - dataset, // data - PlotOrientation.VERTICAL, // orientation - false, // include legend - true, // tooltips - false // urls - ); - - 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.setForegroundAlpha(0.8f); - // plot.setDomainGridlinesVisible(true); - // plot.setDomainGridlinePaint(Color.white); - 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(); - ChartUtil.adjustChebyshev(dataset, rangeAxis); - rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); - rangeAxis.setAutoRange(true); - - StackedAreaRenderer ar = new StackedAreaRenderer2() { - @Override - public Paint getItemPaint(int row, int column) { - ChartLabel key = (ChartLabel) dataset.getColumnKey(column); - if (key.getColor() != null) return key.getColor(); - return super.getItemPaint(row, column); - } - - @Override - public String generateURL(CategoryDataset dataset, int row, - int column) { - ChartLabel label = (ChartLabel) dataset.getColumnKey(column); - return String.valueOf(label.o.getOwner().number); - } + /** + * Graph of duration of tests over time. + */ + public Graph getDurationGraph() { + return new GraphImpl("seconds") { + protected DataSetBuilder createDataSet() { + DataSetBuilder data = new DataSetBuilder(); + for (TestObject o: getList()) { + data.add(((double) o.getDuration()) / (1000), "", new ChartLabel(o) { + @Override + public Color getColor() { + if (o.getFailCount() > 0) + return ColorPalette.RED; + else if (o.getSkipCount() > 0) + return ColorPalette.YELLOW; + else + return ColorPalette.BLUE; + } + }); + } + return data; + } + }; + } - @Override - public String generateToolTip(CategoryDataset dataset, int row, - int column) { - ChartLabel label = (ChartLabel) dataset.getColumnKey(column); - return label.o.getOwner().getDisplayName() + " : " - + label.o.getDurationString(); + /** + * Graph of # of tests over time. + */ + public Graph getCountGraph() { + return new GraphImpl("") { + protected DataSetBuilder createDataSet() { + DataSetBuilder data = new DataSetBuilder(); + + for (TestObject o: getList()) { + data.add(o.getPassCount(), "2Passed", new ChartLabel(o)); + data.add(o.getFailCount(), "1Failed", new ChartLabel(o)); + data.add(o.getSkipCount(), "0Skipped", new ChartLabel(o)); + } + return data; } }; - plot.setRenderer(ar); - ar.setSeriesPaint(0,ColorPalette.RED); // Failures. - ar.setSeriesPaint(1,ColorPalette.YELLOW); // Skips. - ar.setSeriesPaint(2,ColorPalette.BLUE); // Total. + } - // crop extra space around the graph - plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); + private abstract class GraphImpl extends Graph { + private final String yLabel; - return chart; - } + protected GraphImpl(String yLabel) { + super(testObject.getOwner().getTimestamp(),500,400); + this.yLabel = yLabel; + } + + protected abstract DataSetBuilder createDataSet(); + + protected JFreeChart createGraph() { + final CategoryDataset dataset = createDataSet().build(); + + final JFreeChart chart = ChartFactory.createStackedAreaChart(null, // chart + // title + null, // unused + yLabel, // range axis label + dataset, // data + PlotOrientation.VERTICAL, // orientation + false, // include legend + true, // tooltips + false // urls + ); + + 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.setForegroundAlpha(0.8f); + // plot.setDomainGridlinesVisible(true); + // plot.setDomainGridlinePaint(Color.white); + 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(); + ChartUtil.adjustChebyshev(dataset, rangeAxis); + rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setAutoRange(true); + + StackedAreaRenderer ar = new StackedAreaRenderer2() { + @Override + public Paint getItemPaint(int row, int column) { + ChartLabel key = (ChartLabel) dataset.getColumnKey(column); + if (key.getColor() != null) return key.getColor(); + return super.getItemPaint(row, column); + } + + @Override + public String generateURL(CategoryDataset dataset, int row, + int column) { + ChartLabel label = (ChartLabel) dataset.getColumnKey(column); + return String.valueOf(label.o.getOwner().number); + } + + @Override + public String generateToolTip(CategoryDataset dataset, int row, + int column) { + ChartLabel label = (ChartLabel) dataset.getColumnKey(column); + return label.o.getOwner().getDisplayName() + " : " + + label.o.getDurationString(); + } + }; + plot.setRenderer(ar); + ar.setSeriesPaint(0,ColorPalette.RED); // Failures. + ar.setSeriesPaint(1,ColorPalette.YELLOW); // Skips. + ar.setSeriesPaint(2,ColorPalette.BLUE); // Total. + + // crop extra space around the graph + plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); + + return chart; + } + } class ChartLabel implements Comparable { TestObject o; @@ -180,8 +209,7 @@ public class History { } public int compareTo(ChartLabel that) { - int result = this.o.getOwner().number - that.o.getOwner().number; - return result; + return this.o.getOwner().number - that.o.getOwner().number; } public boolean equals(Object o) { @@ -189,8 +217,7 @@ public class History { return false; } ChartLabel that = (ChartLabel) o; - boolean result = this.o == that.o; - return result; + return this.o == that.o; } public Color getColor() { diff --git a/core/src/main/java/hudson/util/ChartUtil.java b/core/src/main/java/hudson/util/ChartUtil.java index b1e7f3957c..914e4e0582 100644 --- a/core/src/main/java/hudson/util/ChartUtil.java +++ b/core/src/main/java/hudson/util/ChartUtil.java @@ -24,19 +24,14 @@ package hudson.util; import hudson.model.AbstractBuild; -import org.jfree.chart.ChartRenderingInfo; -import org.jfree.chart.ChartUtilities; +import hudson.tasks.junit.History; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.data.category.CategoryDataset; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; -import javax.imageio.ImageIO; -import javax.servlet.ServletOutputStream; import java.awt.Font; -import java.awt.HeadlessException; -import java.awt.image.BufferedImage; import java.io.IOException; /** @@ -94,6 +89,9 @@ public class ChartUtil { * @param defaultSize * The size of the picture to be generated. These values can be overridden * by the query paramter 'width' and 'height' in the request. + * @deprecated as of 1.320 + * Bind {@link Graph} to the URL space. See {@link History} as an example (note that doing so involves + * a bit of URL structure change.) */ public static void generateGraph(StaplerRequest req, StaplerResponse rsp, JFreeChart chart, Area defaultSize) throws IOException { generateGraph(req,rsp,chart,defaultSize.width, defaultSize.height); @@ -106,62 +104,24 @@ public class ChartUtil { * @param defaultH * The size of the picture to be generated. These values can be overridden * by the query paramter 'width' and 'height' in the request. + * @deprecated as of 1.320 + * Bind {@link Graph} to the URL space. See {@link History} as an example (note that doing so involves + * a bit of URL structure change.) */ - public static void generateGraph(StaplerRequest req, StaplerResponse rsp, JFreeChart chart, int defaultW, int defaultH) throws IOException { - try { - String w = req.getParameter("width"); - if(w==null) w=String.valueOf(defaultW); - String h = req.getParameter("height"); - if(h==null) h=String.valueOf(defaultH); - BufferedImage image = chart.createBufferedImage(Integer.parseInt(w),Integer.parseInt(h)); - rsp.setContentType("image/png"); - ServletOutputStream os = rsp.getOutputStream(); - ImageIO.write(image, "PNG", os); - os.close(); - } catch(Error e) { - /* OpenJDK on ARM produces an error like this in case of headless error - Caused by: java.lang.Error: Probable fatal error:No fonts found. - at sun.font.FontManager.getDefaultPhysicalFont(FontManager.java:1088) - at sun.font.FontManager.initialiseDeferredFont(FontManager.java:967) - at sun.font.CompositeFont.doDeferredInitialisation(CompositeFont.java:254) - at sun.font.CompositeFont.getSlotFont(CompositeFont.java:334) - at sun.font.CompositeStrike.getStrikeForSlot(CompositeStrike.java:77) - at sun.font.CompositeStrike.getFontMetrics(CompositeStrike.java:93) - at sun.font.Font2D.getFontMetrics(Font2D.java:387) - at java.awt.Font.defaultLineMetrics(Font.java:2082) - at java.awt.Font.getLineMetrics(Font.java:2152) - at org.jfree.chart.axis.NumberAxis.estimateMaximumTickLabelHeight(NumberAxis.java:974) - at org.jfree.chart.axis.NumberAxis.selectVerticalAutoTickUnit(NumberAxis.java:1104) - at org.jfree.chart.axis.NumberAxis.selectAutoTickUnit(NumberAxis.java:1048) - at org.jfree.chart.axis.NumberAxis.refreshTicksVertical(NumberAxis.java:1249) - at org.jfree.chart.axis.NumberAxis.refreshTicks(NumberAxis.java:1149) - at org.jfree.chart.axis.ValueAxis.reserveSpace(ValueAxis.java:788) - at org.jfree.chart.plot.CategoryPlot.calculateRangeAxisSpace(CategoryPlot.java:2650) - at org.jfree.chart.plot.CategoryPlot.calculateAxisSpace(CategoryPlot.java:2669) - at org.jfree.chart.plot.CategoryPlot.draw(CategoryPlot.java:2716) - at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1222) - at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1396) - at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1376) - at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1361) - at hudson.util.ChartUtil.generateGraph(ChartUtil.java:116) - at hudson.util.ChartUtil.generateGraph(ChartUtil.java:99) - at hudson.tasks.test.AbstractTestResultAction.doGraph(AbstractTestResultAction.java:196) - at hudson.tasks.test.TestResultProjectAction.doTrend(TestResultProjectAction.java:97) - ... 37 more - */ - if(e.getMessage().contains("Probable fatal error:No fonts found")) { - rsp.sendRedirect2(req.getContextPath()+"/images/headless.png"); - return; + public static void generateGraph(StaplerRequest req, StaplerResponse rsp, final JFreeChart chart, int defaultW, int defaultH) throws IOException { + new Graph(-1,defaultW,defaultH) { + protected JFreeChart createGraph() { + return chart; } - throw e; // otherwise let the caller deal with it - } catch(HeadlessException e) { - // not available. send out error message - rsp.sendRedirect2(req.getContextPath()+"/images/headless.png"); - } + }.doPng(req,rsp); } /** * Generates the clickable map info and sends that to the response. + * + * @deprecated as of 1.320 + * Bind {@link Graph} to the URL space. See {@link History} as an example (note that doing so involves + * a bit of URL structure change.) */ public static void generateClickableMap(StaplerRequest req, StaplerResponse rsp, JFreeChart chart, Area defaultSize) throws IOException { generateClickableMap(req,rsp,chart,defaultSize.width,defaultSize.height); @@ -169,18 +129,17 @@ public class ChartUtil { /** * Generates the clickable map info and sends that to the response. + * + * @deprecated as of 1.320 + * Bind {@link Graph} to the URL space. See {@link History} as an example (note that doing so involves + * a bit of URL structure change.) */ - public static void generateClickableMap(StaplerRequest req, StaplerResponse rsp, JFreeChart chart, int defaultW, int defaultH) throws IOException { - String w = req.getParameter("width"); - if(w==null) w=String.valueOf(defaultW); - String h = req.getParameter("height"); - if(h==null) h=String.valueOf(defaultH); - - ChartRenderingInfo info = new ChartRenderingInfo(); - chart.createBufferedImage(Integer.parseInt(w),Integer.parseInt(h),info); - - rsp.setContentType("text/plain;charset=UTF-8"); - rsp.getWriter().println(ChartUtilities.getImageMap( "map", info )); + public static void generateClickableMap(StaplerRequest req, StaplerResponse rsp, final JFreeChart chart, int defaultW, int defaultH) throws IOException { + new Graph(-1,defaultW,defaultH) { + protected JFreeChart createGraph() { + return chart; + } + }.doMap(req,rsp); } /** diff --git a/core/src/main/java/hudson/util/Graph.java b/core/src/main/java/hudson/util/Graph.java new file mode 100644 index 0000000000..05becc36d7 --- /dev/null +++ b/core/src/main/java/hudson/util/Graph.java @@ -0,0 +1,162 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.util; + +import org.jfree.chart.JFreeChart; +import org.jfree.chart.ChartRenderingInfo; +import org.jfree.chart.ChartUtilities; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +import javax.servlet.ServletOutputStream; +import javax.imageio.ImageIO; +import java.io.IOException; +import java.util.Calendar; +import java.awt.image.BufferedImage; +import java.awt.*; + +/** + * A JFreeChart-generated graph that's bound to UI. + * + *

+ * This object exposes two URLs: + *

+ *
/png + *
PNG image of a graph + * + *
/map + *
Clickable map + *
+ * + * @author Kohsuke Kawaguchi + * @since 1.320 + */ +public abstract class Graph { + private final long timestamp; + private final int defaultW; + private final int defaultH; + private volatile JFreeChart graph; + + /** + * @param timestamp + * Timestamp of this graph. Used for HTTP cache related headers. + * If the graph doesn't have any timestamp to tie it to, pass -1. + */ + protected Graph(long timestamp, int defaultW, int defaultH) { + this.timestamp = timestamp; + this.defaultW = defaultW; + this.defaultH = defaultH; + } + + protected Graph(Calendar timestamp, int defaultW, int defaultH) { + this(timestamp.getTimeInMillis(),defaultW,defaultH); + } + + /** + * Actually render a chart. + */ + protected abstract JFreeChart createGraph(); + + private BufferedImage render(StaplerRequest req, ChartRenderingInfo info) { + String w = req.getParameter("width"); + if(w==null) w=String.valueOf(defaultW); + String h = req.getParameter("height"); + if(h==null) h=String.valueOf(defaultH); + + if (graph==null) graph = createGraph(); + return graph.createBufferedImage(Integer.parseInt(w),Integer.parseInt(h),info); + } + + /** + * Renders a graph. + */ + public void doPng(StaplerRequest req, StaplerResponse rsp) throws IOException { + if (req.checkIfModified(timestamp, rsp)) return; + + try { + BufferedImage image = render(req,null); + rsp.setContentType("image/png"); + ServletOutputStream os = rsp.getOutputStream(); + ImageIO.write(image, "PNG", os); + os.close(); + } catch(Error e) { + /* OpenJDK on ARM produces an error like this in case of headless error + Caused by: java.lang.Error: Probable fatal error:No fonts found. + at sun.font.FontManager.getDefaultPhysicalFont(FontManager.java:1088) + at sun.font.FontManager.initialiseDeferredFont(FontManager.java:967) + at sun.font.CompositeFont.doDeferredInitialisation(CompositeFont.java:254) + at sun.font.CompositeFont.getSlotFont(CompositeFont.java:334) + at sun.font.CompositeStrike.getStrikeForSlot(CompositeStrike.java:77) + at sun.font.CompositeStrike.getFontMetrics(CompositeStrike.java:93) + at sun.font.Font2D.getFontMetrics(Font2D.java:387) + at java.awt.Font.defaultLineMetrics(Font.java:2082) + at java.awt.Font.getLineMetrics(Font.java:2152) + at org.jfree.chart.axis.NumberAxis.estimateMaximumTickLabelHeight(NumberAxis.java:974) + at org.jfree.chart.axis.NumberAxis.selectVerticalAutoTickUnit(NumberAxis.java:1104) + at org.jfree.chart.axis.NumberAxis.selectAutoTickUnit(NumberAxis.java:1048) + at org.jfree.chart.axis.NumberAxis.refreshTicksVertical(NumberAxis.java:1249) + at org.jfree.chart.axis.NumberAxis.refreshTicks(NumberAxis.java:1149) + at org.jfree.chart.axis.ValueAxis.reserveSpace(ValueAxis.java:788) + at org.jfree.chart.plot.CategoryPlot.calculateRangeAxisSpace(CategoryPlot.java:2650) + at org.jfree.chart.plot.CategoryPlot.calculateAxisSpace(CategoryPlot.java:2669) + at org.jfree.chart.plot.CategoryPlot.draw(CategoryPlot.java:2716) + at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1222) + at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1396) + at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1376) + at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1361) + at hudson.util.ChartUtil.generateGraph(ChartUtil.java:116) + at hudson.util.ChartUtil.generateGraph(ChartUtil.java:99) + at hudson.tasks.test.AbstractTestResultAction.doPng(AbstractTestResultAction.java:196) + at hudson.tasks.test.TestResultProjectAction.doTrend(TestResultProjectAction.java:97) + ... 37 more + */ + if(e.getMessage().contains("Probable fatal error:No fonts found")) { + rsp.sendRedirect2(req.getContextPath()+"/images/headless.png"); + return; + } + throw e; // otherwise let the caller deal with it + } catch(HeadlessException e) { + // not available. send out error message + rsp.sendRedirect2(req.getContextPath()+"/images/headless.png"); + } + } + + /** + * Renders a clickable map. + */ + public void doMap(StaplerRequest req, StaplerResponse rsp) throws IOException { + if (req.checkIfModified(timestamp, rsp)) return; + + String w = req.getParameter("width"); + if(w==null) w=String.valueOf(defaultW); + String h = req.getParameter("height"); + if(h==null) h=String.valueOf(defaultH); + + ChartRenderingInfo info = new ChartRenderingInfo(); + render(req,info); + + rsp.setContentType("text/plain;charset=UTF-8"); + rsp.getWriter().println(ChartUtilities.getImageMap( "map", info )); + } +} diff --git a/core/src/main/resources/hudson/model/Job/buildTimeTrend.jelly b/core/src/main/resources/hudson/model/Job/buildTimeTrend.jelly index d339aef959..ea42b6eacd 100644 --- a/core/src/main/resources/hudson/model/Job/buildTimeTrend.jelly +++ b/core/src/main/resources/hudson/model/Job/buildTimeTrend.jelly @@ -30,7 +30,7 @@ THE SOFTWARE.
- [Build time graph] + [Build time graph]
diff --git a/core/src/main/resources/hudson/tasks/junit/History/index.jelly b/core/src/main/resources/hudson/tasks/junit/History/index.jelly index 6f4f327e48..b7ac0f0930 100644 --- a/core/src/main/resources/hudson/tasks/junit/History/index.jelly +++ b/core/src/main/resources/hudson/tasks/junit/History/index.jelly @@ -28,14 +28,14 @@ THE SOFTWARE.