diff --git a/LICENSE.txt b/LICENSE.txt index c22677c950..b635dbc8ea 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -267,7 +267,7 @@ Applications: See licenses/LICENSE-Apache2.0.txt See licenses/LICENSE-ECLIPSE-1.0.html - RRD4J 3.9 (jrobin.jar): + RRD4J 3.9.1 (jrobin.jar): Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2011 The OpenNMS Group, Inc. Copyright 2011 The RRD4J Authors. diff --git a/apps/jrobin/java/build.xml b/apps/jrobin/java/build.xml index b949e769dd..ce347c2935 100644 --- a/apps/jrobin/java/build.xml +++ b/apps/jrobin/java/build.xml @@ -66,7 +66,7 @@ - + diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java index 3be1036ead..75491729b9 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java @@ -310,10 +310,7 @@ public abstract class RrdBackendFactory implements Closeable { } @Override public void clear() { - try { - backend.close(); - } catch (IOException e) { - } + // backend doesn't need to be closed here as it already happens in RrdBackend.rrdClose() backend = null; super.clear(); } diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java index b95e8ae34a..ffedca8972 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java @@ -300,6 +300,9 @@ public class RrdDbPool { RrdEntry ref; try { ref = getEntry(dburi, false); + } catch (IllegalStateException e) { + // This means that corresponding database has been already closed before, so returning immediately. + return; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Release interrupted for " + rrdDb.getPath(), e); diff --git a/apps/jrobin/java/src/org/rrd4j/graph/BufferedImageWorker.java b/apps/jrobin/java/src/org/rrd4j/graph/BufferedImageWorker.java new file mode 100644 index 0000000000..6a778dbc8a --- /dev/null +++ b/apps/jrobin/java/src/org/rrd4j/graph/BufferedImageWorker.java @@ -0,0 +1,161 @@ +package org.rrd4j.graph; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; + +class BufferedImageWorker extends ImageWorker { + + static class Builder { + private int width = 1; + private int height = 1; + private RrdGraphDef gdef; + private ImageWriter writer; + private ImageWriteParam imageWriteParam; + BufferedImageWorker build() { + return new BufferedImageWorker(this); + } + private ImageWriteParam getImageParams() { + ImageWriteParam iwp = writer.getDefaultWriteParam(); + ImageWriterSpi imgProvider = writer.getOriginatingProvider(); + //If lossy compression, use the quality + if (! imgProvider.isFormatLossless()) { + iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + iwp.setCompressionQuality(gdef.imageQuality); + } + + if (iwp.canWriteProgressive()) { + iwp.setProgressiveMode(gdef.interlaced ? ImageWriteParam.MODE_DEFAULT:ImageWriteParam.MODE_DISABLED); + } + return iwp; + } + + public Builder setWidth(int width) { + this.width = width; + return this; + } + + public Builder setHeight(int height) { + this.height = height; + return this; + } + + public Builder setGdef(RrdGraphDef gdef) { + this.gdef = gdef; + if (this.writer == null) { + this.writer = ImageIO.getImageWritersByFormatName(gdef.imageFormat).next(); + } + if (this.imageWriteParam == null) { + this.imageWriteParam = getImageParams(); + } + return this; + } + + public Builder setWriter(ImageWriter writer) { + this.writer = writer; + if (this.imageWriteParam == null) { + this.imageWriteParam = getImageParams(); + } + return this; + } + + public Builder setImageWriteParam(ImageWriteParam imageWriteParam) { + this.imageWriteParam = imageWriteParam; + return this; + } + } + public static Builder getBuilder() { + return new Builder(); + } + + private BufferedImage img; + private int imgWidth; + private int imgHeight; + private AffineTransform initialAffineTransform; + private final ImageWriter writer; + private final ImageWriteParam iwp; + + private BufferedImageWorker(Builder builder) { + this.imgHeight = builder.height; + this.imgWidth = builder.width; + this.writer = builder.writer; + this.iwp = builder.imageWriteParam; + resize(imgWidth, imgHeight); + } + + /* + * Not for use, only for tests + */ + BufferedImageWorker(int width, int height) { + this.imgHeight = height; + this.imgWidth = width; + this.writer = ImageIO.getImageWritersByFormatName("png").next(); + this.iwp = writer.getDefaultWriteParam(); + resize(imgWidth, imgHeight); + } + + protected void resize(int width, int height) { + imgWidth = width; + imgHeight = height; + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = img.createGraphics(); + setG2d(g2d); + initialAffineTransform = g2d.getTransform(); + + setAntiAliasing(false); + setTextAntiAliasing(false); + + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + } + + protected void reset(Graphics2D g2d) { + g2d.setTransform(initialAffineTransform); + g2d.setClip(0, 0, imgWidth, imgHeight); + } + + protected void makeImage(OutputStream stream) throws IOException { + BufferedImage outputImage = img; + + ImageWriterSpi imgProvider = writer.getOriginatingProvider(); + + img.coerceData(false); + + // Some format can't manage 16M colors images + // JPEG don't like transparency + if (! imgProvider.canEncodeImage(outputImage) || "image/jpeg".equalsIgnoreCase(imgProvider.getMIMETypes()[0])) { + int w = img.getWidth(); + int h = img.getHeight(); + outputImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + outputImage.getGraphics().drawImage(img, 0, 0, w, h, null); + if (! imgProvider.canEncodeImage(outputImage)) { + throw new IllegalArgumentException("Invalid image type"); + } + } + + if (! imgProvider.canEncodeImage(outputImage)) { + throw new IllegalArgumentException("Invalid image type"); + } + + try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(stream)) { + writer.setOutput(imageStream); + writer.write(null, new IIOImage(outputImage, null, null), iwp); + imageStream.flush(); + } catch (IOException e) { + writer.abort(); + throw e; + } finally { + writer.dispose(); + } + } + +} diff --git a/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java b/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java index e278a4a17e..f5a9892529 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java @@ -1,223 +1,211 @@ package org.rrd4j.graph; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Paint; -import java.awt.RenderingHints; -import java.awt.Stroke; +import java.awt.*; import java.awt.font.LineMetrics; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.spi.ImageWriterSpi; -import javax.imageio.stream.ImageOutputStream; +/** + * An abstract class, that allows to use custom {@link Graphics2D}. To use it the construct should build it and call + * {@link #setG2d(Graphics2D)} when finished. + */ +public abstract class ImageWorker { -class ImageWorker { private static final String DUMMY_TEXT = "Dummy"; private static final int IMG_BUFFER_CAPACITY = 10000; // bytes - - private BufferedImage img; private Graphics2D g2d; - private int imgWidth, imgHeight; - private AffineTransform initialAffineTransform; - ImageWorker(int width, int height) { - resize(width, height); - } - - void resize(int width, int height) { + protected void setG2d(Graphics2D g2d) { if (g2d != null) { dispose(); } - - imgWidth = width; - imgHeight = height; - img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - - g2d = img.createGraphics(); - initialAffineTransform = g2d.getTransform(); - - setAntiAliasing(false); - setTextAntiAliasing(false); - - g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + this.g2d = g2d; } - void clip(int x, int y, int width, int height) { + protected abstract void resize(int width, int height); + + protected void clip(int x, int y, int width, int height) { g2d.setClip(x, y, width, height); } - void transform(int x, int y, double angle) { + protected void transform(int x, int y, double angle) { g2d.translate(x, y); g2d.rotate(angle); } - void reset() { - g2d.setTransform(initialAffineTransform); - g2d.setClip(0, 0, imgWidth, imgHeight); + protected void reset() { + reset(g2d); } - void fillRect(int x, int y, int width, int height, Paint paint) { + /** + * reset the dimensions of the {@link Graphics2D} + */ + protected abstract void reset(Graphics2D g2d); + + protected void fillRect(int x, int y, int width, int height, Paint paint) { g2d.setPaint(paint); g2d.fillRect(x, y, width, height); } - void fillPolygon(int[] x, int[] y, Paint paint) { - g2d.setPaint(paint); - g2d.fillPolygon(x, y, x.length); - } - - void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) { + protected void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) { g2d.setPaint(paint); PathIterator path = new PathIterator(yTop); for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) { int start = pos[0], end = pos[1], n = end - start; int[] xDev = new int[n + 2], yDev = new int[n + 2]; + int c = 0; for (int i = start; i < end; i++) { - xDev[i - start] = (int) x[i]; - yDev[i - start] = (int) yTop[i]; + int cx = (int) x[i]; + int cy = (int) yTop[i]; + if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) { + if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) { + // collapse horizontal lines + xDev[c - 1] = cx; + } else { + xDev[c] = cx; + yDev[c++] = cy; + } + } } - xDev[n] = xDev[n - 1]; - xDev[n + 1] = xDev[0]; - yDev[n] = yDev[n + 1] = (int) yBottom; - g2d.fillPolygon(xDev, yDev, xDev.length); - g2d.drawPolygon(xDev, yDev, xDev.length); + xDev[c] = xDev[c - 1]; + xDev[c + 1] = xDev[0]; + yDev[c] = yDev[c + 1] = (int) yBottom; + g2d.fillPolygon(xDev, yDev, c + 2); } } - void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) { + protected void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) { g2d.setPaint(paint); PathIterator path = new PathIterator(yTop); for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) { int start = pos[0], end = pos[1], n = end - start; int[] xDev = new int[n * 2], yDev = new int[n * 2]; + int c = 0; for (int i = start; i < end; i++) { - int ix1 = i - start, ix2 = n * 2 - 1 - i + start; - xDev[ix1] = xDev[ix2] = (int) x[i]; - yDev[ix1] = (int) yTop[i]; - yDev[ix2] = (int) yBottom[i]; + int cx = (int) x[i]; + int cy = (int) yTop[i]; + if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) { + if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) { + // collapse horizontal lines + xDev[c - 1] = cx; + } else { + xDev[c] = cx; + yDev[c++] = cy; + } + } } - g2d.fillPolygon(xDev, yDev, xDev.length); + for (int i = end - 1; i >= start; i--) { + int cx = (int) x[i]; + int cy = (int) yBottom[i]; + if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) { + if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) { + // collapse horizontal lines + xDev[c - 1] = cx; + } else { + xDev[c] = cx; + yDev[c++] = cy; + } + } + } + g2d.fillPolygon(xDev, yDev, c); } } - void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) { + protected void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) { g2d.setStroke(stroke); g2d.setPaint(paint); g2d.drawLine(x1, y1, x2, y2); } - void drawPolyline(int[] x, int[] y, Paint paint, Stroke stroke) { - g2d.setStroke(stroke); - g2d.setPaint(paint); - g2d.drawPolyline(x, y, x.length); - } - - void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) { + protected void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) { g2d.setPaint(paint); g2d.setStroke(stroke); PathIterator path = new PathIterator(y); for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) { int start = pos[0], end = pos[1]; int[] xDev = new int[end - start], yDev = new int[end - start]; + int c = 0; for (int i = start; i < end; i++) { - xDev[i - start] = (int) x[i]; - yDev[i - start] = (int) y[i]; + int cx = (int) x[i]; + int cy = (int) y[i]; + if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) { + if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) { + // collapse horizontal lines + xDev[c - 1] = cx; + } else { + xDev[c] = cx; + yDev[c++] = cy; + } + } } - g2d.drawPolyline(xDev, yDev, xDev.length); + g2d.drawPolyline(xDev, yDev, c); } } - void drawString(String text, int x, int y, Font font, Paint paint) { + protected void drawString(String text, int x, int y, Font font, Paint paint) { g2d.setFont(font); g2d.setPaint(paint); g2d.drawString(text, x, y); } - double getFontAscent(Font font) { + protected double getFontAscent(Font font) { LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, g2d.getFontRenderContext()); return lm.getAscent(); } - double getFontHeight(Font font) { + protected double getFontHeight(Font font) { LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, g2d.getFontRenderContext()); return lm.getAscent() + lm.getDescent(); } - double getStringWidth(String text, Font font) { + protected double getStringWidth(String text, Font font) { return font.getStringBounds(text, 0, text.length(), g2d.getFontRenderContext()).getBounds().getWidth(); } - void setAntiAliasing(boolean enable) { + protected void setAntiAliasing(boolean enable) { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, enable ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); } - void setTextAntiAliasing(boolean enable) { + protected void setTextAntiAliasing(boolean enable) { g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, enable ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } - void dispose() { - g2d.dispose(); - } - - void makeImage(Object stream, ImageWriter writer, ImageWriteParam iwp) throws IOException { - BufferedImage outputImage = img; - - ImageWriterSpi imgProvider = writer.getOriginatingProvider(); - - img.coerceData(false); - - // Some format can't manage 16M colors images - // JPEG don't like transparency - if (! imgProvider.canEncodeImage(outputImage) || "image/jpeg".equalsIgnoreCase(imgProvider.getMIMETypes()[0])) { - int w = img.getWidth(); - int h = img.getHeight(); - outputImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - outputImage.getGraphics().drawImage(img, 0, 0, w, h, null); - if (! imgProvider.canEncodeImage(outputImage)) { - throw new RuntimeException("Invalid image type"); - } - } - - if (! imgProvider.canEncodeImage(outputImage)) { - throw new RuntimeException("Invalid image type"); - } - - try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(stream)) { - writer.setOutput(imageStream); - writer.write(null, new IIOImage(outputImage, null, null), iwp); - imageStream.flush(); - } catch (IOException e) { - writer.abort(); - throw e; - } finally { - writer.dispose(); - } - } - - void saveImage(String path, ImageWriter writer, ImageWriteParam iwp) throws IOException { - makeImage(Paths.get(path).toFile(), writer, iwp); - } - - byte[] getImageBytes(ImageWriter writer, ImageWriteParam iwp) throws IOException { - try (ByteArrayOutputStream stream = new ByteArrayOutputStream(IMG_BUFFER_CAPACITY)){ - makeImage(stream, writer, iwp); - return stream.toByteArray(); - } - } - - void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException { + protected void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException { BufferedImage wpImage = imageSource.apply(w, h).getSubimage(0, 0, w, h); g2d.drawImage(wpImage, new AffineTransform(1f, 0f, 0f, 1f, x, y), null); } + protected void dispose() { + if (g2d != null) { + g2d.dispose(); + } + } + + protected void makeImage(Path path) throws IOException { + try (OutputStream os = Files.newOutputStream(path)) { + makeImage(os); + } + } + + protected abstract void makeImage(OutputStream os) throws IOException ; + + protected void saveImage(String path) throws IOException { + makeImage(Paths.get(path)); + } + + protected byte[] getImageBytes() throws IOException { + try (ByteArrayOutputStream stream = new ByteArrayOutputStream(IMG_BUFFER_CAPACITY)){ + makeImage(stream); + return stream.toByteArray(); + } + } + } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java index 404ed556f0..8b029cb6af 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java @@ -12,10 +12,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; -import javax.imageio.spi.ImageWriterSpi; import javax.swing.ImageIcon; import org.rrd4j.core.Util; @@ -47,8 +45,6 @@ public class RrdGraph implements RrdGraphConstants { Mapper mapper; private final RrdGraphInfo info = new RrdGraphInfo(); private final String signature; - private final ImageWriter writer; - private final ImageWriteParam param; /** * Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object. @@ -60,9 +56,8 @@ public class RrdGraph implements RrdGraphConstants { this.gdef = gdef; signature = gdef.getSignature(); im = new ImageParameters(); - worker = new ImageWorker(1, 1); // Dummy worker, just to start with something - writer = ImageIO.getImageWritersByFormatName(gdef.imageFormat).next(); - param = getImageParams(); + + worker = BufferedImageWorker.getBuilder().setGdef(gdef).build(); try { createGraph(); } @@ -73,6 +68,28 @@ public class RrdGraph implements RrdGraphConstants { } } + /** + * Create graph from a custom image worker + * @param gdef + * @param worker + * @throws IOException + */ + public RrdGraph(RrdGraphDef gdef, ImageWorker worker) throws IOException { + this.gdef = gdef; + signature = gdef.getSignature(); + im = new ImageParameters(); + this.worker = worker; + try { + createGraph(); + } + finally { + worker.dispose(); + this.worker = null; + dproc = null; + } + } + + /** *

Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.

*

The graph will be created using customs {@link javax.imageio.ImageWriter} and {@link javax.imageio.ImageWriteParam} given.

@@ -88,9 +105,7 @@ public class RrdGraph implements RrdGraphConstants { this.gdef = gdef; signature = gdef.getSignature(); im = new ImageParameters(); - worker = new ImageWorker(1, 1); // Dummy worker, just to start with something - this.writer = writer; - this.param = param; + worker = BufferedImageWorker.getBuilder().setGdef(gdef).setWriter(writer).setImageWriteParam(param).build(); try { createGraph(); } @@ -110,21 +125,6 @@ public class RrdGraph implements RrdGraphConstants { return info; } - private ImageWriteParam getImageParams() { - ImageWriteParam iwp = writer.getDefaultWriteParam(); - ImageWriterSpi imgProvider = writer.getOriginatingProvider(); - //If lossy compression, use the quality - if (! imgProvider.isFormatLossless()) { - iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - iwp.setCompressionQuality(gdef.imageQuality); - } - - if (iwp.canWriteProgressive()) { - iwp.setProgressiveMode(gdef.interlaced ? ImageWriteParam.MODE_DEFAULT:ImageWriteParam.MODE_DISABLED); - } - return iwp; - } - private void createGraph() throws IOException { boolean lazy = lazyCheck(); if (!lazy || gdef.printStatementCount() != 0) { @@ -177,7 +177,7 @@ public class RrdGraph implements RrdGraphConstants { private void saveImage() throws IOException { if (! RrdGraphConstants.IN_MEMORY_IMAGE.equals(gdef.filename)) { Path imgpath = Paths.get(gdef.filename); - worker.saveImage(gdef.filename, writer, param); + worker.saveImage(gdef.filename); info.bytesSource = () -> { try { return Files.readAllBytes(imgpath); @@ -194,7 +194,7 @@ public class RrdGraph implements RrdGraphConstants { }; } else { - byte[] content = worker.getImageBytes(writer, param); + byte[] content = worker.getImageBytes(); info.bytesSource = () -> Arrays.copyOf(content, content.length); info.bytesCount = () -> content.length; }