This commit is contained in:
Shihab Uddin
2012-09-26 11:17:30 +02:00
625 changed files with 42993 additions and 14410 deletions
+31 -29
View File
@@ -1,35 +1,37 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.0-SNAPSHOT</version> <version>3.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-image</artifactId> <artifactId>common-image</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: Image</name> <name>TwelveMonkeys :: Common :: Image</name>
<description> <description>
The TwelveMonkeys Common Image support The TwelveMonkeys Common Image support
</description> </description>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId> <artifactId>common-lang</artifactId>
</dependency> </dependency>
<dependency>
<groupId>${project.groupId}</groupId> <dependency>
<artifactId>common-io</artifactId> <groupId>${project.groupId}</groupId>
</dependency> <artifactId>common-io</artifactId>
<dependency> </dependency>
<groupId>jmagick</groupId>
<dependency>
<groupId>jmagick</groupId>
<artifactId>jmagick</artifactId> <artifactId>jmagick</artifactId>
<version>6.2.4</version> <version>6.2.4</version>
<optional>true</optional> <optional>true</optional>
</dependency> <scope>provided</scope>
</dependencies> </dependency>
</dependencies>
</project> </project>
@@ -41,21 +41,24 @@ import java.util.ArrayList;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java#1 $
*/ */
public abstract class AbstractImageSource implements ImageProducer { public abstract class AbstractImageSource implements ImageProducer {
private List<ImageConsumer> mConsumers = new ArrayList<ImageConsumer>(); private List<ImageConsumer> consumers = new ArrayList<ImageConsumer>();
protected int mWidth; protected int width;
protected int mHeight; protected int height;
protected int mXOff; protected int xOff;
protected int mYOff; protected int yOff;
// ImageProducer interface // ImageProducer interface
public void addConsumer(ImageConsumer pConsumer) { public void addConsumer(final ImageConsumer pConsumer) {
if (mConsumers.contains(pConsumer)) { if (consumers.contains(pConsumer)) {
return; return;
} }
mConsumers.add(pConsumer);
consumers.add(pConsumer);
try { try {
initConsumer(pConsumer); initConsumer(pConsumer);
sendPixels(pConsumer); sendPixels(pConsumer);
if (isConsumer(pConsumer)) { if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE); pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
@@ -68,34 +71,35 @@ public abstract class AbstractImageSource implements ImageProducer {
} }
catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
if (isConsumer(pConsumer)) { if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.IMAGEERROR); pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
} }
} }
} }
public void removeConsumer(ImageConsumer pConsumer) { public void removeConsumer(final ImageConsumer pConsumer) {
mConsumers.remove(pConsumer); consumers.remove(pConsumer);
} }
/** /**
* This implementation silently ignores this instruction. If pixeldata is * This implementation silently ignores this instruction. If pixel data is
* not in TDLR order by default, subclasses must override this method. * not in TDLR order by default, subclasses must override this method.
* *
* @param pConsumer the consumer that requested the resend * @param pConsumer the consumer that requested the resend
* *
* @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer) * @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
*/ */
public void requestTopDownLeftRightResend(ImageConsumer pConsumer) { public void requestTopDownLeftRightResend(final ImageConsumer pConsumer) {
// ignore // ignore
} }
public void startProduction(ImageConsumer pConsumer) { public void startProduction(final ImageConsumer pConsumer) {
addConsumer(pConsumer); addConsumer(pConsumer);
} }
public boolean isConsumer(ImageConsumer pConsumer) { public boolean isConsumer(final ImageConsumer pConsumer) {
return mConsumers.contains(pConsumer); return consumers.contains(pConsumer);
} }
protected abstract void initConsumer(ImageConsumer pConsumer); protected abstract void initConsumer(ImageConsumer pConsumer);
@@ -47,33 +47,34 @@ import java.io.IOException;
*/ */
public class AreaAverageOp implements BufferedImageOp, RasterOp { public class AreaAverageOp implements BufferedImageOp, RasterOp {
final private int mWidth; final private int width;
final private int mHeight; final private int height;
private Rectangle mSourceRegion; private Rectangle sourceRegion;
public AreaAverageOp(final int pWidth, final int pHeight) { public AreaAverageOp(final int pWidth, final int pHeight) {
mWidth = pWidth; width = pWidth;
mHeight = pHeight; height = pHeight;
} }
public Rectangle getSourceRegion() { public Rectangle getSourceRegion() {
if (mSourceRegion == null) { if (sourceRegion == null) {
return null; return null;
} }
return new Rectangle(mSourceRegion);
return new Rectangle(sourceRegion);
} }
public void setSourceRegion(final Rectangle pSourceRegion) { public void setSourceRegion(final Rectangle pSourceRegion) {
if (pSourceRegion == null) { if (pSourceRegion == null) {
mSourceRegion = null; sourceRegion = null;
} }
else { else {
if (mSourceRegion == null) { if (sourceRegion == null) {
mSourceRegion = new Rectangle(pSourceRegion); sourceRegion = new Rectangle(pSourceRegion);
} }
else { else {
mSourceRegion.setBounds(pSourceRegion); sourceRegion.setBounds(pSourceRegion);
} }
} }
} }
@@ -93,7 +94,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
// Straight-forward version // Straight-forward version
//Image scaled = src.getScaledInstance(mWidth, mHeight, Image.SCALE_AREA_AVERAGING); //Image scaled = src.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
//ImageUtil.drawOnto(result, scaled); //ImageUtil.drawOnto(result, scaled);
//result = new BufferedImageFactory(scaled).getBufferedImage(); //result = new BufferedImageFactory(scaled).getBufferedImage();
@@ -104,7 +105,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
AffineTransform xform = null; AffineTransform xform = null;
int w = src.getWidth(); int w = src.getWidth();
int h = src.getHeight(); int h = src.getHeight();
while (w / 2 > mWidth && h / 2 > mHeight) { while (w / 2 > width && h / 2 > height) {
w /= 2; w /= 2;
h /= 2; h /= 2;
@@ -129,7 +130,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
src = temp.getSubimage(0, 0, w, h); src = temp.getSubimage(0, 0, w, h);
} }
resample(src, result, AffineTransform.getScaleInstance(mWidth / (double) w, mHeight / (double) h)); resample(src, result, AffineTransform.getScaleInstance(width / (double) w, height / (double) h));
*/ */
// The real version // The real version
@@ -160,11 +161,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
private WritableRaster filterImpl(Raster src, WritableRaster dest) { private WritableRaster filterImpl(Raster src, WritableRaster dest) {
//System.out.println("src: " + src); //System.out.println("src: " + src);
//System.out.println("dest: " + dest); //System.out.println("dest: " + dest);
if (mSourceRegion != null) { if (sourceRegion != null) {
int cx = mSourceRegion.x; int cx = sourceRegion.x;
int cy = mSourceRegion.y; int cy = sourceRegion.y;
int cw = mSourceRegion.width; int cw = sourceRegion.width;
int ch = mSourceRegion.height; int ch = sourceRegion.height;
boolean same = src == dest; boolean same = src == dest;
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null); dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
@@ -179,11 +180,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
// TODO: This don't work too well.. // TODO: This don't work too well..
// The thing is that the step length and the scan length will vary, for // The thing is that the step length and the scan length will vary, for
// non-even (1/2, 1/4, 1/8 etc) resampling // non-even (1/2, 1/4, 1/8 etc) resampling
int widthSteps = (width + mWidth - 1) / mWidth; int widthSteps = (width + this.width - 1) / this.width;
int heightSteps = (height + mHeight - 1) / mHeight; int heightSteps = (height + this.height - 1) / this.height;
final boolean oddX = width % mWidth != 0; final boolean oddX = width % this.width != 0;
final boolean oddY = height % mHeight != 0; final boolean oddY = height % this.height != 0;
final int dataElements = src.getNumDataElements(); final int dataElements = src.getNumDataElements();
final int bands = src.getNumBands(); final int bands = src.getNumBands();
@@ -210,16 +211,16 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
} }
} }
for (int y = 0; y < mHeight; y++) { for (int y = 0; y < this.height; y++) {
if (!oddY || y < mHeight) { if (!oddY || y < this.height) {
scanH = heightSteps; scanH = heightSteps;
} }
else { else {
scanH = height - (y * heightSteps); scanH = height - (y * heightSteps);
} }
for (int x = 0; x < mWidth; x++) { for (int x = 0; x < this.width; x++) {
if (!oddX || x < mWidth) { if (!oddX || x < this.width) {
scanW = widthSteps; scanW = widthSteps;
} }
else { else {
@@ -243,8 +244,8 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
// //
//System.err.println("width: " + width); //System.err.println("width: " + width);
//System.err.println("height: " + height); //System.err.println("height: " + height);
//System.err.println("mWidth: " + mWidth); //System.err.println("width: " + width);
//System.err.println("mHeight: " + mHeight); //System.err.println("height: " + height);
// //
//e.printStackTrace(); //e.printStackTrace();
continue; continue;
@@ -382,20 +383,20 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) { public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
ColorModel cm = destCM != null ? destCM : src.getColorModel(); ColorModel cm = destCM != null ? destCM : src.getColorModel();
return new BufferedImage(cm, return new BufferedImage(cm,
ImageUtil.createCompatibleWritableRaster(src, cm, mWidth, mHeight), ImageUtil.createCompatibleWritableRaster(src, cm, width, height),
cm.isAlphaPremultiplied(), null); cm.isAlphaPremultiplied(), null);
} }
public WritableRaster createCompatibleDestRaster(Raster src) { public WritableRaster createCompatibleDestRaster(Raster src) {
return src.createCompatibleWritableRaster(mWidth, mHeight); return src.createCompatibleWritableRaster(width, height);
} }
public Rectangle2D getBounds2D(Raster src) { public Rectangle2D getBounds2D(Raster src) {
return new Rectangle(mWidth, mHeight); return new Rectangle(width, height);
} }
public Rectangle2D getBounds2D(BufferedImage src) { public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(mWidth, mHeight); return new Rectangle(width, height);
} }
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
@@ -67,7 +67,7 @@ public class BrightnessContrastFilter extends RGBImageFilter {
} }
// Use a precalculated lookup table for performace // Use a precalculated lookup table for performace
private int[] mLUT = null; private final int[] LUT;
/** /**
* Creates a BrightnessContrastFilter with default values * Creates a BrightnessContrastFilter with default values
@@ -105,7 +105,7 @@ public class BrightnessContrastFilter extends RGBImageFilter {
* {@code -1.0,..,0.0,..,1.0}. * {@code -1.0,..,0.0,..,1.0}.
*/ */
public BrightnessContrastFilter(float pBrightness, float pContrast) { public BrightnessContrastFilter(float pBrightness, float pContrast) {
mLUT = createLUT(pBrightness, pContrast); LUT = createLUT(pBrightness, pContrast);
} }
private static int[] createLUT(float pBrightness, float pContrast) { private static int[] createLUT(float pBrightness, float pContrast) {
@@ -157,9 +157,9 @@ public class BrightnessContrastFilter extends RGBImageFilter {
int b = pARGB & 0xFF; int b = pARGB & 0xFF;
// Scale to new contrast // Scale to new contrast
r = mLUT[r]; r = LUT[r];
g = mLUT[g]; g = LUT[g];
b = mLUT[b]; b = LUT[b];
// Return ARGB pixel, leave transparency as is // Return ARGB pixel, leave transparency as is
return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b; return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b;
File diff suppressed because it is too large Load Diff
@@ -28,6 +28,8 @@
package com.twelvemonkeys.image; package com.twelvemonkeys.image;
import com.twelvemonkeys.lang.Validate;
import javax.swing.Icon; import javax.swing.Icon;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.*; import java.awt.*;
@@ -41,51 +43,44 @@ import java.awt.geom.AffineTransform;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java#2 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java#2 $
*/ */
public class BufferedImageIcon implements Icon { public class BufferedImageIcon implements Icon {
private final BufferedImage mImage; private final BufferedImage image;
private int mWidth; private int width;
private int mHeight; private int height;
private final boolean mFast; private final boolean fast;
public BufferedImageIcon(BufferedImage pImage) { public BufferedImageIcon(BufferedImage pImage) {
this(pImage, pImage.getWidth(), pImage.getHeight()); this(pImage, pImage != null ? pImage.getWidth() : 0, pImage != null ? pImage.getHeight() : 0);
} }
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) { public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
if (pImage == null) { image = Validate.notNull(pImage, "image");
throw new IllegalArgumentException("image == null"); width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d");
} height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d");
if (pWidth <= 0 || pHeight <= 0) {
throw new IllegalArgumentException("Icon size must be positive");
}
mImage = pImage; fast = image.getWidth() == width && image.getHeight() == height;
mWidth = pWidth;
mHeight = pHeight;
mFast = pImage.getWidth() == mWidth && pImage.getHeight() == mHeight;
} }
public int getIconHeight() { public int getIconHeight() {
return mHeight; return height;
} }
public int getIconWidth() { public int getIconWidth() {
return mWidth; return width;
} }
public void paintIcon(Component c, Graphics g, int x, int y) { public void paintIcon(Component c, Graphics g, int x, int y) {
if (mFast || !(g instanceof Graphics2D)) { if (fast || !(g instanceof Graphics2D)) {
//System.out.println("Scaling fast"); //System.out.println("Scaling fast");
g.drawImage(mImage, x, y, mWidth, mHeight, null); g.drawImage(image, x, y, width, height, null);
} }
else { else {
//System.out.println("Scaling using interpolation"); //System.out.println("Scaling using interpolation");
Graphics2D g2 = (Graphics2D) g; Graphics2D g2 = (Graphics2D) g;
AffineTransform xform = AffineTransform.getTranslateInstance(x, y); AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
xform.scale(mWidth / (double) mImage.getWidth(), mHeight / (double) mImage.getHeight()); xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR); RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(mImage, xform, null); g2.drawImage(image, xform, null);
} }
} }
} }
@@ -73,14 +73,15 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
*/ */
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
private final Kernel mKernel; private final Kernel kernel;
private final int mEdgeCondition; private final int edgeCondition;
private final ConvolveOp mConvolve; private final ConvolveOp convolve;
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) { public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
// Create convolution operation // Create convolution operation
int edge; int edge;
switch (pEdgeCondition) { switch (pEdgeCondition) {
case EDGE_REFLECT: case EDGE_REFLECT:
case EDGE_WRAP: case EDGE_WRAP:
@@ -90,9 +91,10 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
edge = pEdgeCondition; edge = pEdgeCondition;
break; break;
} }
mKernel = pKernel;
mEdgeCondition = pEdgeCondition; kernel = pKernel;
mConvolve = new ConvolveOp(pKernel, edge, pHints); edgeCondition = pEdgeCondition;
convolve = new ConvolveOp(pKernel, edge, pHints);
} }
public ConvolveWithEdgeOp(final Kernel pKernel) { public ConvolveWithEdgeOp(final Kernel pKernel) {
@@ -107,8 +109,8 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
throw new IllegalArgumentException("source image cannot be the same as the destination image"); throw new IllegalArgumentException("source image cannot be the same as the destination image");
} }
int borderX = mKernel.getWidth() / 2; int borderX = kernel.getWidth() / 2;
int borderY = mKernel.getHeight() / 2; int borderY = kernel.getHeight() / 2;
BufferedImage original = addBorder(pSource, borderX, borderY); BufferedImage original = addBorder(pSource, borderX, borderY);
@@ -126,7 +128,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
} }
// Do the filtering (if destination is null, a new image will be created) // Do the filtering (if destination is null, a new image will be created)
destination = mConvolve.filter(original, destination); destination = convolve.filter(original, destination);
if (pSource != original) { if (pSource != original) {
// Remove the border // Remove the border
@@ -137,7 +139,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
} }
private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) { private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) {
if ((mEdgeCondition & 2) == 0) { if ((edgeCondition & 2) == 0) {
return pOriginal; return pOriginal;
} }
@@ -158,7 +160,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
g.drawImage(pOriginal, pBorderX, pBorderY, null); g.drawImage(pOriginal, pBorderX, pBorderY, null);
// TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel // TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel
switch (mEdgeCondition) { switch (edgeCondition) {
case EDGE_REFLECT: case EDGE_REFLECT:
// Top/left (empty) // Top/left (empty)
g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center
@@ -186,7 +188,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right
break; break;
default: default:
throw new IllegalArgumentException("Illegal edge operation " + mEdgeCondition); throw new IllegalArgumentException("Illegal edge operation " + edgeCondition);
} }
} }
@@ -206,39 +208,39 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
* @see #EDGE_WRAP * @see #EDGE_WRAP
*/ */
public int getEdgeCondition() { public int getEdgeCondition() {
return mEdgeCondition; return edgeCondition;
} }
public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) { public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) {
return mConvolve.filter(pSource, pDestination); return convolve.filter(pSource, pDestination);
} }
public BufferedImage createCompatibleDestImage(final BufferedImage pSource, final ColorModel pDesinationColorModel) { public BufferedImage createCompatibleDestImage(final BufferedImage pSource, final ColorModel pDesinationColorModel) {
return mConvolve.createCompatibleDestImage(pSource, pDesinationColorModel); return convolve.createCompatibleDestImage(pSource, pDesinationColorModel);
} }
public WritableRaster createCompatibleDestRaster(final Raster pSource) { public WritableRaster createCompatibleDestRaster(final Raster pSource) {
return mConvolve.createCompatibleDestRaster(pSource); return convolve.createCompatibleDestRaster(pSource);
} }
public Rectangle2D getBounds2D(final BufferedImage pSource) { public Rectangle2D getBounds2D(final BufferedImage pSource) {
return mConvolve.getBounds2D(pSource); return convolve.getBounds2D(pSource);
} }
public Rectangle2D getBounds2D(final Raster pSource) { public Rectangle2D getBounds2D(final Raster pSource) {
return mConvolve.getBounds2D(pSource); return convolve.getBounds2D(pSource);
} }
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) { public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
return mConvolve.getPoint2D(pSourcePoint, pDestinationPoint); return convolve.getPoint2D(pSourcePoint, pDestinationPoint);
} }
public RenderingHints getRenderingHints() { public RenderingHints getRenderingHints() {
return mConvolve.getRenderingHints(); return convolve.getRenderingHints();
} }
public Kernel getKernel() { public Kernel getKernel() {
return mConvolve.getKernel(); return convolve.getKernel();
} }
} }
@@ -51,7 +51,7 @@ import java.awt.image.WritableRaster;
*/ */
public class CopyDither implements BufferedImageOp, RasterOp { public class CopyDither implements BufferedImageOp, RasterOp {
protected IndexColorModel mIndexColorModel = null; protected IndexColorModel indexColorModel = null;
/** /**
* Creates a {@code CopyDither}, using the given * Creates a {@code CopyDither}, using the given
@@ -61,7 +61,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
*/ */
public CopyDither(IndexColorModel pICM) { public CopyDither(IndexColorModel pICM) {
// Store colormodel // Store colormodel
mIndexColorModel = pICM; indexColorModel = pICM;
} }
/** /**
@@ -83,17 +83,12 @@ public class CopyDither implements BufferedImageOp, RasterOp {
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or * @throws ImageFilterException if {@code pDestCM} is not {@code null} or
* an instance of {@code IndexColorModel}. * an instance of {@code IndexColorModel}.
*/ */
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
ColorModel pDestCM) {
if (pDestCM == null) { if (pDestCM == null) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, indexColorModel);
BufferedImage.TYPE_BYTE_INDEXED,
mIndexColorModel);
} }
else if (pDestCM instanceof IndexColorModel) { else if (pDestCM instanceof IndexColorModel) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) pDestCM);
BufferedImage.TYPE_BYTE_INDEXED,
(IndexColorModel) pDestCM);
} }
else { else {
throw new ImageFilterException("Only IndexColorModel allowed."); throw new ImageFilterException("Only IndexColorModel allowed.");
@@ -112,13 +107,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
return createCompatibleDestRaster(pSrc, getICM(pSrc)); return createCompatibleDestRaster(pSrc, getICM(pSrc));
} }
public final WritableRaster createCompatibleDestRaster(Raster pSrc, public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
IndexColorModel pIndexColorModel) {
/*
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
pIndexColorModel).getRaster();
*/
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight()); return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
} }
@@ -207,8 +196,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
* @return the destination image, or a new image, if {@code pDest} was * @return the destination image, or a new image, if {@code pDest} was
* {@code null}. * {@code null}.
*/ */
public final BufferedImage filter(BufferedImage pSource, public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
BufferedImage pDest) {
// Create destination image, if none provided // Create destination image, if none provided
if (pDest == null) { if (pDest == null) {
pDest = createCompatibleDestImage(pSource, getICM(pSource)); pDest = createCompatibleDestImage(pSource, getICM(pSource));
@@ -238,16 +226,17 @@ public class CopyDither implements BufferedImageOp, RasterOp {
} }
private IndexColorModel getICM(BufferedImage pSource) { private IndexColorModel getICM(BufferedImage pSource) {
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY)); return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY));
} }
private IndexColorModel getICM(Raster pSource) { private IndexColorModel getICM(Raster pSource) {
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource)); return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
} }
private IndexColorModel createIndexColorModel(Raster pSource) { private IndexColorModel createIndexColorModel(Raster pSource) {
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_INT_ARGB);
BufferedImage.TYPE_INT_ARGB);
image.setData(pSource); image.setData(pSource);
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY); return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY);
} }
@@ -261,8 +250,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
* @return the destination raster, or a new raster, if {@code pDest} was * @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}. * {@code null}.
*/ */
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
IndexColorModel pColorModel) {
int width = pSource.getWidth(); int width = pSource.getWidth();
int height = pSource.getHeight(); int height = pSource.getHeight();
@@ -292,6 +280,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
pDest.setDataElements(x, y, pixel); pDest.setDataElements(x, y, pixel);
} }
} }
return pDest; return pDest;
} }
} }
@@ -36,34 +36,36 @@ import java.util.Random;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* *
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/DiffusionDither.java#1 $ * @version $Id: DiffusionDither.java#1 $
* *
*/ */
public class DiffusionDither implements BufferedImageOp, RasterOp { public class DiffusionDither implements BufferedImageOp, RasterOp {
protected IndexColorModel mIndexColorModel = null;
private boolean mAlternateScans = true;
private static final int FS_SCALE = 1 << 8; private static final int FS_SCALE = 1 << 8;
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
protected final IndexColorModel indexColorModel;
private boolean alternateScans = true;
/** /**
* Creates a {@code DiffusionDither}, using the given * Creates a {@code DiffusionDither}, using the given
* {@code IndexColorModel} for dithering into. * {@code IndexColorModel} for dithering into.
* *
* @param pICM an IndexColorModel. * @param pICM an IndexColorModel.
*/ */
public DiffusionDither(IndexColorModel pICM) { public DiffusionDither(final IndexColorModel pICM) {
// Store colormodel // Store color model
mIndexColorModel = pICM; indexColorModel = pICM;
} }
/** /**
* Creates a {@code DiffusionDither}, with no fixed * Creates a {@code DiffusionDither}, with no fixed
* {@code IndexColorModel}. The colormodel will be generated for each * {@code IndexColorModel}. The color model will be generated for each
* filtering, unless the dest image allready has an * filtering, unless the destination image already has an
* {@code IndexColorModel}. * {@code IndexColorModel}.
*/ */
public DiffusionDither() { public DiffusionDither() {
this(null);
} }
/** /**
@@ -74,7 +76,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* @param pUse {@code true} if scan mode should be alternating left/right * @param pUse {@code true} if scan mode should be alternating left/right
*/ */
public void setAlternateScans(boolean pUse) { public void setAlternateScans(boolean pUse) {
mAlternateScans = pUse; alternateScans = pUse;
} }
/** /**
@@ -86,8 +88,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or * @throws ImageFilterException if {@code pDestCM} is not {@code null} or
* an instance of {@code IndexColorModel}. * an instance of {@code IndexColorModel}.
*/ */
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
ColorModel pDestCM) {
if (pDestCM == null) { if (pDestCM == null) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED, BufferedImage.TYPE_BYTE_INDEXED,
@@ -107,7 +108,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* Creates a compatible {@code Raster} to dither into. * Creates a compatible {@code Raster} to dither into.
* Only {@code IndexColorModel} allowed. * Only {@code IndexColorModel} allowed.
* *
* @param pSrc * @param pSrc the source raster
* *
* @return a {@code WritableRaster} * @return a {@code WritableRaster}
*/ */
@@ -115,14 +116,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
return createCompatibleDestRaster(pSrc, getICM(pSrc)); return createCompatibleDestRaster(pSrc, getICM(pSrc));
} }
public final WritableRaster createCompatibleDestRaster(Raster pSrc, /**
IndexColorModel pIndexColorModel) { * Creates a compatible {@code Raster} to dither into.
*
* @param pSrc the source raster.
* @param pIndexColorModel the index color model used to create a {@code Raster}.
*
* @return a {@code WritableRaster}
*/
public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight()); return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
/*
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
pIndexColorModel).getRaster();
*/
} }
@@ -221,8 +224,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* @return the destination image, or a new image, if {@code pDest} was * @return the destination image, or a new image, if {@code pDest} was
* {@code null}. * {@code null}.
*/ */
public final BufferedImage filter(BufferedImage pSource, public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
BufferedImage pDest) {
// Create destination image, if none provided // Create destination image, if none provided
if (pDest == null) { if (pDest == null) {
pDest = createCompatibleDestImage(pSource, getICM(pSource)); pDest = createCompatibleDestImage(pSource, getICM(pSource));
@@ -252,10 +254,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
} }
private IndexColorModel getICM(BufferedImage pSource) { private IndexColorModel getICM(BufferedImage pSource) {
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK)); return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK));
} }
private IndexColorModel getICM(Raster pSource) { private IndexColorModel getICM(Raster pSource) {
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource)); return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
} }
private IndexColorModel createIndexColorModel(Raster pSource) { private IndexColorModel createIndexColorModel(Raster pSource) {
@@ -265,8 +267,6 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK); return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK);
} }
/** /**
* Performs a single-input/single-output dither operation, applying basic * Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image. * Floyd-Steinberg error-diffusion to the image.
@@ -278,8 +278,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* @return the destination raster, or a new raster, if {@code pDest} was * @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}. * {@code null}.
*/ */
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
IndexColorModel pColorModel) {
int width = pSource.getWidth(); int width = pSource.getWidth();
int height = pSource.getHeight(); int height = pSource.getHeight();
@@ -456,10 +455,11 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
mNextErr = temperr; mNextErr = temperr;
// Toggle direction // Toggle direction
if (mAlternateScans) { if (alternateScans) {
forward = !forward; forward = !forward;
} }
} }
return pDest; return pDest;
} }
} }
@@ -48,8 +48,8 @@ public class GrayFilter extends RGBImageFilter {
canFilterIndexColorModel = true; canFilterIndexColorModel = true;
} }
private int mLow = 0; private int low = 0;
private float mRange = 1.0f; private float range = 1.0f;
/** /**
* Constructs a GrayFilter using ITU color-conversion. * Constructs a GrayFilter using ITU color-conversion.
@@ -82,8 +82,8 @@ public class GrayFilter extends RGBImageFilter {
pHigh = 1f; pHigh = 1f;
} }
mLow = (int) (pLow * 255f); low = (int) (pLow * 255f);
mRange = pHigh - pLow; range = pHigh - pLow;
} }
@@ -118,9 +118,9 @@ public class GrayFilter extends RGBImageFilter {
//int gray = (int) ((float) (r + g + b) / 3.0f); //int gray = (int) ((float) (r + g + b) / 3.0f);
if (mRange != 1.0f) { if (range != 1.0f) {
// Apply range // Apply range
gray = mLow + (int) (gray * mRange); gray = low + (int) (gray * range);
} }
// Return ARGB pixel // Return ARGB pixel
@@ -32,42 +32,20 @@ package com.twelvemonkeys.image;
* This class wraps IllegalArgumentException as thrown by the * This class wraps IllegalArgumentException as thrown by the
* BufferedImageOp interface for more fine-grained control. * BufferedImageOp interface for more fine-grained control.
* *
* @author Harald Kuhr * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* *
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageFilterException.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageFilterException.java#1 $
*/ */
public class ImageFilterException extends IllegalArgumentException { public class ImageFilterException extends IllegalArgumentException {
private Throwable mCause = null; public ImageFilterException(String message) {
super(message);
public ImageFilterException(String pStr) {
super(pStr);
} }
public ImageFilterException(Throwable pT) { public ImageFilterException(Throwable cause) {
initCause(pT); super(cause);
} }
public ImageFilterException(String pStr, Throwable pT) { public ImageFilterException(String message, Throwable cause) {
super(pStr); super(message, cause);
initCause(pT);
}
public Throwable initCause(Throwable pThrowable) {
if (mCause != null) {
// May only be called once
throw new IllegalStateException();
}
else if (pThrowable == this) {
throw new IllegalArgumentException();
}
mCause = pThrowable;
// Hmmm...
return this;
}
public Throwable getCause() {
return mCause;
} }
} }
@@ -32,19 +32,17 @@ import java.awt.*;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.*; import java.awt.image.*;
import java.util.Hashtable; import java.util.Hashtable;
/** /**
* This class contains methods for basic image manipulation and conversion. * This class contains methods for basic image manipulation and conversion.
* *
* @todo Split palette generation out, into ColorModel classes.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
*/ */
public final class ImageUtil { public final class ImageUtil {
// TODO: Split palette generation out, into ColorModel classes (?)
public final static int ROTATE_90_CCW = -90; public final static int ROTATE_90_CCW = -90;
public final static int ROTATE_90_CW = 90; public final static int ROTATE_90_CW = 90;
@@ -59,12 +57,14 @@ public final class ImageUtil {
* @see #EDGE_REFLECT * @see #EDGE_REFLECT
*/ */
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL; public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
/** /**
* Alias for {@link ConvolveOp#EDGE_NO_OP}. * Alias for {@link ConvolveOp#EDGE_NO_OP}.
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
* @see #EDGE_REFLECT * @see #EDGE_REFLECT
*/ */
public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP; public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP;
/** /**
* Adds a border to the image while convolving. The border will reflect the * Adds a border to the image while convolving. The border will reflect the
* edges of the original image. This is usually a good default. * edges of the original image. This is usually a good default.
@@ -74,6 +74,7 @@ public final class ImageUtil {
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int) * @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
*/ */
public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT
/** /**
* Adds a border to the image while convolving. The border will wrap the * Adds a border to the image while convolving. The border will wrap the
* edges of the original image. This is usually the best choice for tiles. * edges of the original image. This is usually the best choice for tiles.
@@ -229,7 +230,7 @@ public final class ImageUtil {
* The new image will have the <em>same</em> {@code ColorModel}, * The new image will have the <em>same</em> {@code ColorModel},
* {@code Raster} and properties as the original image, if possible. * {@code Raster} and properties as the original image, if possible.
* <p/> * <p/>
* If the image is allready a {@code BufferedImage}, it is simply returned * If the image is already a {@code BufferedImage}, it is simply returned
* and no conversion takes place. * and no conversion takes place.
* *
* @param pOriginal the image to convert. * @param pOriginal the image to convert.
@@ -237,7 +238,7 @@ public final class ImageUtil {
* @return a {@code BufferedImage} * @return a {@code BufferedImage}
*/ */
public static BufferedImage toBuffered(RenderedImage pOriginal) { public static BufferedImage toBuffered(RenderedImage pOriginal) {
// Don't convert if it allready is a BufferedImage // Don't convert if it already is a BufferedImage
if (pOriginal instanceof BufferedImage) { if (pOriginal instanceof BufferedImage) {
return (BufferedImage) pOriginal; return (BufferedImage) pOriginal;
} }
@@ -283,7 +284,7 @@ public final class ImageUtil {
* Converts the {@code RenderedImage} to a {@code BufferedImage} of the * Converts the {@code RenderedImage} to a {@code BufferedImage} of the
* given type. * given type.
* <p/> * <p/>
* If the image is allready a {@code BufferedImage} of the given type, it * If the image is already a {@code BufferedImage} of the given type, it
* is simply returned and no conversion takes place. * is simply returned and no conversion takes place.
* *
* @param pOriginal the image to convert. * @param pOriginal the image to convert.
@@ -297,7 +298,7 @@ public final class ImageUtil {
* @see java.awt.image.BufferedImage#getType() * @see java.awt.image.BufferedImage#getType()
*/ */
public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) { public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) {
// Don't convert if it allready is BufferedImage and correct type // Don't convert if it already is BufferedImage and correct type
if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) { if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) {
return (BufferedImage) pOriginal; return (BufferedImage) pOriginal;
} }
@@ -329,7 +330,7 @@ public final class ImageUtil {
* given type. The new image will have the same {@code ColorModel}, * given type. The new image will have the same {@code ColorModel},
* {@code Raster} and properties as the original image, if possible. * {@code Raster} and properties as the original image, if possible.
* <p/> * <p/>
* If the image is allready a {@code BufferedImage} of the given type, it * If the image is already a {@code BufferedImage} of the given type, it
* is simply returned and no conversion takes place. * is simply returned and no conversion takes place.
* <p/> * <p/>
* This method simply invokes * This method simply invokes
@@ -354,7 +355,7 @@ public final class ImageUtil {
* The new image will have the same {@code ColorModel}, {@code Raster} and * The new image will have the same {@code ColorModel}, {@code Raster} and
* properties as the original image, if possible. * properties as the original image, if possible.
* <p/> * <p/>
* If the image is allready a {@code BufferedImage}, it is simply returned * If the image is already a {@code BufferedImage}, it is simply returned
* and no conversion takes place. * and no conversion takes place.
* *
* @param pOriginal the image to convert. * @param pOriginal the image to convert.
@@ -365,7 +366,7 @@ public final class ImageUtil {
* @throws ImageConversionException if the image cannot be converted * @throws ImageConversionException if the image cannot be converted
*/ */
public static BufferedImage toBuffered(Image pOriginal) { public static BufferedImage toBuffered(Image pOriginal) {
// Don't convert if it allready is BufferedImage // Don't convert if it already is BufferedImage
if (pOriginal instanceof BufferedImage) { if (pOriginal instanceof BufferedImage) {
return (BufferedImage) pOriginal; return (BufferedImage) pOriginal;
} }
@@ -536,32 +537,45 @@ public final class ImageUtil {
* *
* @param pOriginal the orignal image * @param pOriginal the orignal image
* @param pModel the original color model * @param pModel the original color model
* @param mWidth the requested width of the raster * @param width the requested width of the raster
* @param mHeight the requested height of the raster * @param height the requested height of the raster
* *
* @return a new WritableRaster * @return a new WritableRaster
*/ */
static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int mWidth, int mHeight) { static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int width, int height) {
if (pModel == null || equals(pOriginal.getColorModel(), pModel)) { if (pModel == null || equals(pOriginal.getColorModel(), pModel)) {
int[] bOffs;
switch (pOriginal.getType()) { switch (pOriginal.getType()) {
case BufferedImage.TYPE_3BYTE_BGR: case BufferedImage.TYPE_3BYTE_BGR:
int[] bOffs = {2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return bOffs = new int[]{2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
mWidth, mHeight, width, height,
mWidth * 3, 3, width * 3, 3,
bOffs, null); bOffs, null);
case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_4BYTE_ABGR:
case BufferedImage.TYPE_4BYTE_ABGR_PRE: case BufferedImage.TYPE_4BYTE_ABGR_PRE:
bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
mWidth, mHeight, width, height,
mWidth * 4, 4, width * 4, 4,
bOffs, null); bOffs, null);
case BufferedImage.TYPE_CUSTOM:
// Peek into the sample model to see if we have a sample model that will be incompatible with the default case
SampleModel sm = pOriginal.getRaster().getSampleModel();
if (sm instanceof ComponentSampleModel) {
bOffs = ((ComponentSampleModel) sm).getBandOffsets();
return Raster.createInterleavedRaster(sm.getDataType(),
width, height,
width * bOffs.length, bOffs.length,
bOffs, null);
}
// Else fall through
default: default:
return pOriginal.getColorModel().createCompatibleWritableRaster(mWidth, mHeight); return pOriginal.getColorModel().createCompatibleWritableRaster(width, height);
} }
} }
return pModel.createCompatibleWritableRaster(mWidth, mHeight);
return pModel.createCompatibleWritableRaster(width, height);
} }
/** /**
@@ -569,7 +583,7 @@ public final class ImageUtil {
* The new image will have the same {@code ColorModel}, {@code Raster} and * The new image will have the same {@code ColorModel}, {@code Raster} and
* properties as the original image, if possible. * properties as the original image, if possible.
* <p/> * <p/>
* If the image is allready a {@code BufferedImage} of the given type, it * If the image is already a {@code BufferedImage} of the given type, it
* is simply returned and no conversion takes place. * is simply returned and no conversion takes place.
* *
* @param pOriginal the image to convert. * @param pOriginal the image to convert.
@@ -597,7 +611,7 @@ public final class ImageUtil {
* the color model * the color model
*/ */
private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) { private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) {
// Don't convert if it allready is BufferedImage and correct type // Don't convert if it already is BufferedImage and correct type
if ((pOriginal instanceof BufferedImage) if ((pOriginal instanceof BufferedImage)
&& ((BufferedImage) pOriginal).getType() == pType && ((BufferedImage) pOriginal).getType() == pType
&& (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) { && (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) {
@@ -784,7 +798,7 @@ public final class ImageUtil {
* Creates a scaled instance of the given {@code Image}, and converts it to * Creates a scaled instance of the given {@code Image}, and converts it to
* a {@code BufferedImage} if needed. * a {@code BufferedImage} if needed.
* If the original image is a {@code BufferedImage} the result will have * If the original image is a {@code BufferedImage} the result will have
* same type and colormodel. Note that this implies overhead, and is * same type and color model. Note that this implies overhead, and is
* probably not useful for anything but {@code IndexColorModel} images. * probably not useful for anything but {@code IndexColorModel} images.
* *
* @param pImage the {@code Image} to scale * @param pImage the {@code Image} to scale
@@ -820,7 +834,7 @@ public final class ImageUtil {
BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints); BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints);
// Convert if colormodels or type differ, to behave as documented // Convert if color models or type differ, to behave as documented
if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) { if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) {
//System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... "); //System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... ");
//long start = System.currentTimeMillis(); //long start = System.currentTimeMillis();
@@ -965,9 +979,6 @@ public final class ImageUtil {
} }
private static int convertAWTHints(int pHints) { private static int convertAWTHints(int pHints) {
// TODO: These conversions are broken!
// box == area average
// point == replicate (or..?)
switch (pHints) { switch (pHints) {
case Image.SCALE_FAST: case Image.SCALE_FAST:
case Image.SCALE_REPLICATE: case Image.SCALE_REPLICATE:
@@ -89,6 +89,8 @@ import java.awt.image.BufferedImage;
import java.awt.image.ColorModel; import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel; import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage; import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -127,7 +129,7 @@ import java.util.List;
* @author <A href="mailto:deweese@apache.org">Thomas DeWeese</A> * @author <A href="mailto:deweese@apache.org">Thomas DeWeese</A>
* @author <A href="mailto:jun@oop-reserch.com">Jun Inamori</A> * @author <A href="mailto:jun@oop-reserch.com">Jun Inamori</A>
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/IndexImage.java#1 $ * @version $Id: IndexImage.java#1 $
* @see DiffusionDither * @see DiffusionDither
*/ */
class IndexImage { class IndexImage {
@@ -237,19 +239,22 @@ class IndexImage {
if (this.val != val) { if (this.val != val) {
return false; return false;
} }
count++; count++;
return true; return true;
} }
} }
/** /**
* Used to define a cube of the colorspace. The cube can be split * Used to define a cube of the color space. The cube can be split
* approximagely in half to generate two cubes. * approximately in half to generate two cubes.
*/ */
private static class Cube { private static class Cube {
int[] min = {0, 0, 0}, max = {255, 255, 255}; int[] min = {0, 0, 0};
int[] max = {255, 255, 255};
boolean done = false; boolean done = false;
List[] colors = null; List<Counter>[] colors = null;
int count = 0; int count = 0;
static final int RED = 0; static final int RED = 0;
static final int GRN = 1; static final int GRN = 1;
@@ -261,7 +266,7 @@ class IndexImage {
* @param colors contains the 3D color histogram to be subdivided * @param colors contains the 3D color histogram to be subdivided
* @param count the total number of pixels in the 3D histogram. * @param count the total number of pixels in the 3D histogram.
*/ */
public Cube(List[] colors, int count) { public Cube(List<Counter>[] colors, int count) {
this.colors = colors; this.colors = colors;
this.count = count; this.count = count;
} }
@@ -312,20 +317,27 @@ class IndexImage {
c0 = RED; c0 = RED;
c1 = GRN; c1 = GRN;
} }
Cube ret; Cube ret;
ret = splitChannel(splitChannel, c0, c1); ret = splitChannel(splitChannel, c0, c1);
if (ret != null) { if (ret != null) {
return ret; return ret;
} }
ret = splitChannel(c0, splitChannel, c1); ret = splitChannel(c0, splitChannel, c1);
if (ret != null) { if (ret != null) {
return ret; return ret;
} }
ret = splitChannel(c1, splitChannel, c0); ret = splitChannel(c1, splitChannel, c0);
if (ret != null) { if (ret != null) {
return ret; return ret;
} }
done = true; done = true;
return null; return null;
@@ -381,16 +393,13 @@ class IndexImage {
for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) { for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) {
int idx = idx2 | (k << c1Sh4); int idx = idx2 | (k << c1Sh4);
List v = colors[idx]; List<Counter> v = colors[idx];
if (v == null) { if (v == null) {
continue; continue;
} }
Iterator itr = v.iterator();
Counter c; for (Counter c : v) {
while (itr.hasNext()) {
c = (Counter) itr.next();
val = c.val; val = c.val;
vals[0] = (val & 0xFF0000) >> 16; vals[0] = (val & 0xFF0000) >> 16;
vals[1] = (val & 0xFF00) >> 8; vals[1] = (val & 0xFF00) >> 8;
@@ -425,7 +434,6 @@ class IndexImage {
int c = counts[i]; int c = counts[i];
if (c == 0) { if (c == 0) {
// No counts below this so move up bottom of cube. // No counts below this so move up bottom of cube.
if ((tcount == 0) && (i < max[splitChannel])) { if ((tcount == 0) && (i < max[splitChannel])) {
this.min[splitChannel] = i + 1; this.min[splitChannel] = i + 1;
@@ -438,10 +446,8 @@ class IndexImage {
continue; continue;
} }
if ((half - tcount) <= ((tcount + c) - half)) { if ((half - tcount) <= ((tcount + c) - half)) {
// Then lastAdd is a better top idx for this then i. // Then lastAdd is a better top idx for this then i.
if (lastAdd == -1) { if (lastAdd == -1) {
// No lower place to break. // No lower place to break.
if (c == this.count) { if (c == this.count) {
@@ -465,13 +471,11 @@ class IndexImage {
else { else {
if (i == this.max[splitChannel]) { if (i == this.max[splitChannel]) {
if (c == this.count) { if (c == this.count) {
// would move min up but that should // would move min up but that should
// have happened already. // have happened already.
return null;// no split to make. return null;// no split to make.
} }
else { else {
// Would like to break between i and i+1 // Would like to break between i and i+1
// but no i+1 so use lastAdd and i; // but no i+1 so use lastAdd and i;
splitLo = lastAdd; splitLo = lastAdd;
@@ -503,6 +507,7 @@ class IndexImage {
ret.max[c0] = this.max[c0]; ret.max[c0] = this.max[c0];
ret.min[c1] = this.min[c1]; ret.min[c1] = this.min[c1];
ret.max[c1] = this.max[c1]; ret.max[c1] = this.max[c1];
return ret; return ret;
} }
@@ -515,6 +520,7 @@ class IndexImage {
if (this.count == 0) { if (this.count == 0) {
return 0; return 0;
} }
float red = 0, grn = 0, blu = 0; float red = 0, grn = 0, blu = 0;
int minR = min[0], minG = min[1], minB = min[2]; int minR = min[0], minG = min[1], minB = min[2];
int maxR = max[0], maxG = max[1], maxB = max[2]; int maxR = max[0], maxG = max[1], maxB = max[2];
@@ -531,20 +537,18 @@ class IndexImage {
for (int k = minIdx[2]; k <= maxIdx[2]; k++) { for (int k = minIdx[2]; k <= maxIdx[2]; k++) {
int idx = idx2 | k; int idx = idx2 | k;
List v = colors[idx]; List<Counter> v = colors[idx];
if (v == null) { if (v == null) {
continue; continue;
} }
Iterator itr = v.iterator();
Counter c;
while (itr.hasNext()) { for (Counter c : v) {
c = (Counter) itr.next();
val = c.val; val = c.val;
ired = (val & 0xFF0000) >> 16; ired = (val & 0xFF0000) >> 16;
igrn = (val & 0x00FF00) >> 8; igrn = (val & 0x00FF00) >> 8;
iblu = (val & 0x0000FF); iblu = (val & 0x0000FF);
if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) { if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) {
weight = (c.count / (float) this.count); weight = (c.count / (float) this.count);
red += ((float) ired) * weight; red += ((float) ired) * weight;
@@ -579,10 +583,7 @@ class IndexImage {
* This version will be removed in a later version of the API. * This version will be removed in a later version of the API.
*/ */
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) { public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
return getIndexColorModel(pImage, pNumberOfColors, pFast
? COLOR_SELECTION_FAST
: COLOR_SELECTION_QUALITY);
} }
/** /**
@@ -636,17 +637,12 @@ class IndexImage {
// We now have at least a buffered image, create model from it // We now have at least a buffered image, create model from it
if (icm == null) { if (icm == null) {
icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints); icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints);
}
//System.out.println("IndexColorModel created from colors.");
}
else if (!(icm instanceof InverseColorMapIndexColorModel)) { else if (!(icm instanceof InverseColorMapIndexColorModel)) {
// If possible, use faster code // If possible, use faster code
//System.out.println("Wrappimg IndexColorModel in InverseColorMapIndexColorModel");
icm = new InverseColorMapIndexColorModel(icm); icm = new InverseColorMapIndexColorModel(icm);
} }
//else {
//System.out.println("Allredy InverseColorMapIndexColorModel");
//}
return icm; return icm;
} }
@@ -674,7 +670,8 @@ class IndexImage {
int height = pImage.getHeight(); int height = pImage.getHeight();
// Using 4 bits from R, G & B. // Using 4 bits from R, G & B.
List[] colors = new List[1 << 12];// [4096] @SuppressWarnings("unchecked")
List<Counter>[] colors = new List[1 << 12];// [4096]
// Speedup, doesn't decrease image quality much // Speedup, doesn't decrease image quality much
int step = 1; int step = 1;
@@ -739,13 +736,16 @@ class IndexImage {
while (numberOfCubes < pNumberOfColors) { while (numberOfCubes < pNumberOfColors) {
while (cubes[fCube].isDone()) { while (cubes[fCube].isDone()) {
fCube++; fCube++;
if (fCube == numberOfCubes) { if (fCube == numberOfCubes) {
break; break;
} }
} }
if (fCube == numberOfCubes) { if (fCube == numberOfCubes) {
break; break;
} }
Cube cube = cubes[fCube]; Cube cube = cubes[fCube];
Cube newCube = cube.split(); Cube newCube = cube.split();
@@ -756,6 +756,7 @@ class IndexImage {
cube = newCube; cube = newCube;
newCube = tmp; newCube = tmp;
} }
int j = fCube; int j = fCube;
int count = cube.count; int count = cube.count;
@@ -765,17 +766,19 @@ class IndexImage {
} }
cubes[j++] = cubes[i]; cubes[j++] = cubes[i];
} }
cubes[j++] = cube; cubes[j++] = cube;
count = newCube.count; count = newCube.count;
while (j < numberOfCubes) { while (j < numberOfCubes) {
if (cubes[j].count < count) { if (cubes[j].count < count) {
break; break;
} }
j++; j++;
} }
for (int i = numberOfCubes; i > j; i--) {
cubes[i] = cubes[i - 1]; System.arraycopy(cubes, j, cubes, j + 1, numberOfCubes - j);
}
cubes[j/*++*/] = newCube; cubes[j/*++*/] = newCube;
numberOfCubes++; numberOfCubes++;
} }
@@ -803,15 +806,13 @@ class IndexImage {
// - transparency added to all totally black colors? // - transparency added to all totally black colors?
int numOfBits = 8; int numOfBits = 8;
// -- haraldK, 20021024, as suggested by Thomas E Deweese // -- haraldK, 20021024, as suggested by Thomas E. Deweese
// plus adding a transparent pixel // plus adding a transparent pixel
IndexColorModel icm; IndexColorModel icm;
if (useTransparency) { if (useTransparency) {
//icm = new IndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1); icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
} }
else { else {
//icm = new IndexColorModel(numOfBits, r.length, r, g, b);
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b); icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b);
} }
return icm; return icm;
@@ -925,7 +926,7 @@ class IndexImage {
* @see IndexColorModel * @see IndexColorModel
*/ */
public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) { public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) {
// NOTE: We need to apply matte before creating colormodel, otherwise we // NOTE: We need to apply matte before creating color model, otherwise we
// won't have colors for potential faded transitions // won't have colors for potential faded transitions
IndexColorModel icm; IndexColorModel icm;
@@ -985,15 +986,16 @@ class IndexImage {
final int width = pImage.getWidth(); final int width = pImage.getWidth();
final int height = pImage.getHeight(); final int height = pImage.getHeight();
// Support transparancy? // Support transparency?
boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE); boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE);
// Create image with solid background // Create image with solid background
BufferedImage solid = pImage; BufferedImage solid = pImage;
if (pMatte != null) {// transparency doesn't really matter if (pMatte != null) { // transparency doesn't really matter
solid = createSolid(pImage, pMatte); solid = createSolid(pImage, pMatte);
} }
BufferedImage indexed; BufferedImage indexed;
// Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default // Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default
@@ -1044,12 +1046,12 @@ class IndexImage {
finally { finally {
g2d.dispose(); g2d.dispose();
} }
break; break;
} }
// Transparency support, this approach seems lame, but it's the only // Transparency support, this approach seems lame, but it's the only
// solution I've found until now (that actually works). // solution I've found until now (that actually works).
// Got anything to do with isPremultiplied? Hmm...
if (transparency) { if (transparency) {
// Re-apply the alpha-channel of the original image // Re-apply the alpha-channel of the original image
applyAlpha(indexed, pImage); applyAlpha(indexed, pImage);
@@ -1183,14 +1185,14 @@ class IndexImage {
* @param pAlpha the image containing the alpha * @param pAlpha the image containing the alpha
*/ */
private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) { private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) {
// Apply alpha as transparancy, using threshold of 25% // Apply alpha as transparency, using threshold of 25%
for (int y = 0; y < pAlpha.getHeight(); y++) { for (int y = 0; y < pAlpha.getHeight(); y++) {
for (int x = 0; x < pAlpha.getWidth(); x++) { for (int x = 0; x < pAlpha.getWidth(); x++) {
// Get alpha component of pixel, if less than 25% opaque // Get alpha component of pixel, if less than 25% opaque
// (0x40 = 64 => 25% of 256), the pixel will be transparent // (0x40 = 64 => 25% of 256), the pixel will be transparent
if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) { if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) {
pImage.setRGB(x, y, 0x00FFFFFF);// 100% transparent pImage.setRGB(x, y, 0x00FFFFFF); // 100% transparent
} }
} }
} }
@@ -1200,7 +1202,6 @@ class IndexImage {
* This class is also a command-line utility. * This class is also a command-line utility.
*/ */
public static void main(String pArgs[]) { public static void main(String pArgs[]) {
// Defaults // Defaults
int argIdx = 0; int argIdx = 0;
int speedTest = -1; int speedTest = -1;
@@ -1237,14 +1238,13 @@ class IndexImage {
speedTest = 10; speedTest = 10;
} }
} }
else else if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
overWrite = true; overWrite = true;
argIdx++; argIdx++;
} }
else else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
argIdx++; argIdx++;
try { try {
numColors = Integer.parseInt(pArgs[argIdx++]); numColors = Integer.parseInt(pArgs[argIdx++]);
} }
@@ -1253,34 +1253,28 @@ class IndexImage {
break; break;
} }
} }
else else if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
argIdx++; argIdx++;
gray = true; gray = true;
} }
else else if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
argIdx++; argIdx++;
numColors = 2; numColors = 2;
monochrome = true; monochrome = true;
} }
else else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
argIdx++; argIdx++;
dither = pArgs[argIdx++]; dither = pArgs[argIdx++];
} }
else else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
argIdx++; argIdx++;
paletteFileName = pArgs[argIdx++]; paletteFileName = pArgs[argIdx++];
} }
else else if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
argIdx++; argIdx++;
quality = pArgs[argIdx++]; quality = pArgs[argIdx++];
} }
else else if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
argIdx++; argIdx++;
try { try {
background = StringUtil.toColor(pArgs[argIdx++]); background = StringUtil.toColor(pArgs[argIdx++]);
@@ -1290,18 +1284,15 @@ class IndexImage {
break; break;
} }
} }
else else if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
argIdx++; argIdx++;
transparency = true; transparency = true;
} }
else else if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
argIdx++; argIdx++;
format = StringUtil.toLowerCase(pArgs[argIdx++]); format = StringUtil.toLowerCase(pArgs[argIdx++]);
} }
else else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
argIdx++; argIdx++;
// Setting errArgs to true, to print usage // Setting errArgs to true, to print usage
@@ -1321,6 +1312,7 @@ class IndexImage {
? ", " ? ", "
: "\n")); : "\n"));
} }
System.err.print("Output format names: "); System.err.print("Output format names: ");
String[] writers = ImageIO.getWriterFormatNames(); String[] writers = ImageIO.getWriterFormatNames();
@@ -1333,7 +1325,7 @@ class IndexImage {
} }
// Read in image // Read in image
java.io.File in = new java.io.File(pArgs[argIdx++]); File in = new File(pArgs[argIdx++]);
if (!in.exists()) { if (!in.exists()) {
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!"); System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
@@ -1341,10 +1333,10 @@ class IndexImage {
} }
// Read palette if needed // Read palette if needed
java.io.File paletteFile = null; File paletteFile = null;
if (paletteFileName != null) { if (paletteFileName != null) {
paletteFile = new java.io.File(paletteFileName); paletteFile = new File(paletteFileName);
if (!paletteFile.exists()) { if (!paletteFile.exists()) {
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!"); System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
System.exit(5); System.exit(5);
@@ -1352,10 +1344,10 @@ class IndexImage {
} }
// Make sure we can write // Make sure we can write
java.io.File out; File out;
if (argIdx < pArgs.length) { if (argIdx < pArgs.length) {
out = new java.io.File(pArgs[argIdx/*++*/]); out = new File(pArgs[argIdx/*++*/]);
// Get format from file extension // Get format from file extension
if (format == null) { if (format == null) {
@@ -1363,7 +1355,6 @@ class IndexImage {
} }
} }
else { else {
// Create new file in current dir, same name + format extension // Create new file in current dir, same name + format extension
String baseName = FileUtil.getBasename(in); String baseName = FileUtil.getBasename(in);
@@ -1371,8 +1362,9 @@ class IndexImage {
if (format == null) { if (format == null) {
format = "png"; format = "png";
} }
out = new java.io.File(baseName + '.' + format); out = new File(baseName + '.' + format);
} }
if (!overWrite && out.exists()) { if (!overWrite && out.exists()) {
System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!"); System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!");
System.exit(5); System.exit(5);
@@ -1396,7 +1388,7 @@ class IndexImage {
} }
} }
} }
catch (java.io.IOException ioe) { catch (IOException ioe) {
ioe.printStackTrace(System.err); ioe.printStackTrace(System.err);
System.exit(5); System.exit(5);
} }
@@ -1441,16 +1433,14 @@ class IndexImage {
/////////////////////////////// ///////////////////////////////
// Index // Index
long start = 0; long start = 0;
long end;
if (speedTest > 0) { if (speedTest > 0) {
// SPEED TESTING // SPEED TESTING
System.out.println("Measuring speed!"); System.out.println("Measuring speed!");
start = System.currentTimeMillis(); start = System.currentTimeMillis();
// END SPEED TESTING // END SPEED TESTING
} }
BufferedImage indexed; BufferedImage indexed;
IndexColorModel colors; IndexColorModel colors;
@@ -1459,7 +1449,6 @@ class IndexImage {
colors = MonochromeColorModel.getInstance(); colors = MonochromeColorModel.getInstance();
} }
else if (gray) { else if (gray) {
//indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY); //indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY);
image = ImageUtil.toBuffered(ImageUtil.grayscale(image)); image = ImageUtil.toBuffered(ImageUtil.grayscale(image));
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints); indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
@@ -1470,7 +1459,6 @@ class IndexImage {
} }
} }
else if (paletteImg != null) { else if (paletteImg != null) {
// Get palette from image // Get palette from image
indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB), indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB),
colors = getIndexColorModel(paletteImg, numColors, hints), background, hints); colors = getIndexColorModel(paletteImg, numColors, hints), background, hints);
@@ -1479,12 +1467,10 @@ class IndexImage {
image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB); image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB);
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints); indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
} }
if (speedTest > 0) { if (speedTest > 0) {
// SPEED TESTING // SPEED TESTING
end = System.currentTimeMillis(); System.out.println("Color selection + dither: " + (System.currentTimeMillis() - start) + " ms");
System.out.println("Color selection + dither: " + (end - start) + " ms");
// END SPEED TESTING // END SPEED TESTING
} }
@@ -1494,11 +1480,11 @@ class IndexImage {
System.err.println("No writer for format: \"" + format + "\"!"); System.err.println("No writer for format: \"" + format + "\"!");
} }
} }
catch (java.io.IOException ioe) { catch (IOException ioe) {
ioe.printStackTrace(System.err); ioe.printStackTrace(System.err);
} }
if (speedTest > 0) {
if (speedTest > 0) {
// SPEED TESTING // SPEED TESTING
System.out.println("Measuring speed!"); System.out.println("Measuring speed!");
@@ -1513,17 +1499,16 @@ class IndexImage {
for (int i = 0; i < speedTest; i++) { for (int i = 0; i < speedTest; i++) {
start = System.currentTimeMillis(); start = System.currentTimeMillis();
getIndexedImage(image, colors, background, hints); getIndexedImage(image, colors, background, hints);
end = System.currentTimeMillis(); time += (System.currentTimeMillis() - start);
time += (end - start);
System.out.print('.'); System.out.print('.');
if ((i + 1) % 10 == 0) { if ((i + 1) % 10 == 0) {
System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms"); System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms");
} }
} }
System.out.println("\nDither only:"); System.out.println("\nDither only:");
System.out.println("Total time (" + speedTest + " invocations): " + time + "ms"); System.out.println("Total time (" + speedTest + " invocations): " + time + "ms");
System.out.println("Average: " + time / speedTest + "ms"); System.out.println("Average: " + time / speedTest + "ms");
// END SPEED TESTING // END SPEED TESTING
} }
} }
@@ -72,12 +72,12 @@ class InverseColorMap {
*/ */
final static int MAXQUANTVAL = 1 << 5; final static int MAXQUANTVAL = 1 << 5;
byte[] mRGBMapByte; byte[] rgbMapByte;
int[] mRGBMapInt; int[] rgbMapInt;
int mNumColors; int numColors;
int mMaxColor; int maxColor;
byte[] mInverseRGB; // inverse rgb color map byte[] inverseRGB; // inverse rgb color map
int mTransparentIndex = -1; int transparentIndex = -1;
/** /**
* @param pRGBColorMap the rgb color map to create inverse color map for. * @param pRGBColorMap the rgb color map to create inverse color map for.
@@ -99,11 +99,11 @@ class InverseColorMap {
* @param pTransparent the index of the transparent pixel in the map * @param pTransparent the index of the transparent pixel in the map
*/ */
InverseColorMap(byte[] pRGBColorMap, int pTransparent) { InverseColorMap(byte[] pRGBColorMap, int pTransparent) {
mRGBMapByte = pRGBColorMap; rgbMapByte = pRGBColorMap;
mNumColors = mRGBMapByte.length / 4; numColors = rgbMapByte.length / 4;
mTransparentIndex = pTransparent; transparentIndex = pTransparent;
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]; inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]); initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
} }
@@ -112,11 +112,11 @@ class InverseColorMap {
* @param pTransparent the index of the transparent pixel in the map * @param pTransparent the index of the transparent pixel in the map
*/ */
InverseColorMap(int[] pRGBColorMap, int pTransparent) { InverseColorMap(int[] pRGBColorMap, int pTransparent) {
mRGBMapInt = pRGBColorMap; rgbMapInt = pRGBColorMap;
mNumColors = mRGBMapInt.length; numColors = rgbMapInt.length;
mTransparentIndex = pTransparent; transparentIndex = pTransparent;
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]; inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]); initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
} }
@@ -130,8 +130,8 @@ class InverseColorMap {
final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors
final int xsqr2 = xsqr + xsqr; final int xsqr2 = xsqr + xsqr;
for (int i = 0; i < mNumColors; ++i) { for (int i = 0; i < numColors; ++i) {
if (i == mTransparentIndex) { if (i == transparentIndex) {
// Skip the transparent pixel // Skip the transparent pixel
continue; continue;
} }
@@ -141,15 +141,15 @@ class InverseColorMap {
int blue, b, bdist, binc, bxx; int blue, b, bdist, binc, bxx;
// HaraldK 20040801: Added support for int[] // HaraldK 20040801: Added support for int[]
if (mRGBMapByte != null) { if (rgbMapByte != null) {
red = mRGBMapByte[i * 4] & 0xFF; red = rgbMapByte[i * 4] & 0xFF;
green = mRGBMapByte[i * 4 + 1] & 0xFF; green = rgbMapByte[i * 4 + 1] & 0xFF;
blue = mRGBMapByte[i * 4 + 2] & 0xFF; blue = rgbMapByte[i * 4 + 2] & 0xFF;
} }
else if (mRGBMapInt != null) { else if (rgbMapInt != null) {
red = (mRGBMapInt[i] >> 16) & 0xFF; red = (rgbMapInt[i] >> 16) & 0xFF;
green = (mRGBMapInt[i] >> 8) & 0xFF; green = (rgbMapInt[i] >> 8) & 0xFF;
blue = mRGBMapInt[i] & 0xFF; blue = rgbMapInt[i] & 0xFF;
} }
else { else {
throw new IllegalStateException("colormap == null"); throw new IllegalStateException("colormap == null");
@@ -170,7 +170,7 @@ class InverseColorMap {
for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) { for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) {
if (i == 0 || pTemp[rgbI] > bdist) { if (i == 0 || pTemp[rgbI] > bdist) {
pTemp[rgbI] = bdist; pTemp[rgbI] = bdist;
mInverseRGB[rgbI] = (byte) i; inverseRGB[rgbI] = (byte) i;
} }
} }
} }
@@ -187,7 +187,7 @@ class InverseColorMap {
* created inverse color map. * created inverse color map.
*/ */
public final int getIndexNearest(int pColor) { public final int getIndexNearest(int pColor) {
return mInverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) + return inverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) +
((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) + ((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) +
((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF; ((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
} }
@@ -203,7 +203,7 @@ class InverseColorMap {
*/ */
public final int getIndexNearest(int pRed, int pGreen, int pBlue) { public final int getIndexNearest(int pRed, int pGreen, int pBlue) {
// NOTE: the third line in expression for blue is shifting DOWN not UP. // NOTE: the third line in expression for blue is shifting DOWN not UP.
return mInverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) + return inverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) +
((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) + ((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) +
((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF; ((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
} }
@@ -46,13 +46,13 @@ import java.awt.image.IndexColorModel;
*/ */
public class InverseColorMapIndexColorModel extends IndexColorModel { public class InverseColorMapIndexColorModel extends IndexColorModel {
protected int mRGBs[]; protected int rgbs[];
protected int mMapSize; protected int mapSize;
protected InverseColorMap mInverseMap = null; protected InverseColorMap inverseMap = null;
private final static int ALPHA_THRESHOLD = 0x80; private final static int ALPHA_THRESHOLD = 0x80;
private int mWhiteIndex = -1; private int whiteIndex = -1;
private final static int WHITE = 0x00FFFFFF; private final static int WHITE = 0x00FFFFFF;
private final static int RGB_MASK = 0x00FFFFFF; private final static int RGB_MASK = 0x00FFFFFF;
@@ -74,11 +74,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
ImageUtil.getTransferType(pColorModel), ImageUtil.getTransferType(pColorModel),
pColorModel.getValidPixels()); pColorModel.getValidPixels());
mRGBs = pRGBs; rgbs = pRGBs;
mMapSize = mRGBs.length; mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs); inverseMap = new InverseColorMap(rgbs);
mWhiteIndex = getWhiteIndex(); whiteIndex = getWhiteIndex();
} }
/** /**
@@ -91,6 +91,7 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
private static int[] getRGBs(IndexColorModel pColorModel) { private static int[] getRGBs(IndexColorModel pColorModel) {
int[] rgb = new int[pColorModel.getMapSize()]; int[] rgb = new int[pColorModel.getMapSize()];
pColorModel.getRGBs(rgb); pColorModel.getRGBs(rgb);
return rgb; return rgb;
} }
@@ -111,15 +112,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
* *
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int) * @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
*/ */
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs, public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs, int pStart, boolean pAlpha, int pTransparentIndex, int pTransferType) {
int pStart, boolean pAlpha, int pTransparentIndex,
int pTransferType) {
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType); super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
mRGBs = getRGBs(this); rgbs = getRGBs(this);
mMapSize = mRGBs.length; mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex); inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
mWhiteIndex = getWhiteIndex(); whiteIndex = getWhiteIndex();
} }
/** /**
@@ -138,15 +137,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
* *
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int) * @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
*/ */
public InverseColorMapIndexColorModel(int pNumBits, int pSize, public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues, int pTransparentIndex) {
byte[] pReds, byte[] pGreens, byte[] pBlues,
int pTransparentIndex) {
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex); super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
mRGBs = getRGBs(this); rgbs = getRGBs(this);
mMapSize = mRGBs.length; mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex); inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
mWhiteIndex = getWhiteIndex(); whiteIndex = getWhiteIndex();
} }
/** /**
@@ -164,19 +161,18 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
* *
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[]) * @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
*/ */
public InverseColorMapIndexColorModel(int pNumBits, int pSize, public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues) {
byte[] pReds, byte[] pGreens, byte[] pBlues) {
super(pNumBits, pSize, pReds, pGreens, pBlues); super(pNumBits, pSize, pReds, pGreens, pBlues);
mRGBs = getRGBs(this); rgbs = getRGBs(this);
mMapSize = mRGBs.length; mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs); inverseMap = new InverseColorMap(rgbs);
mWhiteIndex = getWhiteIndex(); whiteIndex = getWhiteIndex();
} }
private int getWhiteIndex() { private int getWhiteIndex() {
for (int i = 0; i < mRGBs.length; i++) { for (int i = 0; i < rgbs.length; i++) {
int color = mRGBs[i]; int color = rgbs[i];
if ((color & RGB_MASK) == WHITE) { if ((color & RGB_MASK) == WHITE) {
return i; return i;
} }
@@ -244,7 +240,6 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
* *
*/ */
public Object getDataElements(int rgb, Object pixel) { public Object getDataElements(int rgb, Object pixel) {
int alpha = (rgb>>>24); int alpha = (rgb>>>24);
int pix; int pix;
@@ -253,11 +248,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
} }
else { else {
int color = rgb & RGB_MASK; int color = rgb & RGB_MASK;
if (color == WHITE && mWhiteIndex != -1) { if (color == WHITE && whiteIndex != -1) {
pix = mWhiteIndex; pix = whiteIndex;
} }
else { else {
pix = mInverseMap.getIndexNearest(color); pix = inverseMap.getIndexNearest(color);
} }
} }
@@ -297,8 +292,7 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
shortObj[0] = (short) pix; shortObj[0] = (short) pix;
break; break;
default: default:
throw new UnsupportedOperationException("This method has not been " + throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
"implemented for transferType " + transferType);
} }
return pixel; return pixel;
} }
@@ -54,11 +54,11 @@ final class MagickAccelerator {
private static final int RESAMPLE_OP = 0; private static final int RESAMPLE_OP = 0;
private static Class[] sNativeOp = new Class[1]; private static Class[] nativeOp = new Class[1];
static { static {
try { try {
sNativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp"); nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
} }
catch (ClassNotFoundException e) { catch (ClassNotFoundException e) {
System.err.println("Could not find class: " + e); System.err.println("Could not find class: " + e);
@@ -94,8 +94,8 @@ final class MagickAccelerator {
} }
private static int getNativeOpIndex(Class pOpClass) { private static int getNativeOpIndex(Class pOpClass) {
for (int i = 0; i < sNativeOp.length; i++) { for (int i = 0; i < nativeOp.length; i++) {
if (pOpClass == sNativeOp[i]) { if (pOpClass == nativeOp[i]) {
return i; return i;
} }
} }
@@ -112,7 +112,7 @@ final class MagickAccelerator {
switch (getNativeOpIndex(pOperation.getClass())) { switch (getNativeOpIndex(pOperation.getClass())) {
case RESAMPLE_OP: case RESAMPLE_OP:
ResampleOp resample = (ResampleOp) pOperation; ResampleOp resample = (ResampleOp) pOperation;
result = resampleMagick(pInput, resample.mWidth, resample.mHeight, resample.mFilterType); result = resampleMagick(pInput, resample.width, resample.height, resample.filterType);
// NOTE: If output parameter is non-null, we have to return that // NOTE: If output parameter is non-null, we have to return that
// image, instead of result // image, instead of result
@@ -33,7 +33,7 @@ import java.awt.image.*;
/** /**
* Monochrome B/W color model. * Monochrome B/W color model.
* *
* @author Harald Kuhr * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/ */
public class MonochromeColorModel extends IndexColorModel { public class MonochromeColorModel extends IndexColorModel {
@@ -48,37 +48,37 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
// TODO: support more raster types/color models // TODO: support more raster types/color models
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it... // TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
final private int mPixelSizeX; final private int pixelSizeX;
final private int mPixelSizeY; final private int pixelSizeY;
private Rectangle mSourceRegion; private Rectangle sourceRegion;
public PixelizeOp(final int pPixelSize) { public PixelizeOp(final int pPixelSize) {
this(pPixelSize, pPixelSize); this(pPixelSize, pPixelSize);
} }
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) { public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
mPixelSizeX = pPixelSizeX; pixelSizeX = pPixelSizeX;
mPixelSizeY = pPixelSizeY; pixelSizeY = pPixelSizeY;
} }
public Rectangle getSourceRegion() { public Rectangle getSourceRegion() {
if (mSourceRegion == null) { if (sourceRegion == null) {
return null; return null;
} }
return new Rectangle(mSourceRegion); return new Rectangle(sourceRegion);
} }
public void setSourceRegion(final Rectangle pSourceRegion) { public void setSourceRegion(final Rectangle pSourceRegion) {
if (pSourceRegion == null) { if (pSourceRegion == null) {
mSourceRegion = null; sourceRegion = null;
} }
else { else {
if (mSourceRegion == null) { if (sourceRegion == null) {
mSourceRegion = new Rectangle(pSourceRegion); sourceRegion = new Rectangle(pSourceRegion);
} }
else { else {
mSourceRegion.setBounds(pSourceRegion); sourceRegion.setBounds(pSourceRegion);
} }
} }
} }
@@ -107,11 +107,11 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
private WritableRaster filterImpl(Raster src, WritableRaster dest) { private WritableRaster filterImpl(Raster src, WritableRaster dest) {
//System.out.println("src: " + src); //System.out.println("src: " + src);
//System.out.println("dest: " + dest); //System.out.println("dest: " + dest);
if (mSourceRegion != null) { if (sourceRegion != null) {
int cx = mSourceRegion.x; int cx = sourceRegion.x;
int cy = mSourceRegion.y; int cy = sourceRegion.y;
int cw = mSourceRegion.width; int cw = sourceRegion.width;
int ch = mSourceRegion.height; int ch = sourceRegion.height;
boolean same = src == dest; boolean same = src == dest;
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null); dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
@@ -122,8 +122,8 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
final int width = src.getWidth(); final int width = src.getWidth();
final int height = src.getHeight(); final int height = src.getHeight();
int w = (width + mPixelSizeX - 1) / mPixelSizeX; int w = (width + pixelSizeX - 1) / pixelSizeX;
int h = (height + mPixelSizeY - 1) / mPixelSizeY; int h = (height + pixelSizeY - 1) / pixelSizeY;
final boolean oddX = width % w != 0; final boolean oddX = width % w != 0;
final boolean oddY = height % h != 0; final boolean oddY = height % h != 0;
@@ -156,23 +156,23 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
if (!oddY || y + 1 < h) { if (!oddY || y + 1 < h) {
scanH = mPixelSizeY; scanH = pixelSizeY;
} }
else { else {
scanH = height - (y * mPixelSizeY); scanH = height - (y * pixelSizeY);
} }
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
if (!oddX || x + 1 < w) { if (!oddX || x + 1 < w) {
scanW = mPixelSizeX; scanW = pixelSizeX;
} }
else { else {
scanW = width - (x * mPixelSizeX); scanW = width - (x * pixelSizeX);
} }
final int pixelCount = scanW * scanH; final int pixelCount = scanW * scanH;
final int pixelLength = pixelCount * dataElements; final int pixelLength = pixelCount * dataElements;
data = src.getDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data); data = src.getDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
// NOTE: These are not neccessarily ARGB.. // NOTE: These are not neccessarily ARGB..
double valueA = 0.0; double valueA = 0.0;
@@ -277,7 +277,7 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
} }
dest.setDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data); dest.setDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
} }
} }
/*/ /*/
@@ -60,7 +60,6 @@ import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.*; import java.awt.image.*;
/** /**
* Resamples (scales) a {@code BufferedImage} to a new width and height, using * Resamples (scales) a {@code BufferedImage} to a new width and height, using
* high performance and high quality algorithms. * high performance and high quality algorithms.
@@ -138,7 +137,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// MagickAccelerator to work consistently (see magick.FilterType). // MagickAccelerator to work consistently (see magick.FilterType).
/** /**
* Undefined interpolation, filter method will use default filter * Undefined interpolation, filter method will use default filter.
*/ */
public final static int FILTER_UNDEFINED = 0; public final static int FILTER_UNDEFINED = 0;
/** /**
@@ -194,11 +193,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
/** /**
* Mitchell interpolation. High quality. * Mitchell interpolation. High quality.
*/ */
public final static int FILTER_MITCHELL = 12;// IM default scale with palette or alpha, or scale up public final static int FILTER_MITCHELL = 12; // IM default scale with palette or alpha, or scale up
/** /**
* Lanczos interpolation. High quality. * Lanczos interpolation. High quality.
*/ */
public final static int FILTER_LANCZOS = 13;// IM default public final static int FILTER_LANCZOS = 13; // IM default
/** /**
* Blackman-Bessel interpolation. High quality. * Blackman-Bessel interpolation. High quality.
*/ */
@@ -291,10 +290,10 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// Member variables // Member variables
// Package access, to allow access from MagickAccelerator // Package access, to allow access from MagickAccelerator
int mWidth; int width;
int mHeight; int height;
int mFilterType; int filterType;
private static final boolean TRANSFORM_OP_BICUBIC_SUPPORT = SystemUtil.isFieldAvailable(AffineTransformOp.class.getName(), "TYPE_BICUBIC"); private static final boolean TRANSFORM_OP_BICUBIC_SUPPORT = SystemUtil.isFieldAvailable(AffineTransformOp.class.getName(), "TYPE_BICUBIC");
/** /**
@@ -302,14 +301,13 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
*/ */
// TODO: Move to abstract class AbstractBufferedImageOp? // TODO: Move to abstract class AbstractBufferedImageOp?
static class Key extends RenderingHints.Key { static class Key extends RenderingHints.Key {
static int sIndex = 10000; static int sIndex = 10000;
private final String mName; private final String name;
public Key(final String pName) { public Key(final String pName) {
super(sIndex++); super(sIndex++);
mName = pName; name = pName;
} }
public boolean isCompatibleValue(Object pValue) { public boolean isCompatibleValue(Object pValue) {
@@ -317,7 +315,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
public String toString() { public String toString() {
return mName; return name;
} }
} }
@@ -326,27 +324,27 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
*/ */
// TODO: Extract abstract Value class, and move to AbstractBufferedImageOp // TODO: Extract abstract Value class, and move to AbstractBufferedImageOp
static final class Value { static final class Value {
final private RenderingHints.Key mKey; final private RenderingHints.Key key;
final private String mName; final private String name;
final private int mType; final private int type;
public Value(final RenderingHints.Key pKey, final String pName, final int pType) { public Value(final RenderingHints.Key pKey, final String pName, final int pType) {
mKey = pKey; key = pKey;
mName = pName; name = pName;
validateFilterType(pType); validateFilterType(pType);
mType = pType;// TODO: test for duplicates type = pType;// TODO: test for duplicates
} }
public boolean isCompatibleKey(Key pKey) { public boolean isCompatibleKey(Key pKey) {
return pKey == mKey; return pKey == key;
} }
public int getFilterType() { public int getFilterType() {
return mType; return type;
} }
public String toString() { public String toString() {
return mName; return name;
} }
} }
@@ -354,11 +352,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
* Creates a {@code ResampleOp} that will resample input images to the * Creates a {@code ResampleOp} that will resample input images to the
* given width and height, using the default interpolation filter. * given width and height, using the default interpolation filter.
* *
* @param pWidth width of the resampled image * @param width width of the re-sampled image
* @param pHeight height of the resampled image * @param height height of the re-sampled image
*/ */
public ResampleOp(int pWidth, int pHeight) { public ResampleOp(int width, int height) {
this(pWidth, pHeight, FILTER_UNDEFINED); this(width, height, FILTER_UNDEFINED);
} }
/** /**
@@ -394,38 +392,38 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
* </ul> * </ul>
* Other hints have no effect on this filter. * Other hints have no effect on this filter.
* *
* @param pWidth width of the resampled image * @param width width of the re-sampled image
* @param pHeight height of the resampled image * @param height height of the re-sampled image
* @param pHints rendering hints, affecting interpolation algorithm * @param hints rendering hints, affecting interpolation algorithm
* @see #KEY_RESAMPLE_INTERPOLATION * @see #KEY_RESAMPLE_INTERPOLATION
* @see RenderingHints#KEY_INTERPOLATION * @see RenderingHints#KEY_INTERPOLATION
* @see RenderingHints#KEY_RENDERING * @see RenderingHints#KEY_RENDERING
* @see RenderingHints#KEY_COLOR_RENDERING * @see RenderingHints#KEY_COLOR_RENDERING
*/ */
public ResampleOp(int pWidth, int pHeight, RenderingHints pHints) { public ResampleOp(int width, int height, RenderingHints hints) {
this(pWidth, pHeight, getFilterType(pHints)); this(width, height, getFilterType(hints));
} }
/** /**
* Creates a {@code ResampleOp} that will resample input images to the * Creates a {@code ResampleOp} that will resample input images to the
* given width and height, using the given interpolation filter. * given width and height, using the given interpolation filter.
* *
* @param pWidth width of the resampled image * @param width width of the re-sampled image
* @param pHeight height of the resampled image * @param height height of the re-sampled image
* @param pFilterType interpolation filter algorithm * @param filterType interpolation filter algorithm
* @see <a href="#field_summary">filter type constants</a> * @see <a href="#field_summary">filter type constants</a>
*/ */
public ResampleOp(int pWidth, int pHeight, int pFilterType) { public ResampleOp(int width, int height, int filterType) {
if (pWidth <= 0 || pHeight <= 0) { if (width <= 0 || height <= 0) {
// NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P // NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P
throw new IllegalArgumentException("width and height must be positive"); throw new IllegalArgumentException("width and height must be positive");
} }
mWidth = pWidth; this.width = width;
mHeight = pHeight; this.height = height;
validateFilterType(pFilterType); validateFilterType(filterType);
mFilterType = pFilterType; this.filterType = filterType;
} }
private static void validateFilterType(int pFilterType) { private static void validateFilterType(int pFilterType) {
@@ -471,25 +469,21 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED; return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED;
} }
else else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
|| (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION) || (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION)
&& (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING)) && (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING))
|| RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) { || RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) {
// Nearest neighbour, or prioritze speed // Nearest neighbour, or prioritize speed
return FILTER_POINT; return FILTER_POINT;
} }
else else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
// Triangle equals bi-linear interpolation // Triangle equals bi-linear interpolation
return FILTER_TRIANGLE; return FILTER_TRIANGLE;
} }
else else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
return FILTER_QUADRATIC;// No idea if this is correct..? return FILTER_QUADRATIC;// No idea if this is correct..?
} }
else else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
|| RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) { || RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) {
// Prioritize quality // Prioritize quality
return FILTER_MITCHELL; return FILTER_MITCHELL;
@@ -500,83 +494,87 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
/** /**
* Resamples (scales) the image to the size, and using the algorithm * Re-samples (scales) the image to the size, and using the algorithm
* specified in the constructor. * specified in the constructor.
* *
* @param pInput The {@code BufferedImage} to be filtered * @param input The {@code BufferedImage} to be filtered
* @param pOutput The {@code BufferedImage} in which to store the resampled * @param output The {@code BufferedImage} in which to store the resampled
* image * image
* @return The resampled {@code BufferedImage}. * @return The re-sampled {@code BufferedImage}.
* @throws NullPointerException if {@code pInput} is {@code null} * @throws NullPointerException if {@code input} is {@code null}
* @throws IllegalArgumentException if {@code pInput == pOutput}. * @throws IllegalArgumentException if {@code input == output}.
* @see #ResampleOp(int,int,int) * @see #ResampleOp(int,int,int)
*/ */
public final BufferedImage filter(final BufferedImage pInput, final BufferedImage pOutput) { public final BufferedImage filter(final BufferedImage input, final BufferedImage output) {
if (pInput == null) { if (input == null) {
throw new NullPointerException("Input == null"); throw new NullPointerException("Input == null");
} }
if (pInput == pOutput) { if (input == output) {
throw new IllegalArgumentException("Output image cannot be the same as the input image"); throw new IllegalArgumentException("Output image cannot be the same as the input image");
} }
InterpolationFilter filter; InterpolationFilter filter;
// Special case for POINT, TRIANGLE and QUADRATIC filter, as standard // Special case for POINT, TRIANGLE and QUADRATIC filter, as standard
// Java implementation is very fast (possibly H/W accellerated) // Java implementation is very fast (possibly H/W accelerated)
switch (mFilterType) { switch (filterType) {
case FILTER_POINT: case FILTER_POINT:
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); if (input.getType() != BufferedImage.TYPE_CUSTOM) {
case FILTER_TRIANGLE: return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR);
case FILTER_QUADRATIC:
if (TRANSFORM_OP_BICUBIC_SUPPORT) {
return fastResample(pInput, pOutput, mWidth, mHeight, 3); // AffineTransformOp.TYPE_BICUBIC
} }
// Fall through // Else fall through
case FILTER_TRIANGLE:
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
}
// Else fall through
case FILTER_QUADRATIC:
if (input.getType() != BufferedImage.TYPE_CUSTOM && TRANSFORM_OP_BICUBIC_SUPPORT) {
return fastResample(input, output, width, height, 3); // AffineTransformOp.TYPE_BICUBIC
}
// Else fall through
default: default:
filter = createFilter(mFilterType); filter = createFilter(filterType);
// NOTE: Workaround for filter throwing exceptions when input or output is less than support... // NOTE: Workaround for filter throwing exceptions when input or output is less than support...
if (Math.min(pInput.getWidth(), pInput.getHeight()) <= filter.support() || Math.min(mWidth, mHeight) <= filter.support()) { if (Math.min(input.getWidth(), input.getHeight()) <= filter.support() || Math.min(width, height) <= filter.support()) {
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR); return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
} }
// Fall through // Fall through
} }
// Try to use native ImageMagick code // Try to use native ImageMagick code
BufferedImage result = MagickAccelerator.filter(this, pInput, pOutput); BufferedImage result = MagickAccelerator.filter(this, input, output);
if (result != null) { if (result != null) {
return result; return result;
} }
// Otherwise, continue in pure Java mode // Otherwise, continue in pure Java mode
// TODO: What if pOutput != null and wrong size? Create new? Render on only a part? Document? // TODO: What if output != null and wrong size? Create new? Render on only a part? Document?
// If filter type != POINT or BOX an input has IndexColorModel, convert // If filter type != POINT or BOX an input has IndexColorModel, convert
// to true color, with alpha reflecting that of the original colormodel. // to true color, with alpha reflecting that of the original color model.
BufferedImage input; BufferedImage temp;
ColorModel cm; ColorModel cm;
if (mFilterType != FILTER_BOX && (cm = pInput.getColorModel()) instanceof IndexColorModel) { if (filterType != FILTER_POINT && filterType != FILTER_BOX && (cm = input.getColorModel()) instanceof IndexColorModel) {
// TODO: OPTIMIZE: If colormodel has only b/w or gray, we could skip color info // TODO: OPTIMIZE: If color model has only b/w or gray, we could skip color info
input = ImageUtil.toBuffered(pInput, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); temp = ImageUtil.toBuffered(input, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
} }
else { else {
input = pInput; temp = input;
} }
// Create or convert output to a suitable image // Create or convert output to a suitable image
// TODO: OPTIMIZE: Don't really need to convert all types to same as input // TODO: OPTIMIZE: Don't really need to convert all types to same as input
result = pOutput != null ? /*pOutput*/ ImageUtil.toBuffered(pOutput, input.getType()) : createCompatibleDestImage(input, null); result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null);
resample(input, result, filter); resample(temp, result, filter);
// If pOutput != null and needed to be converted, draw it back // If output != null and needed to be converted, draw it back
if (pOutput != null && pOutput != result) { if (output != null && output != result) {
//pOutput.setData(output.getRaster()); //output.setData(output.getRaster());
ImageUtil.drawOnto(pOutput, result); ImageUtil.drawOnto(output, result);
result = pOutput; result = output;
} }
return result; return result;
@@ -672,8 +670,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
*/ */
private static BufferedImage fastResample(final BufferedImage pInput, final BufferedImage pOutput, final int pWidth, final int pHeight, final int pType) { private static BufferedImage fastResample(final BufferedImage input, final BufferedImage output, final int width, final int height, final int type) {
BufferedImage temp = pInput; BufferedImage temp = input;
double xScale; double xScale;
double yScale; double yScale;
@@ -681,20 +679,20 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
AffineTransform transform; AffineTransform transform;
AffineTransformOp scale; AffineTransformOp scale;
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
// Initially scale so all remaining operations will halve the image // Initially scale so all remaining operations will halve the image
if (pWidth < pInput.getWidth() || pHeight < pInput.getHeight()) { if (width < input.getWidth() || height < input.getHeight()) {
int w = pWidth; int w = width;
int h = pHeight; int h = height;
while (w < pInput.getWidth() / 2) { while (w < input.getWidth() / 2) {
w *= 2; w *= 2;
} }
while (h < pInput.getHeight() / 2) { while (h < input.getHeight() / 2) {
h *= 2; h *= 2;
} }
xScale = w / (double) pInput.getWidth(); xScale = w / (double) input.getWidth();
yScale = h / (double) pInput.getHeight(); yScale = h / (double) input.getHeight();
//System.out.println("First scale by x=" + xScale + ", y=" + yScale); //System.out.println("First scale by x=" + xScale + ", y=" + yScale);
@@ -704,12 +702,12 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
} }
scale = null;// NOTE: This resets! scale = null; // NOTE: This resets!
xScale = pWidth / (double) temp.getWidth(); xScale = width / (double) temp.getWidth();
yScale = pHeight / (double) temp.getHeight(); yScale = height / (double) temp.getHeight();
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
// TODO: Test skipping first scale (above), and instead scale once // TODO: Test skipping first scale (above), and instead scale once
// more here, and a little less than .5 each time... // more here, and a little less than .5 each time...
// That would probably make the scaling smoother... // That would probably make the scaling smoother...
@@ -740,17 +738,15 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
temp = scale.filter(temp, null); temp = scale.filter(temp, null);
} }
} }
//System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale); //System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale);
transform = AffineTransform.getScaleInstance(xScale, yScale); transform = AffineTransform.getScaleInstance(xScale, yScale);
scale = new AffineTransformOp(transform, pType); scale = new AffineTransformOp(transform, type);
return scale.filter(temp, pOutput);
return scale.filter(temp, output);
} }
/** /**
@@ -760,7 +756,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
* @see <a href="#field_summary">filter type constants</a> * @see <a href="#field_summary">filter type constants</a>
*/ */
public int getFilterType() { public int getFilterType() {
return mFilterType; return filterType;
} }
private static InterpolationFilter createFilter(int pFilterType) { private static InterpolationFilter createFilter(int pFilterType) {
@@ -770,7 +766,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
} }
switch (pFilterType) { switch (pFilterType) {
//case FILTER_POINT: // Should never happen case FILTER_POINT:
return new PointFilter();
case FILTER_BOX: case FILTER_BOX:
return new BoxFilter(); return new BoxFilter();
case FILTER_TRIANGLE: case FILTER_TRIANGLE:
@@ -815,14 +812,13 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// If indexcolormodel, we probably don't want to use that... // If indexcolormodel, we probably don't want to use that...
// NOTE: Either BOTH or NONE of the images must have ALPHA // NOTE: Either BOTH or NONE of the images must have ALPHA
return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, mWidth, mHeight), return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, width, height),
cm.isAlphaPremultiplied(), null); cm.isAlphaPremultiplied(), null);
} }
public RenderingHints getRenderingHints() { public RenderingHints getRenderingHints() {
Object value; Object value;
switch (mFilterType) { switch (filterType) {
case FILTER_UNDEFINED: case FILTER_UNDEFINED:
return null; return null;
case FILTER_POINT: case FILTER_POINT:
@@ -871,14 +867,14 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
value = VALUE_INTERPOLATION_BLACKMAN_SINC; value = VALUE_INTERPOLATION_BLACKMAN_SINC;
break; break;
default: default:
throw new IllegalStateException("Unknown filter type: " + mFilterType); throw new IllegalStateException("Unknown filter type: " + filterType);
} }
return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value); return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value);
} }
public Rectangle2D getBounds2D(BufferedImage src) { public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(mWidth, mHeight); return new Rectangle(width, height);
} }
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
@@ -1439,10 +1435,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
resample() resample()
Resizes bitmaps while resampling them. Resizes bitmaps while resampling them.
Returns -1 if error, 0 if success.
*/ */
private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) { private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) {
// TODO: Don't work... Could fix by creating a temporary image in filter method
final int dstWidth = pDest.getWidth(); final int dstWidth = pDest.getWidth();
final int dstHeight = pDest.getHeight(); final int dstHeight = pDest.getHeight();
@@ -1451,7 +1445,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
/* create intermediate column to hold horizontal dst column zoom */ /* create intermediate column to hold horizontal dst column zoom */
final ColorModel cm = pSource.getColorModel(); final ColorModel cm = pSource.getColorModel();
final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight); // final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight);
final WritableRaster work = ImageUtil.createCompatibleWritableRaster(pSource, cm, 1, srcHeight);
double xscale = (double) dstWidth / (double) srcWidth; double xscale = (double) dstWidth / (double) srcWidth;
double yscale = (double) dstHeight / (double) srcHeight; double yscale = (double) dstHeight / (double) srcHeight;
@@ -1566,7 +1561,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
final WritableRaster out = pDest.getRaster(); final WritableRaster out = pDest.getRaster();
// TODO: This is not optimal for non-byte-packed rasters... // TODO: This is not optimal for non-byte-packed rasters...
// (What? Maybe I implemented the fix, but forgot to remove the qTODO?) // (What? Maybe I implemented the fix, but forgot to remove the TODO?)
final int numChannels = raster.getNumBands(); final int numChannels = raster.getNumBands();
final int[] channelMax = new int[numChannels]; final int[] channelMax = new int[numChannels];
for (int k = 0; k < numChannels; k++) { for (int k = 0; k < numChannels; k++) {
@@ -1575,7 +1570,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
for (int xx = 0; xx < dstWidth; xx++) { for (int xx = 0; xx < dstWidth; xx++) {
ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx); ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx);
/* Apply horz filter to make dst column in tmp. */ /* Apply horiz filter to make dst column in tmp. */
for (int k = 0; k < srcHeight; k++) { for (int k = 0; k < srcHeight; k++) {
for (int channel = 0; channel < numChannels; channel++) { for (int channel = 0; channel < numChannels; channel++) {
@@ -42,8 +42,8 @@ import java.awt.image.ReplicateScaleFilter;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java#1 $
*/ */
public class SubsamplingFilter extends ReplicateScaleFilter { public class SubsamplingFilter extends ReplicateScaleFilter {
private int mXSub; private int xSub;
private int mYSub; private int ySub;
/** /**
* Creates a {@code SubsamplingFilter}. * Creates a {@code SubsamplingFilter}.
@@ -62,16 +62,16 @@ public class SubsamplingFilter extends ReplicateScaleFilter {
throw new IllegalArgumentException("Subsampling factors must be positive."); throw new IllegalArgumentException("Subsampling factors must be positive.");
} }
mXSub = pXSub; xSub = pXSub;
mYSub = pYSub; ySub = pYSub;
} }
/** {@code ImageFilter} implementation, do not invoke. */ /** {@code ImageFilter} implementation, do not invoke. */
public void setDimensions(int pWidth, int pHeight) { public void setDimensions(int pWidth, int pHeight) {
destWidth = (pWidth + mXSub - 1) / mXSub; destWidth = (pWidth + xSub - 1) / xSub;
destHeight = (pHeight + mYSub - 1) / mYSub; destHeight = (pHeight + ySub - 1) / ySub;
//System.out.println("Subsampling: " + mXSub + "," + mYSub + "-> " + destWidth + ", " + destHeight); //System.out.println("Subsampling: " + xSub + "," + ySub + "-> " + destWidth + ", " + destHeight);
super.setDimensions(pWidth, pHeight); super.setDimensions(pWidth, pHeight);
} }
} }
@@ -4,6 +4,6 @@
* See the class {@link com.twelvemonkeys.image.ImageUtil}. * See the class {@link com.twelvemonkeys.image.ImageUtil}.
* *
* @version 1.0 * @version 1.0
* @author <a href="mailto:harald@escenic.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/ */
package com.twelvemonkeys.image; package com.twelvemonkeys.image;
@@ -0,0 +1,366 @@
package com.twelvemonkeys.image;
import org.junit.Ignore;
import org.junit.Test;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.net.URL;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* BufferedImageFactoryTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedImageFactoryTestCase.java,v 1.0 May 7, 2010 12:40:08 PM haraldk Exp$
*/
public class BufferedImageFactoryTestCase {
@Test(expected = IllegalArgumentException.class)
public void testCreateNullImage() {
new BufferedImageFactory((Image) null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNullProducer() {
new BufferedImageFactory((ImageProducer) null);
}
// NPE in Toolkit, ok
@Test(expected = RuntimeException.class)
public void testGetBufferedImageErrorSourceByteArray() {
Image source = Toolkit.getDefaultToolkit().createImage((byte[]) null);
new BufferedImageFactory(source);
}
@Test(expected = IllegalArgumentException.class)
public void testGetBufferedImageErrorSourceImageProducer() {
Image source = Toolkit.getDefaultToolkit().createImage((ImageProducer) null);
new BufferedImageFactory(source);
}
// TODO: This is a quite serious bug, however, the bug is in the Toolkit, allowing such images in the first place...
// In any case, there's not much we can do, except until someone is bored and kills the app/thread... :-P
@Ignore("Bug in Toolkit")
@Test(timeout = 1000, expected = ImageConversionException.class)
public void testGetBufferedImageErrorSourceString() {
Image source = Toolkit.getDefaultToolkit().createImage((String) null);
BufferedImageFactory factory = new BufferedImageFactory(source);
factory.getBufferedImage();
}
// This is a little random, and it would be nicer if we could throw an IllegalArgumentException on create.
// Unfortunately, the API doesn't allow this...
@Test(timeout = 1000, expected = ImageConversionException.class)
public void testGetBufferedImageErrorSourceURL() {
Image source = Toolkit.getDefaultToolkit().createImage(getClass().getResource("/META-INF/MANIFEST.MF"));
BufferedImageFactory factory = new BufferedImageFactory(source);
factory.getBufferedImage();
}
@Test
public void testGetBufferedImageJPEG() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage image = factory.getBufferedImage();
assertEquals(187, image.getWidth());
assertEquals(283, image.getHeight());
}
@Test
public void testGetColorModelJPEG() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
ColorModel colorModel = factory.getColorModel();
assertNotNull(colorModel);
assertEquals(3, colorModel.getNumColorComponents()); // getNumComponents may include alpha, we don't care
assertEquals(ColorSpace.getInstance(ColorSpace.CS_sRGB), colorModel.getColorSpace());
for (int i = 0; i < colorModel.getNumComponents(); i++) {
assertEquals(8, colorModel.getComponentSize(i));
}
}
@Test
public void testGetBufferedImageGIF() {
URL resource = getClass().getResource("/tux.gif");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage image = factory.getBufferedImage();
assertEquals(250, image.getWidth());
assertEquals(250, image.getHeight());
assertEquals(Transparency.BITMASK, image.getTransparency());
// All corners of image should be fully transparent
assertEquals(0, image.getRGB(0, 0) >>> 24);
assertEquals(0, image.getRGB(249, 0) >>> 24);
assertEquals(0, image.getRGB(0, 249) >>> 24);
assertEquals(0, image.getRGB(249, 249) >>> 24);
}
@Test
public void testGetColorModelGIF() {
URL resource = getClass().getResource("/tux.gif");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
ColorModel colorModel = factory.getColorModel();
assertNotNull(colorModel);
assertEquals(3, colorModel.getNumColorComponents());
assertEquals(ColorSpace.getInstance(ColorSpace.CS_sRGB), colorModel.getColorSpace());
assertTrue(colorModel instanceof IndexColorModel);
assertTrue(colorModel.hasAlpha());
assertEquals(4, colorModel.getNumComponents());
assertTrue(((IndexColorModel) colorModel).getTransparentPixel() >= 0);
assertEquals(Transparency.BITMASK, colorModel.getTransparency());
for (int i = 0; i < colorModel.getNumComponents(); i++) {
assertEquals(8, colorModel.getComponentSize(i));
}
}
@Test
public void testGetBufferedImageSubsampled() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage original = factory.getBufferedImage();
factory.setSourceSubsampling(2, 2);
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
// Values rounded up
assertEquals(94, image.getWidth());
assertEquals(142, image.getHeight());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(x * 2, y * 2), image.getRGB(x, y));
}
}
}
@Test
public void testGetBufferedImageSourceRegion() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage original = factory.getBufferedImage();
factory.setSourceRegion(new Rectangle(40, 40, 40, 40));
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
assertEquals(40, image.getWidth());
assertEquals(40, image.getHeight());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(40 + x, 40 + y), image.getRGB(x, y));
}
}
}
@Test
public void testGetBufferedImageSubsampledSourceRegion() throws Exception{
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage original = factory.getBufferedImage();
factory.setSourceRegion(new Rectangle(40, 40, 40, 40));
factory.setSourceSubsampling(2, 2);
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
assertEquals(20, image.getWidth());
assertEquals(20, image.getHeight());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(40 + x * 2, 40 + y * 2), image.getRGB(x, y));
}
}
}
@Test
public void testAbort() throws Exception {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
final BufferedImageFactory factory = new BufferedImageFactory(source);
// Listener should abort ASAP
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) {
if (pPercentage > 5) {
pFactory.abort();
}
}
});
BufferedImage image = factory.getBufferedImage();
assertEquals(187, image.getWidth());
assertEquals(283, image.getHeight());
// Upper right should be loaded
assertEquals((image.getRGB(186, 0) & 0xFF0000) >> 16 , 0x68, 10);
assertEquals((image.getRGB(186, 0) & 0xFF00) >> 8, 0x91, 10);
assertEquals(image.getRGB(186, 0) & 0xFF, 0xE0, 10);
// Lower right should be blank
assertEquals(image.getRGB(186, 282) & 0xFFFFFF, 0);
}
@Test
public void testListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.getBufferedImage();
listener.verify(100f);
}
@Test
public void testRemoveListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(listener);
factory.getBufferedImage();
listener.verify(0);
}
@Test
public void testRemoveNullListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(null);
factory.getBufferedImage();
listener.verify(100);
}
@Test
public void testRemoveNotAdddedListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) {
}
});
factory.getBufferedImage();
listener.verify(100);
}
@Test
public void testRemoveAllListeners() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
VerifyingListener listener2 = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.addProgressListener(listener);
factory.addProgressListener(listener2);
factory.removeAllProgressListeners();
factory.getBufferedImage();
listener.verify(0);
listener2.verify(0);
}
private static class VerifyingListener implements BufferedImageFactory.ProgressListener {
private final BufferedImageFactory factory;
private float progress;
public VerifyingListener(BufferedImageFactory factory) {
this.factory = factory;
}
public void progress(BufferedImageFactory pFactory, float pPercentage) {
assertEquals(factory, pFactory);
assertTrue(pPercentage >= progress && pPercentage <= 100f);
progress = pPercentage;
}
public void verify(final float expectedProgress) {
assertEquals(expectedProgress, progress, .1f); // Sanity test that the listener was invoked
}
}
}
@@ -24,35 +24,35 @@ import java.lang.reflect.InvocationTargetException;
public class ImageUtilTestCase extends TestCase { public class ImageUtilTestCase extends TestCase {
private final static String IMAGE_NAME = "/sunflower.jpg"; private final static String IMAGE_NAME = "/sunflower.jpg";
private BufferedImage mOriginal; private BufferedImage original;
private BufferedImage mImage; private BufferedImage image;
private Image mScaled; private Image scaled;
public ImageUtilTestCase() throws Exception { public ImageUtilTestCase() throws Exception {
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST); scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
// Read image from class path // Read image from class path
InputStream is = getClass().getResourceAsStream(IMAGE_NAME); InputStream is = getClass().getResourceAsStream(IMAGE_NAME);
mOriginal = ImageIO.read(is); original = ImageIO.read(is);
assertNotNull(mOriginal); assertNotNull(original);
} }
/* /*
public void setUp() throws Exception { public void setUp() throws Exception {
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST); scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
// Read image from class path // Read image from class path
InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME); InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME);
mOriginal = ImageIO.read(is); original = ImageIO.read(is);
assertNotNull(mOriginal); assertNotNull(original);
} }
protected void tearDown() throws Exception { protected void tearDown() throws Exception {
mOriginal = null; original = null;
} }
*/ */
@@ -94,20 +94,20 @@ public class ImageUtilTestCase extends TestCase {
// Should not be a buffered image // Should not be a buffered image
assertFalse( assertFalse(
"FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.", "FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.",
mScaled instanceof BufferedImage scaled instanceof BufferedImage
); );
} }
public void testToBufferedImage() { public void testToBufferedImage() {
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) mImage); BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) image);
BufferedImage bufferedScaled = ImageUtil.toBuffered(mScaled); BufferedImage bufferedScaled = ImageUtil.toBuffered(scaled);
// Should be no need to convert // Should be no need to convert
assertSame(mImage, sameAsImage); assertSame(image, sameAsImage);
// Should have same dimensions // Should have same dimensions
assertEquals(mScaled.getWidth(null), bufferedScaled.getWidth()); assertEquals(scaled.getWidth(null), bufferedScaled.getWidth());
assertEquals(mScaled.getHeight(null), bufferedScaled.getHeight()); assertEquals(scaled.getHeight(null), bufferedScaled.getHeight());
// Hmmm... // Hmmm...
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number")) assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
@@ -116,28 +116,28 @@ public class ImageUtilTestCase extends TestCase {
} }
public void testToBufferedImageType() { public void testToBufferedImageType() {
// Assumes mImage is TYPE_INT_ARGB // Assumes image is TYPE_INT_ARGB
BufferedImage converted = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_INDEXED); BufferedImage converted = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_INDEXED);
BufferedImage convertedToo = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_BINARY); BufferedImage convertedToo = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_BINARY);
// Should not be the same // Should not be the same
assertNotSame(mImage, converted); assertNotSame(image, converted);
assertNotSame(mImage, convertedToo); assertNotSame(image, convertedToo);
// Correct type // Correct type
assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED); assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED);
assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY); assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY);
// Should have same dimensions // Should have same dimensions
assertEquals(mImage.getWidth(), converted.getWidth()); assertEquals(image.getWidth(), converted.getWidth());
assertEquals(mImage.getHeight(), converted.getHeight()); assertEquals(image.getHeight(), converted.getHeight());
assertEquals(mImage.getWidth(), convertedToo.getWidth()); assertEquals(image.getWidth(), convertedToo.getWidth());
assertEquals(mImage.getHeight(), convertedToo.getHeight()); assertEquals(image.getHeight(), convertedToo.getHeight());
} }
public void testBrightness() { public void testBrightness() {
final BufferedImage original = mOriginal; final BufferedImage original = this.original;
assertNotNull(original); assertNotNull(original);
final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f)); final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f));
@@ -217,7 +217,7 @@ public class ImageUtilTestCase extends TestCase {
public void testContrast() { public void testContrast() {
final BufferedImage original = mOriginal; final BufferedImage original = this.original;
assertNotNull(original); assertNotNull(original);
@@ -370,7 +370,7 @@ public class ImageUtilTestCase extends TestCase {
} }
public void testSharpen() { public void testSharpen() {
final BufferedImage original = mOriginal; final BufferedImage original = this.original;
assertNotNull(original); assertNotNull(original);
@@ -495,7 +495,7 @@ public class ImageUtilTestCase extends TestCase {
} }
public void testBlur() { public void testBlur() {
final BufferedImage original = mOriginal; final BufferedImage original = this.original;
assertNotNull(original); assertNotNull(original);
@@ -563,7 +563,7 @@ public class ImageUtilTestCase extends TestCase {
} }
public void testIndexImage() { public void testIndexImage() {
BufferedImage sunflower = mOriginal; BufferedImage sunflower = original;
assertNotNull(sunflower); assertNotNull(sunflower);
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

+6
View File
@@ -0,0 +1,6 @@
TODO:
Remove compile-time dependency on JMagick:
- Extract interface for MagickAccelerator
- Move implementation to separate module
- Instantiate impl via reflection
DONE:
+25 -24
View File
@@ -2,30 +2,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.0-SNAPSHOT</version> <version>3.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-io</artifactId> <artifactId>common-io</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: IO</name> <name>TwelveMonkeys :: Common :: IO</name>
<description> <description>
The TwelveMonkeys IO support The TwelveMonkeys Common IO support
</description> </description>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId> <artifactId>common-lang</artifactId>
</dependency> </dependency>
<dependency>
<groupId>${project.groupId}</groupId> <dependency>
<artifactId>common-lang</artifactId> <groupId>${project.groupId}</groupId>
<classifier>tests</classifier> <artifactId>common-lang</artifactId>
<scope>test</scope> <classifier>tests</classifier>
</dependency> <scope>test</scope>
</dependencies> </dependency>
</dependencies>
</project> </project>
@@ -14,28 +14,28 @@ import java.io.InputStream;
*/ */
abstract class AbstractCachedSeekableStream extends SeekableInputStream { abstract class AbstractCachedSeekableStream extends SeekableInputStream {
/** The backing stream */ /** The backing stream */
protected final InputStream mStream; protected final InputStream stream;
/** The stream positon in the backing stream (mStream) */ /** The stream positon in the backing stream (stream) */
protected long mStreamPosition; protected long streamPosition;
private StreamCache mCache; private StreamCache cache;
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) { protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
Validate.notNull(pStream, "stream"); Validate.notNull(pStream, "stream");
Validate.notNull(pCache, "cache"); Validate.notNull(pCache, "cache");
mStream = pStream; stream = pStream;
mCache = pCache; cache = pCache;
} }
protected final StreamCache getCache() { protected final StreamCache getCache() {
return mCache; return cache;
} }
@Override @Override
public int available() throws IOException { public int available() throws IOException {
long avail = mStreamPosition - mPosition + mStream.available(); long avail = streamPosition - position + stream.available();
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail; return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
} }
@@ -43,26 +43,26 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
checkOpen(); checkOpen();
int read; int read;
if (mPosition == mStreamPosition) { if (position == streamPosition) {
// TODO: Read more bytes here! // TODO: Read more bytes here!
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides). // TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
// Read a byte from the stream // Read a byte from the stream
read = mStream.read(); read = stream.read();
if (read >= 0) { if (read >= 0) {
mStreamPosition++; streamPosition++;
mCache.write(read); cache.write(read);
} }
} }
else { else {
// ..or read byte from the cache // ..or read byte from the cache
syncPosition(); syncPosition();
read = mCache.read(); read = cache.read();
} }
// TODO: This field is not REALLY considered accessible.. :-P // TODO: This field is not REALLY considered accessible.. :-P
if (read != -1) { if (read != -1) {
mPosition++; position++;
} }
return read; return read;
@@ -73,32 +73,32 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
checkOpen(); checkOpen();
int length; int length;
if (mPosition == mStreamPosition) { if (position == streamPosition) {
// Read bytes from the stream // Read bytes from the stream
length = mStream.read(pBytes, pOffset, pLength); length = stream.read(pBytes, pOffset, pLength);
if (length > 0) { if (length > 0) {
mStreamPosition += length; streamPosition += length;
mCache.write(pBytes, pOffset, length); cache.write(pBytes, pOffset, length);
} }
} }
else { else {
// ...or read bytes from the cache // ...or read bytes from the cache
syncPosition(); syncPosition();
length = mCache.read(pBytes, pOffset, pLength); length = cache.read(pBytes, pOffset, pLength);
} }
// TODO: This field is not REALLY considered accessible.. :-P // TODO: This field is not REALLY considered accessible.. :-P
if (length > 0) { if (length > 0) {
mPosition += length; position += length;
} }
return length; return length;
} }
protected final void syncPosition() throws IOException { protected final void syncPosition() throws IOException {
if (mCache.getPosition() != mPosition) { if (cache.getPosition() != position) {
mCache.seek(mPosition); // Assure EOF is correctly thrown cache.seek(position); // Assure EOF is correctly thrown
} }
} }
@@ -111,14 +111,14 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
public abstract boolean isCachedFile(); public abstract boolean isCachedFile();
protected void seekImpl(long pPosition) throws IOException { protected void seekImpl(long pPosition) throws IOException {
if (mStreamPosition < pPosition) { if (streamPosition < pPosition) {
// Make sure we append at end of cache // Make sure we append at end of cache
if (mCache.getPosition() != mStreamPosition) { if (cache.getPosition() != streamPosition) {
mCache.seek(mStreamPosition); cache.seek(streamPosition);
} }
// Read diff from stream into cache // Read diff from stream into cache
long left = pPosition - mStreamPosition; long left = pPosition - streamPosition;
// TODO: Use fixed buffer, instead of allocating here... // TODO: Use fixed buffer, instead of allocating here...
int bufferLen = left > 1024 ? 1024 : (int) left; int bufferLen = left > 1024 ? 1024 : (int) left;
@@ -126,11 +126,11 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
while (left > 0) { while (left > 0) {
int length = buffer.length < left ? buffer.length : (int) left; int length = buffer.length < left ? buffer.length : (int) left;
int read = mStream.read(buffer, 0, length); int read = stream.read(buffer, 0, length);
if (read > 0) { if (read > 0) {
mCache.write(buffer, 0, read); cache.write(buffer, 0, read);
mStreamPosition += read; streamPosition += read;
left -= read; left -= read;
} }
else if (read < 0) { else if (read < 0) {
@@ -138,27 +138,27 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
} }
} }
} }
else if (mStreamPosition >= pPosition) { else /*if (streamPosition >= pPosition) */ {
// Seek backwards into the cache // Seek backwards into the cache
mCache.seek(pPosition); cache.seek(pPosition);
} }
// System.out.println("pPosition: " + pPosition); // System.out.println("pPosition: " + pPosition);
// System.out.println("mPosition: " + mPosition); // System.out.println("position: " + position);
// System.out.println("mStreamPosition: " + mStreamPosition); // System.out.println("streamPosition: " + streamPosition);
// System.out.println("mCache.mPosition: " + mCache.getPosition()); // System.out.println("cache.position: " + cache.getPosition());
// NOTE: If mPosition == pPosition then we're good to go // NOTE: If position == pPosition then we're good to go
} }
protected void flushBeforeImpl(long pPosition) { protected void flushBeforeImpl(long pPosition) {
mCache.flush(pPosition); cache.flush(pPosition);
} }
protected void closeImpl() throws IOException { protected void closeImpl() throws IOException {
mCache.flush(mPosition); cache.flush(position);
mCache = null; cache = null;
mStream.close(); stream.close();
} }
/** /**
@@ -46,15 +46,16 @@ import java.util.List;
*/ */
public class CompoundReader extends Reader { public class CompoundReader extends Reader {
private Reader mCurrent; private Reader current;
private List<Reader> mReaders; private List<Reader> readers;
protected final Object mLock;
protected final boolean mMarkSupported; protected final Object finalLock;
private int mCurrentReader; protected final boolean markSupported;
private int mMarkedReader;
private int mMark; private int currentReader;
private int markedReader;
private int mark;
private int mNext; private int mNext;
/** /**
@@ -71,10 +72,10 @@ public class CompoundReader extends Reader {
public CompoundReader(final Iterator<Reader> pReaders) { public CompoundReader(final Iterator<Reader> pReaders) {
super(Validate.notNull(pReaders, "readers")); super(Validate.notNull(pReaders, "readers"));
mLock = pReaders; // NOTE: It's ok to sync on pReaders, as the finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
// reference can't change, only it's elements // reference can't change, only it's elements
mReaders = new ArrayList<Reader>(); readers = new ArrayList<Reader>();
boolean markSupported = true; boolean markSupported = true;
while (pReaders.hasNext()) { while (pReaders.hasNext()) {
@@ -82,25 +83,25 @@ public class CompoundReader extends Reader {
if (reader == null) { if (reader == null) {
throw new NullPointerException("readers cannot contain null-elements"); throw new NullPointerException("readers cannot contain null-elements");
} }
mReaders.add(reader); readers.add(reader);
markSupported = markSupported && reader.markSupported(); markSupported = markSupported && reader.markSupported();
} }
mMarkSupported = markSupported; this.markSupported = markSupported;
mCurrent = nextReader(); current = nextReader();
} }
protected final Reader nextReader() { protected final Reader nextReader() {
if (mCurrentReader >= mReaders.size()) { if (currentReader >= readers.size()) {
mCurrent = new EmptyReader(); current = new EmptyReader();
} }
else { else {
mCurrent = mReaders.get(mCurrentReader++); current = readers.get(currentReader++);
} }
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods! // NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
mNext = 0; mNext = 0;
return mCurrent; return current;
} }
/** /**
@@ -109,17 +110,18 @@ public class CompoundReader extends Reader {
* @throws IOException if the stream is closed * @throws IOException if the stream is closed
*/ */
protected final void ensureOpen() throws IOException { protected final void ensureOpen() throws IOException {
if (mReaders == null) { if (readers == null) {
throw new IOException("Stream closed"); throw new IOException("Stream closed");
} }
} }
public void close() throws IOException { public void close() throws IOException {
// Close all readers // Close all readers
for (Reader reader : mReaders) { for (Reader reader : readers) {
reader.close(); reader.close();
} }
mReaders = null;
readers = null;
} }
@Override @Override
@@ -130,46 +132,46 @@ public class CompoundReader extends Reader {
// TODO: It would be nice if we could actually close some readers now // TODO: It would be nice if we could actually close some readers now
synchronized (mLock) { synchronized (finalLock) {
ensureOpen(); ensureOpen();
mMark = mNext; mark = mNext;
mMarkedReader = mCurrentReader; markedReader = currentReader;
mCurrent.mark(pReadLimit); current.mark(pReadLimit);
} }
} }
@Override @Override
public void reset() throws IOException { public void reset() throws IOException {
synchronized (mLock) { synchronized (finalLock) {
ensureOpen(); ensureOpen();
if (mCurrentReader != mMarkedReader) { if (currentReader != markedReader) {
// Reset any reader before this // Reset any reader before this
for (int i = mCurrentReader; i >= mMarkedReader; i--) { for (int i = currentReader; i >= markedReader; i--) {
mReaders.get(i).reset(); readers.get(i).reset();
} }
mCurrentReader = mMarkedReader - 1; currentReader = markedReader - 1;
nextReader(); nextReader();
} }
mCurrent.reset(); current.reset();
mNext = mMark; mNext = mark;
} }
} }
@Override @Override
public boolean markSupported() { public boolean markSupported() {
return mMarkSupported; return markSupported;
} }
@Override @Override
public int read() throws IOException { public int read() throws IOException {
synchronized (mLock) { synchronized (finalLock) {
int read = mCurrent.read(); int read = current.read();
if (read < 0 && mCurrentReader < mReaders.size()) { if (read < 0 && currentReader < readers.size()) {
nextReader(); nextReader();
return read(); // In case of 0-length readers return read(); // In case of 0-length readers
} }
@@ -181,10 +183,10 @@ public class CompoundReader extends Reader {
} }
public int read(char pBuffer[], int pOffset, int pLength) throws IOException { public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (mLock) { synchronized (finalLock) {
int read = mCurrent.read(pBuffer, pOffset, pLength); int read = current.read(pBuffer, pOffset, pLength);
if (read < 0 && mCurrentReader < mReaders.size()) { if (read < 0 && currentReader < readers.size()) {
nextReader(); nextReader();
return read(pBuffer, pOffset, pLength); // In case of 0-length readers return read(pBuffer, pOffset, pLength); // In case of 0-length readers
} }
@@ -197,15 +199,15 @@ public class CompoundReader extends Reader {
@Override @Override
public boolean ready() throws IOException { public boolean ready() throws IOException {
return mCurrent.ready(); return current.ready();
} }
@Override @Override
public long skip(long pChars) throws IOException { public long skip(long pChars) throws IOException {
synchronized (mLock) { synchronized (finalLock) {
long skipped = mCurrent.skip(pChars); long skipped = current.skip(pChars);
if (skipped == 0 && mCurrentReader < mReaders.size()) { if (skipped == 0 && currentReader < readers.size()) {
nextReader(); nextReader();
return skip(pChars); // In case of 0-length readers return skip(pChars); // In case of 0-length readers
} }
@@ -39,11 +39,12 @@ import java.io.ByteArrayInputStream;
* <p/> * <p/>
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java#2 $ * @version $Id: FastByteArrayOutputStream.java#2 $
*/ */
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
public final class FastByteArrayOutputStream extends ByteArrayOutputStream { public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
/** Max grow size (unless if writing more than this ammount of bytes) */ /** Max grow size (unless if writing more than this amount of bytes) */
protected int mMaxGrowSize = 1024 * 1024; // 1 MB protected int maxGrowSize = 1024 * 1024; // 1 MB
/** /**
* Creates a {@code ByteArrayOutputStream} with the given initial buffer * Creates a {@code ByteArrayOutputStream} with the given initial buffer
@@ -69,7 +70,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
} }
@Override @Override
public synchronized void write(byte pBytes[], int pOffset, int pLength) { public void write(byte pBytes[], int pOffset, int pLength) {
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
@@ -77,23 +78,24 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
else if (pLength == 0) { else if (pLength == 0) {
return; return;
} }
int newcount = count + pLength;
growIfNeeded(newcount); int newCount = count + pLength;
growIfNeeded(newCount);
System.arraycopy(pBytes, pOffset, buf, count, pLength); System.arraycopy(pBytes, pOffset, buf, count, pLength);
count = newcount; count = newCount;
} }
@Override @Override
public synchronized void write(int pByte) { public void write(int pByte) {
int newcount = count + 1; int newCount = count + 1;
growIfNeeded(newcount); growIfNeeded(newCount);
buf[count] = (byte) pByte; buf[count] = (byte) pByte;
count = newcount; count = newCount;
} }
private void growIfNeeded(int pNewcount) { private void growIfNeeded(int pNewCount) {
if (pNewcount > buf.length) { if (pNewCount > buf.length) {
int newSize = Math.max(Math.min(buf.length << 1, buf.length + mMaxGrowSize), pNewcount); int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
byte newBuf[] = new byte[newSize]; byte newBuf[] = new byte[newSize];
System.arraycopy(buf, 0, newBuf, 0, count); System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf; buf = newBuf;
@@ -109,9 +111,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
// Non-synchronized version of toByteArray // Non-synchronized version of toByteArray
@Override @Override
public byte[] toByteArray() { public byte[] toByteArray() {
byte newbuf[] = new byte[count]; byte newBuf[] = new byte[count];
System.arraycopy(buf, 0, newbuf, 0, count); System.arraycopy(buf, 0, newBuf, 0, count);
return newbuf;
return newBuf;
} }
/** /**
@@ -121,7 +124,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
* <p/> * <p/>
* Note that care needs to be taken to avoid writes to * Note that care needs to be taken to avoid writes to
* this output stream after the input stream is created. * this output stream after the input stream is created.
* Failing to do so, may result in unpredictable behviour. * Failing to do so, may result in unpredictable behaviour.
* *
* @return a new {@code ByteArrayInputStream}, reading from this stream's buffer. * @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
*/ */
@@ -48,16 +48,7 @@ import java.io.*;
*/ */
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream { public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
// private final InputStream mStream; private byte[] buffer;
// private final RandomAccessFile mCache;
private byte[] mBuffer;
/** The stream positon in the backing stream (mStream) */
// private long mStreamPosition;
// TODO: getStreamPosition() should always be the same as
// mCache.getFilePointer()
// otherwise there's some inconsistency here... Enforce this?
/** /**
* Creates a {@code FileCacheSeekableStream} reading from the given * Creates a {@code FileCacheSeekableStream} reading from the given
@@ -118,7 +109,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
super(pStream, new FileCache(pFile)); super(pStream, new FileCache(pFile));
// TODO: Allow for custom buffer sizes? // TODO: Allow for custom buffer sizes?
mBuffer = new byte[1024]; buffer = new byte[1024];
} }
public final boolean isCachedMemory() { public final boolean isCachedMemory() {
@@ -132,39 +123,19 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
@Override @Override
protected void closeImpl() throws IOException { protected void closeImpl() throws IOException {
super.closeImpl(); super.closeImpl();
mBuffer = null; buffer = null;
} }
/*
public final boolean isCached() {
return true;
}
// InputStream overrides
@Override
public int available() throws IOException {
long avail = mStreamPosition - mPosition + mStream.available();
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
}
public void closeImpl() throws IOException {
mStream.close();
mCache.close();
// TODO: Delete cache file here?
// ThreadPool.invokeLater(new DeleteFileAction(mCacheFile));
}
*/
@Override @Override
public int read() throws IOException { public int read() throws IOException {
checkOpen(); checkOpen();
int read; int read;
if (mPosition == mStreamPosition) { if (position == streamPosition) {
// Read ahead into buffer, for performance // Read ahead into buffer, for performance
read = readAhead(mBuffer, 0, mBuffer.length); read = readAhead(buffer, 0, buffer.length);
if (read >= 0) { if (read >= 0) {
read = mBuffer[0] & 0xff; read = buffer[0] & 0xff;
} }
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff)); //System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff));
@@ -179,7 +150,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
// TODO: This field is not REALLY considered accessible.. :-P // TODO: This field is not REALLY considered accessible.. :-P
if (read != -1) { if (read != -1) {
mPosition++; position++;
} }
return read; return read;
} }
@@ -189,7 +160,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
checkOpen(); checkOpen();
int length; int length;
if (mPosition == mStreamPosition) { if (position == streamPosition) {
// Read bytes from the stream // Read bytes from the stream
length = readAhead(pBytes, pOffset, pLength); length = readAhead(pBytes, pOffset, pLength);
@@ -198,83 +169,29 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
else { else {
// ...or read bytes from the cache // ...or read bytes from the cache
syncPosition(); syncPosition();
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, mStreamPosition - mPosition)); length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, streamPosition - position));
//System.out.println("Read " + length + " byte from cache"); //System.out.println("Read " + length + " byte from cache");
} }
// TODO: This field is not REALLY considered accessible.. :-P // TODO: This field is not REALLY considered accessible.. :-P
if (length > 0) { if (length > 0) {
mPosition += length; position += length;
} }
return length; return length;
} }
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
int length; int length;
length = mStream.read(pBytes, pOffset, pLength); length = stream.read(pBytes, pOffset, pLength);
if (length > 0) { if (length > 0) {
mStreamPosition += length; streamPosition += length;
getCache().write(pBytes, pOffset, length); getCache().write(pBytes, pOffset, length);
} }
return length; return length;
} }
/*
private void syncPosition() throws IOException {
if (mCache.getFilePointer() != mPosition) {
mCache.seek(mPosition); // Assure EOF is correctly thrown
}
}
// Seekable overrides
protected void flushBeforeImpl(long pPosition) {
// TODO: Implement
// For now, it's probably okay to do nothing, this is just for
// performance (as long as people follow spec, not behaviour)
}
protected void seekImpl(long pPosition) throws IOException {
if (mStreamPosition < pPosition) {
// Make sure we append at end of cache
if (mCache.getFilePointer() != mStreamPosition) {
mCache.seek(mStreamPosition);
}
// Read diff from stream into cache
long left = pPosition - mStreamPosition;
int bufferLen = left > 1024 ? 1024 : (int) left;
byte[] buffer = new byte[bufferLen];
while (left > 0) {
int length = buffer.length < left ? buffer.length : (int) left;
int read = mStream.read(buffer, 0, length);
if (read > 0) {
mCache.write(buffer, 0, read);
mStreamPosition += read;
left -= read;
}
else if (read < 0) {
break;
}
}
}
else if (mStreamPosition >= pPosition) {
// Seek backwards into the cache
mCache.seek(pPosition);
}
// System.out.println("pPosition: " + pPosition);
// System.out.println("mStreamPosition: " + mStreamPosition);
// System.out.println("mCache.getFilePointer(): " + mCache.getFilePointer());
// NOTE: If mPosition == pPosition then we're good to go
}
*/
final static class FileCache extends StreamCache { final static class FileCache extends StreamCache {
private RandomAccessFile mCacheFile; private RandomAccessFile mCacheFile;
@@ -87,7 +87,7 @@ public final class FileSeekableStream extends SeekableInputStream {
@Override @Override
public int available() throws IOException { public int available() throws IOException {
long length = mRandomAccess.length() - mPosition; long length = mRandomAccess.length() - position;
return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length; return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
} }
@@ -100,7 +100,7 @@ public final class FileSeekableStream extends SeekableInputStream {
int read = mRandomAccess.read(); int read = mRandomAccess.read();
if (read >= 0) { if (read >= 0) {
mPosition++; position++;
} }
return read; return read;
} }
@@ -111,7 +111,7 @@ public final class FileSeekableStream extends SeekableInputStream {
int read = mRandomAccess.read(pBytes, pOffset, pLength); int read = mRandomAccess.read(pBytes, pOffset, pLength);
if (read > 0) { if (read > 0) {
mPosition += read; position += read;
} }
return read; return read;
} }
@@ -38,7 +38,7 @@ import java.io.InputStreamReader;
* <p/> * <p/>
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSystem.java#1 $ * @version $Id: FileSystem.java#1 $
*/ */
abstract class FileSystem { abstract class FileSystem {
abstract long getFreeSpace(File pPath); abstract long getFreeSpace(File pPath);
@@ -57,21 +57,21 @@ abstract class FileSystem {
//System.out.println("os = " + os); //System.out.println("os = " + os);
os = os.toLowerCase(); os = os.toLowerCase();
if (os.indexOf("windows") != -1) { if (os.contains("windows")) {
return new Win32FileSystem(); return new Win32FileSystem();
} }
else if (os.indexOf("linux") != -1 || else if (os.contains("linux") ||
os.indexOf("sun os") != -1 || os.contains("sun os") ||
os.indexOf("sunos") != -1 || os.contains("sunos") ||
os.indexOf("solaris") != -1 || os.contains("solaris") ||
os.indexOf("mpe/ix") != -1 || os.contains("mpe/ix") ||
os.indexOf("hp-ux") != -1 || os.contains("hp-ux") ||
os.indexOf("aix") != -1 || os.contains("aix") ||
os.indexOf("freebsd") != -1 || os.contains("freebsd") ||
os.indexOf("irix") != -1 || os.contains("irix") ||
os.indexOf("digital unix") != -1 || os.contains("digital unix") ||
os.indexOf("unix") != -1 || os.contains("unix") ||
os.indexOf("mac os x") != -1) { os.contains("mac os x")) {
return new UnixFileSystem(); return new UnixFileSystem();
} }
else { else {
@@ -80,10 +80,10 @@ abstract class FileSystem {
} }
private static class UnknownFileSystem extends FileSystem { private static class UnknownFileSystem extends FileSystem {
private final String mOSName; private final String osName;
UnknownFileSystem(String pOSName) { UnknownFileSystem(String pOSName) {
mOSName = pOSName; osName = pOSName;
} }
long getFreeSpace(File pPath) { long getFreeSpace(File pPath) {
@@ -95,7 +95,7 @@ abstract class FileSystem {
} }
String getName() { String getName() {
return "Unknown (" + mOSName + ")"; return "Unknown (" + osName + ")";
} }
} }
} }
@@ -186,6 +186,7 @@ public final class FileUtil {
if (!pOverWrite && pToFile.exists()) { if (!pOverWrite && pToFile.exists()) {
return false; return false;
} }
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
@@ -202,6 +203,7 @@ public final class FileUtil {
close(in); close(in);
close(out); close(out);
} }
return true; // If we got here, everything's probably okay.. ;-) return true; // If we got here, everything's probably okay.. ;-)
} }
@@ -307,6 +309,8 @@ public final class FileUtil {
Validate.notNull(pFrom, "from"); Validate.notNull(pFrom, "from");
Validate.notNull(pTo, "to"); Validate.notNull(pTo, "to");
// TODO: Consider using file channels for faster copy where possible
// Use buffer size two times byte array, to avoid i/o bottleneck // Use buffer size two times byte array, to avoid i/o bottleneck
// TODO: Consider letting the client decide as this is sometimes not a good thing! // TODO: Consider letting the client decide as this is sometimes not a good thing!
InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2); InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2);
@@ -322,31 +326,9 @@ public final class FileUtil {
// Flush out stream, to write any remaining buffered data // Flush out stream, to write any remaining buffered data
out.flush(); out.flush();
return true; // If we got here, everything's probably okay.. ;-) return true; // If we got here, everything is probably okay.. ;-)
} }
/*
// Consider using the example from
// http://developer.java.sun.com/developer/Books/performance/ch04.pdf
// Test if this is really faster. And what about a lot of concurrence?
// Have a pool of buffers? :-)
static final int BUFF_SIZE = 100000;
static final byte[] buffer = new byte[BUFF_SIZE];
public static void copy(InputStream in, OutputStream out) throws IOException {
while (true) {
synchronized (buffer) {
int amountRead = in.read(buffer);
if (amountRead == -1) {
break;
}
out.write(buffer, 0, amountRead);
}
}
}
*/
/** /**
* Gets the file (type) extension of the given file. * Gets the file (type) extension of the given file.
* A file extension is the part of the filename, after the last occurence * A file extension is the part of the filename, after the last occurence
@@ -568,6 +550,7 @@ public final class FileUtil {
if (!pFile.exists()) { if (!pFile.exists()) {
throw new FileNotFoundException(pFile.toString()); throw new FileNotFoundException(pFile.toString());
} }
byte[] bytes = new byte[(int) pFile.length()]; byte[] bytes = new byte[(int) pFile.length()];
InputStream in = null; InputStream in = null;
@@ -586,6 +569,7 @@ public final class FileUtil {
finally { finally {
close(in); close(in);
} }
return bytes; return bytes;
} }
@@ -685,28 +669,28 @@ public final class FileUtil {
// a file array, which may throw OutOfMemoryExceptions for // a file array, which may throw OutOfMemoryExceptions for
// large directories/in low memory situations // large directories/in low memory situations
class DeleteFilesVisitor implements Visitor<File> { class DeleteFilesVisitor implements Visitor<File> {
private int mFailedCount = 0; private int failedCount = 0;
private IOException mException = null; private IOException exception = null;
public void visit(final File pFile) { public void visit(final File pFile) {
try { try {
if (!delete(pFile, true)) { if (!delete(pFile, true)) {
mFailedCount++; failedCount++;
} }
} }
catch (IOException e) { catch (IOException e) {
mFailedCount++; failedCount++;
if (mException == null) { if (exception == null) {
mException = e; exception = e;
} }
} }
} }
boolean succeeded() throws IOException { boolean succeeded() throws IOException {
if (mException != null) { if (exception != null) {
throw mException; throw exception;
} }
return mFailedCount == 0; return failedCount == 0;
} }
} }
DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor(); DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
@@ -886,6 +870,8 @@ public final class FileUtil {
return folder.listFiles(); return folder.listFiles();
} }
// TODO: Rewrite to use regexp
FilenameFilter filter = new FilenameMaskFilter(pFilenameMask); FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
return folder.listFiles(filter); return folder.listFiles(filter);
} }
@@ -1029,6 +1015,7 @@ public final class FileUtil {
* @return a human readable string representation * @return a human readable string representation
*/ */
public static String toHumanReadableSize(final long pSizeInBytes) { public static String toHumanReadableSize(final long pSizeInBytes) {
// TODO: Rewrite to use String.format?
if (pSizeInBytes < 1024L) { if (pSizeInBytes < 1024L) {
return pSizeInBytes + " Bytes"; return pSizeInBytes + " Bytes";
} }
@@ -1053,7 +1040,7 @@ public final class FileUtil {
private static ThreadLocal<NumberFormat> sNumberFormat = new ThreadLocal<NumberFormat>() { private static ThreadLocal<NumberFormat> sNumberFormat = new ThreadLocal<NumberFormat>() {
protected NumberFormat initialValue() { protected NumberFormat initialValue() {
NumberFormat format = NumberFormat.getNumberInstance(); NumberFormat format = NumberFormat.getNumberInstance();
// TODO: Consider making this locale/platfor specific, OR a method parameter... // TODO: Consider making this locale/platform specific, OR a method parameter...
// format.setMaximumFractionDigits(2); // format.setMaximumFractionDigits(2);
format.setMaximumFractionDigits(0); format.setMaximumFractionDigits(0);
return format; return format;
@@ -1075,6 +1062,7 @@ public final class FileUtil {
* *
* @see com.twelvemonkeys.util.Visitor * @see com.twelvemonkeys.util.Visitor
*/ */
@SuppressWarnings({"ResultOfMethodCallIgnored"})
public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor<File> pVisitor) { public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor<File> pVisitor) {
Validate.notNull(pDirectory, "directory"); Validate.notNull(pDirectory, "directory");
Validate.notNull(pVisitor, "visitor"); Validate.notNull(pVisitor, "visitor");
@@ -56,13 +56,16 @@ import java.io.FilenameFilter;
* @see File#list(java.io.FilenameFilter) java.io.File.list * @see File#list(java.io.FilenameFilter) java.io.File.list
* @see FilenameFilter java.io.FilenameFilter * @see FilenameFilter java.io.FilenameFilter
* @see WildcardStringParser * @see WildcardStringParser
* @deprecated
*/ */
public class FilenameMaskFilter implements FilenameFilter { public class FilenameMaskFilter implements FilenameFilter {
// TODO: Rewrite to use regexp, or create new class
// Members // Members
private String[] mFilenameMasksForInclusion; private String[] filenameMasksForInclusion;
private String[] mFilenameMasksForExclusion; private String[] filenameMasksForExclusion;
private boolean mInclusion = true; private boolean inclusion = true;
/** /**
@@ -127,29 +130,29 @@ public class FilenameMaskFilter implements FilenameFilter {
* @param pFilenameMasksForInclusion the filename masks to include * @param pFilenameMasksForInclusion the filename masks to include
*/ */
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) { public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
mFilenameMasksForInclusion = pFilenameMasksForInclusion; filenameMasksForInclusion = pFilenameMasksForInclusion;
} }
/** /**
* @return the current inclusion masks * @return the current inclusion masks
*/ */
public String[] getFilenameMasksForInclusion() { public String[] getFilenameMasksForInclusion() {
return mFilenameMasksForInclusion.clone(); return filenameMasksForInclusion.clone();
} }
/** /**
* @param pFilenameMasksForExclusion the filename masks to exclude * @param pFilenameMasksForExclusion the filename masks to exclude
*/ */
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) { public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
mFilenameMasksForExclusion = pFilenameMasksForExclusion; filenameMasksForExclusion = pFilenameMasksForExclusion;
mInclusion = false; inclusion = false;
} }
/** /**
* @return the current exclusion masks * @return the current exclusion masks
*/ */
public String[] getFilenameMasksForExclusion() { public String[] getFilenameMasksForExclusion() {
return mFilenameMasksForExclusion.clone(); return filenameMasksForExclusion.clone();
} }
/** /**
@@ -164,8 +167,8 @@ public class FilenameMaskFilter implements FilenameFilter {
WildcardStringParser parser; WildcardStringParser parser;
// Check each filename string mask whether the file is to be accepted // Check each filename string mask whether the file is to be accepted
if (mInclusion) { // Inclusion if (inclusion) { // Inclusion
for (String mask : mFilenameMasksForInclusion) { for (String mask : filenameMasksForInclusion) {
parser = new WildcardStringParser(mask); parser = new WildcardStringParser(mask);
if (parser.parseString(pName)) { if (parser.parseString(pName)) {
@@ -181,7 +184,7 @@ public class FilenameMaskFilter implements FilenameFilter {
} }
else { else {
// Exclusion // Exclusion
for (String mask : mFilenameMasksForExclusion) { for (String mask : filenameMasksForExclusion) {
parser = new WildcardStringParser(mask); parser = new WildcardStringParser(mask);
if (parser.parseString(pName)) { if (parser.parseString(pName)) {
@@ -204,32 +207,32 @@ public class FilenameMaskFilter implements FilenameFilter {
StringBuilder retVal = new StringBuilder(); StringBuilder retVal = new StringBuilder();
int i; int i;
if (mInclusion) { if (inclusion) {
// Inclusion // Inclusion
if (mFilenameMasksForInclusion == null) { if (filenameMasksForInclusion == null) {
retVal.append("No filename masks set - property mFilenameMasksForInclusion is null!"); retVal.append("No filename masks set - property filenameMasksForInclusion is null!");
} }
else { else {
retVal.append(mFilenameMasksForInclusion.length); retVal.append(filenameMasksForInclusion.length);
retVal.append(" filename mask(s) - "); retVal.append(" filename mask(s) - ");
for (i = 0; i < mFilenameMasksForInclusion.length; i++) { for (i = 0; i < filenameMasksForInclusion.length; i++) {
retVal.append("\""); retVal.append("\"");
retVal.append(mFilenameMasksForInclusion[i]); retVal.append(filenameMasksForInclusion[i]);
retVal.append("\", \""); retVal.append("\", \"");
} }
} }
} }
else { else {
// Exclusion // Exclusion
if (mFilenameMasksForExclusion == null) { if (filenameMasksForExclusion == null) {
retVal.append("No filename masks set - property mFilenameMasksForExclusion is null!"); retVal.append("No filename masks set - property filenameMasksForExclusion is null!");
} }
else { else {
retVal.append(mFilenameMasksForExclusion.length); retVal.append(filenameMasksForExclusion.length);
retVal.append(" exclusion filename mask(s) - "); retVal.append(" exclusion filename mask(s) - ");
for (i = 0; i < mFilenameMasksForExclusion.length; i++) { for (i = 0; i < filenameMasksForExclusion.length; i++) {
retVal.append("\""); retVal.append("\"");
retVal.append(mFilenameMasksForExclusion[i]); retVal.append(filenameMasksForExclusion[i]);
retVal.append("\", \""); retVal.append("\", \"");
} }
} }
@@ -1,93 +0,0 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.StringUtil;
import java.io.File;
import java.io.FilenameFilter;
/**
* A Java Bean used for approving file names which are to be included in a
* {@code java.io.File} listing. The file name suffixes are used as a
* filter input and is given to the class via the string array property:<br>
* <dd>{@code filenameSuffixesToExclude}
* <p>
* A recommended way of doing this is by referencing to the component which uses
* this class for file listing. In this way all properties are set in the same
* component and this utility component is kept in the background with only
* initial configuration necessary.
*
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
* @see File#list(java.io.FilenameFilter) java.io.File.list
* @see FilenameFilter java.io.FilenameFilter
*/
public class FilenameSuffixFilter implements FilenameFilter {
// Members
String[] mFilenameSuffixesToExclude;
/** Creates a {@code FileNameSuffixFilter} */
public FilenameSuffixFilter() {
}
public void setFilenameSuffixesToExclude(String[] pFilenameSuffixesToExclude) {
mFilenameSuffixesToExclude = pFilenameSuffixesToExclude;
}
public String[] getFilenameSuffixesToExclude() {
return mFilenameSuffixesToExclude;
}
/**
* This method implements the {@code java.io.FilenameFilter} interface.
* <p/>
*
* @param pDir the directory in which the file was found.
* @param pName the pName of the file.
* @return {@code true} if the pName should be included in the file list;
* {@code false} otherwise.
*/
public boolean accept(final File pDir, final String pName) {
if (StringUtil.isEmpty(mFilenameSuffixesToExclude)) {
return true;
}
for (String aMFilenameSuffixesToExclude : mFilenameSuffixesToExclude) {
// -- Edit by haraldK, to make interfaces more consistent
// if (StringUtil.filenameSuffixIs(pName, mFilenameSuffixesToExclude[i])) {
if (aMFilenameSuffixesToExclude.equals(FileUtil.getExtension(pName))) {
return false;
}
}
return true;
}
}
@@ -38,6 +38,8 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.*; import java.io.*;
/** /**
@@ -75,10 +77,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
* @see java.io.FilterInputStream#in * @see java.io.FilterInputStream#in
*/ */
public LittleEndianDataInputStream(final InputStream pStream) { public LittleEndianDataInputStream(final InputStream pStream) {
super(pStream); super(Validate.notNull(pStream, "stream"));
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
} }
/** /**
@@ -93,9 +92,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
*/ */
public boolean readBoolean() throws IOException { public boolean readBoolean() throws IOException {
int b = in.read(); int b = in.read();
if (b < 0) { if (b < 0) {
throw new EOFException(); throw new EOFException();
} }
return b != 0; return b != 0;
} }
@@ -110,9 +111,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
*/ */
public byte readByte() throws IOException { public byte readByte() throws IOException {
int b = in.read(); int b = in.read();
if (b < 0) { if (b < 0) {
throw new EOFException(); throw new EOFException();
} }
return (byte) b; return (byte) b;
} }
@@ -128,9 +131,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
*/ */
public int readUnsignedByte() throws IOException { public int readUnsignedByte() throws IOException {
int b = in.read(); int b = in.read();
if (b < 0) { if (b < 0) {
throw new EOFException(); throw new EOFException();
} }
return b; return b;
} }
@@ -146,11 +151,13 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public short readShort() throws IOException { public short readShort() throws IOException {
int byte1 = in.read(); int byte1 = in.read();
int byte2 = in.read(); int byte2 = in.read();
// only need to test last byte read // only need to test last byte read
// if byte1 is -1 so is byte2 // if byte1 is -1 so is byte2
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24); return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
} }
@@ -166,10 +173,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public int readUnsignedShort() throws IOException { public int readUnsignedShort() throws IOException {
int byte1 = in.read(); int byte1 = in.read();
int byte2 = in.read(); int byte2 = in.read();
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
return (byte2 << 8) + byte1; return (byte2 << 8) + byte1;
} }
@@ -185,9 +193,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public char readChar() throws IOException { public char readChar() throws IOException {
int byte1 = in.read(); int byte1 = in.read();
int byte2 = in.read(); int byte2 = in.read();
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24)); return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
} }
@@ -210,6 +220,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
if (byte4 < 0) { if (byte4 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (byte4 << 24) + ((byte3 << 24) >>> 8) return (byte4 << 24) + ((byte3 << 24) >>> 8)
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24); + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
} }
@@ -236,11 +247,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
if (byte8 < 0) { if (byte8 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (byte8 << 56) + ((byte7 << 56) >>> 8) return (byte8 << 56) + ((byte7 << 56) >>> 8)
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24) + ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40) + ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56); + ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
} }
/** /**
@@ -260,16 +271,17 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public String readUTF() throws IOException { public String readUTF() throws IOException {
int byte1 = in.read(); int byte1 = in.read();
int byte2 = in.read(); int byte2 = in.read();
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
int numbytes = (byte1 << 8) + byte2; int numbytes = (byte1 << 8) + byte2;
char result[] = new char[numbytes]; char result[] = new char[numbytes];
int numread = 0; int numread = 0;
int numchars = 0; int numchars = 0;
while (numread < numbytes) { while (numread < numbytes) {
int c1 = readUnsignedByte(); int c1 = readUnsignedByte();
int c2, c3; int c2, c3;
@@ -281,27 +293,34 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
} }
else if (test == 12 || test == 13) { // two bytes else if (test == 12 || test == 13) { // two bytes
numread += 2; numread += 2;
if (numread > numbytes) { if (numread > numbytes) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
c2 = readUnsignedByte(); c2 = readUnsignedByte();
if ((c2 & 0xC0) != 0x80) { if ((c2 & 0xC0) != 0x80) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
} }
else if (test == 14) { // three bytes else if (test == 14) { // three bytes
numread += 3; numread += 3;
if (numread > numbytes) { if (numread > numbytes) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
c2 = readUnsignedByte(); c2 = readUnsignedByte();
c3 = readUnsignedByte(); c3 = readUnsignedByte();
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
result[numchars++] = (char)
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
} }
else { // malformed else { // malformed
throw new UTFDataFormatException(); throw new UTFDataFormatException();
@@ -396,12 +415,16 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
if (pLength < 0) { if (pLength < 0) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
} }
int count = 0; int count = 0;
while (count < pLength) { while (count < pLength) {
int read = in.read(pBytes, pOffset + count, pLength - count); int read = in.read(pBytes, pOffset + count, pLength - count);
if (read < 0) { if (read < 0) {
throw new EOFException(); throw new EOFException();
} }
count += read; count += read;
} }
} }
@@ -38,6 +38,8 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.*; import java.io.*;
/** /**
@@ -69,7 +71,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
/** /**
* The number of bytes written so far to the little endian output stream. * The number of bytes written so far to the little endian output stream.
*/ */
protected int mWritten; protected int bytesWritten;
/** /**
* Creates a new little endian output stream and chains it to the * Creates a new little endian output stream and chains it to the
@@ -79,10 +81,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
* @see java.io.FilterOutputStream#out * @see java.io.FilterOutputStream#out
*/ */
public LittleEndianDataOutputStream(OutputStream pStream) { public LittleEndianDataOutputStream(OutputStream pStream) {
super(pStream); super(Validate.notNull(pStream, "stream"));
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
} }
/** /**
@@ -93,7 +92,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/ */
public synchronized void write(int pByte) throws IOException { public synchronized void write(int pByte) throws IOException {
out.write(pByte); out.write(pByte);
mWritten++; bytesWritten++;
} }
/** /**
@@ -105,10 +104,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
* @param pLength the number of bytes to write. * @param pLength the number of bytes to write.
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public synchronized void write(byte[] pBytes, int pOffset, int pLength) public synchronized void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
throws IOException {
out.write(pBytes, pOffset, pLength); out.write(pBytes, pOffset, pLength);
mWritten += pLength; bytesWritten += pLength;
} }
@@ -137,7 +135,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/ */
public void writeByte(int pByte) throws IOException { public void writeByte(int pByte) throws IOException {
out.write(pByte); out.write(pByte);
mWritten++; bytesWritten++;
} }
/** /**
@@ -150,7 +148,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
public void writeShort(int pShort) throws IOException { public void writeShort(int pShort) throws IOException {
out.write(pShort & 0xFF); out.write(pShort & 0xFF);
out.write((pShort >>> 8) & 0xFF); out.write((pShort >>> 8) & 0xFF);
mWritten += 2; bytesWritten += 2;
} }
/** /**
@@ -163,7 +161,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
public void writeChar(int pChar) throws IOException { public void writeChar(int pChar) throws IOException {
out.write(pChar & 0xFF); out.write(pChar & 0xFF);
out.write((pChar >>> 8) & 0xFF); out.write((pChar >>> 8) & 0xFF);
mWritten += 2; bytesWritten += 2;
} }
/** /**
@@ -178,7 +176,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write((pInt >>> 8) & 0xFF); out.write((pInt >>> 8) & 0xFF);
out.write((pInt >>> 16) & 0xFF); out.write((pInt >>> 16) & 0xFF);
out.write((pInt >>> 24) & 0xFF); out.write((pInt >>> 24) & 0xFF);
mWritten += 4; bytesWritten += 4;
} }
@@ -198,7 +196,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write((int) (pLong >>> 40) & 0xFF); out.write((int) (pLong >>> 40) & 0xFF);
out.write((int) (pLong >>> 48) & 0xFF); out.write((int) (pLong >>> 48) & 0xFF);
out.write((int) (pLong >>> 56) & 0xFF); out.write((int) (pLong >>> 56) & 0xFF);
mWritten += 8; bytesWritten += 8;
} }
/** /**
@@ -235,10 +233,12 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/ */
public void writeBytes(String pString) throws IOException { public void writeBytes(String pString) throws IOException {
int length = pString.length(); int length = pString.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
out.write((byte) pString.charAt(i)); out.write((byte) pString.charAt(i));
} }
mWritten += length;
bytesWritten += length;
} }
/** /**
@@ -253,12 +253,14 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/ */
public void writeChars(String pString) throws IOException { public void writeChars(String pString) throws IOException {
int length = pString.length(); int length = pString.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
int c = pString.charAt(i); int c = pString.charAt(i);
out.write(c & 0xFF); out.write(c & 0xFF);
out.write((c >>> 8) & 0xFF); out.write((c >>> 8) & 0xFF);
} }
mWritten += length * 2;
bytesWritten += length * 2;
} }
/** /**
@@ -282,6 +284,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
for (int i = 0; i < numchars; i++) { for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i); int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) { if ((c >= 0x0001) && (c <= 0x007F)) {
numbytes++; numbytes++;
} }
@@ -299,8 +302,10 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write((numbytes >>> 8) & 0xFF); out.write((numbytes >>> 8) & 0xFF);
out.write(numbytes & 0xFF); out.write(numbytes & 0xFF);
for (int i = 0; i < numchars; i++) { for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i); int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) { if ((c >= 0x0001) && (c <= 0x007F)) {
out.write(c); out.write(c);
} }
@@ -308,16 +313,16 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write(0xE0 | ((c >> 12) & 0x0F)); out.write(0xE0 | ((c >> 12) & 0x0F));
out.write(0x80 | ((c >> 6) & 0x3F)); out.write(0x80 | ((c >> 6) & 0x3F));
out.write(0x80 | (c & 0x3F)); out.write(0x80 | (c & 0x3F));
mWritten += 2; bytesWritten += 2;
} }
else { else {
out.write(0xC0 | ((c >> 6) & 0x1F)); out.write(0xC0 | ((c >> 6) & 0x1F));
out.write(0x80 | (c & 0x3F)); out.write(0x80 | (c & 0x3F));
mWritten += 1; bytesWritten += 1;
} }
} }
mWritten += numchars + 2; bytesWritten += numchars + 2;
} }
/** /**
@@ -326,9 +331,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
* possible that this number is temporarily less than the actual * possible that this number is temporarily less than the actual
* number of bytes written.) * number of bytes written.)
* @return the value of the {@code written} field. * @return the value of the {@code written} field.
* @see #mWritten * @see #bytesWritten
*/ */
public int size() { public int size() {
return mWritten; return bytesWritten;
} }
} }
@@ -56,58 +56,58 @@ import java.nio.channels.FileChannel;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java#1 $
*/ */
public class LittleEndianRandomAccessFile implements DataInput, DataOutput { public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
private RandomAccessFile mFile; private RandomAccessFile file;
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException { public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
this(FileUtil.resolve(pName), pMode); this(FileUtil.resolve(pName), pMode);
} }
public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException { public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException {
mFile = new RandomAccessFile(pFile, pMode); file = new RandomAccessFile(pFile, pMode);
} }
public void close() throws IOException { public void close() throws IOException {
mFile.close(); file.close();
} }
public FileChannel getChannel() { public FileChannel getChannel() {
return mFile.getChannel(); return file.getChannel();
} }
public FileDescriptor getFD() throws IOException { public FileDescriptor getFD() throws IOException {
return mFile.getFD(); return file.getFD();
} }
public long getFilePointer() throws IOException { public long getFilePointer() throws IOException {
return mFile.getFilePointer(); return file.getFilePointer();
} }
public long length() throws IOException { public long length() throws IOException {
return mFile.length(); return file.length();
} }
public int read() throws IOException { public int read() throws IOException {
return mFile.read(); return file.read();
} }
public int read(final byte[] b) throws IOException { public int read(final byte[] b) throws IOException {
return mFile.read(b); return file.read(b);
} }
public int read(final byte[] b, final int off, final int len) throws IOException { public int read(final byte[] b, final int off, final int len) throws IOException {
return mFile.read(b, off, len); return file.read(b, off, len);
} }
public void readFully(final byte[] b) throws IOException { public void readFully(final byte[] b) throws IOException {
mFile.readFully(b); file.readFully(b);
} }
public void readFully(final byte[] b, final int off, final int len) throws IOException { public void readFully(final byte[] b, final int off, final int len) throws IOException {
mFile.readFully(b, off, len); file.readFully(b, off, len);
} }
public String readLine() throws IOException { public String readLine() throws IOException {
return mFile.readLine(); return file.readLine();
} }
/** /**
@@ -121,10 +121,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public boolean readBoolean() throws IOException { public boolean readBoolean() throws IOException {
int b = mFile.read(); int b = file.read();
if (b < 0) { if (b < 0) {
throw new EOFException(); throw new EOFException();
} }
return b != 0; return b != 0;
} }
@@ -138,10 +140,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public byte readByte() throws IOException { public byte readByte() throws IOException {
int b = mFile.read(); int b = file.read();
if (b < 0) { if (b < 0) {
throw new EOFException(); throw new EOFException();
} }
return (byte) b; return (byte) b;
} }
@@ -156,10 +160,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public int readUnsignedByte() throws IOException { public int readUnsignedByte() throws IOException {
int b = mFile.read(); int b = file.read();
if (b < 0) { if (b < 0) {
throw new EOFException(); throw new EOFException();
} }
return b; return b;
} }
@@ -173,13 +179,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public short readShort() throws IOException { public short readShort() throws IOException {
int byte1 = mFile.read(); int byte1 = file.read();
int byte2 = mFile.read(); int byte2 = file.read();
// only need to test last byte read // only need to test last byte read
// if byte1 is -1 so is byte2 // if byte1 is -1 so is byte2
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24); return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
} }
@@ -193,11 +201,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public int readUnsignedShort() throws IOException { public int readUnsignedShort() throws IOException {
int byte1 = mFile.read(); int byte1 = file.read();
int byte2 = mFile.read(); int byte2 = file.read();
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24); //return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
return (byte2 << 8) + byte1; return (byte2 << 8) + byte1;
} }
@@ -212,11 +222,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public char readChar() throws IOException { public char readChar() throws IOException {
int byte1 = mFile.read(); int byte1 = file.read();
int byte2 = mFile.read(); int byte2 = file.read();
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24)); return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
} }
@@ -231,16 +243,16 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public int readInt() throws IOException { public int readInt() throws IOException {
int byte1 = mFile.read(); int byte1 = file.read();
int byte2 = mFile.read(); int byte2 = file.read();
int byte3 = mFile.read(); int byte3 = file.read();
int byte4 = mFile.read(); int byte4 = file.read();
if (byte4 < 0) { if (byte4 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (byte4 << 24) + ((byte3 << 24) >>> 8)
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24); return (byte4 << 24) + ((byte3 << 24) >>> 8) + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
} }
/** /**
@@ -253,18 +265,19 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public long readLong() throws IOException { public long readLong() throws IOException {
long byte1 = mFile.read(); long byte1 = file.read();
long byte2 = mFile.read(); long byte2 = file.read();
long byte3 = mFile.read(); long byte3 = file.read();
long byte4 = mFile.read(); long byte4 = file.read();
long byte5 = mFile.read(); long byte5 = file.read();
long byte6 = mFile.read(); long byte6 = file.read();
long byte7 = mFile.read(); long byte7 = file.read();
long byte8 = mFile.read(); long byte8 = file.read();
if (byte8 < 0) { if (byte8 < 0) {
throw new EOFException(); throw new EOFException();
} }
return (byte8 << 56) + ((byte7 << 56) >>> 8) return (byte8 << 56) + ((byte7 << 56) >>> 8)
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24) + ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40) + ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
@@ -287,11 +300,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public String readUTF() throws IOException { public String readUTF() throws IOException {
int byte1 = mFile.read(); int byte1 = file.read();
int byte2 = mFile.read(); int byte2 = file.read();
if (byte2 < 0) { if (byte2 < 0) {
throw new EOFException(); throw new EOFException();
} }
int numbytes = (byte1 << 8) + byte2; int numbytes = (byte1 << 8) + byte2;
char result[] = new char[numbytes]; char result[] = new char[numbytes];
int numread = 0; int numread = 0;
@@ -310,27 +325,34 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
} }
else if (test == 12 || test == 13) { // two bytes else if (test == 12 || test == 13) { // two bytes
numread += 2; numread += 2;
if (numread > numbytes) { if (numread > numbytes) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
c2 = readUnsignedByte(); c2 = readUnsignedByte();
if ((c2 & 0xC0) != 0x80) { if ((c2 & 0xC0) != 0x80) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F)); result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
} }
else if (test == 14) { // three bytes else if (test == 14) { // three bytes
numread += 3; numread += 3;
if (numread > numbytes) { if (numread > numbytes) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
c2 = readUnsignedByte(); c2 = readUnsignedByte();
c3 = readUnsignedByte(); c3 = readUnsignedByte();
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
result[numchars++] = (char)
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
} }
else { // malformed else { // malformed
throw new UTFDataFormatException(); throw new UTFDataFormatException();
@@ -378,27 +400,27 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* {@code 0} or if an I/O error occurs. * {@code 0} or if an I/O error occurs.
*/ */
public void seek(final long pos) throws IOException { public void seek(final long pos) throws IOException {
mFile.seek(pos); file.seek(pos);
} }
public void setLength(final long newLength) throws IOException { public void setLength(final long newLength) throws IOException {
mFile.setLength(newLength); file.setLength(newLength);
} }
public int skipBytes(final int n) throws IOException { public int skipBytes(final int n) throws IOException {
return mFile.skipBytes(n); return file.skipBytes(n);
} }
public void write(final byte[] b) throws IOException { public void write(final byte[] b) throws IOException {
mFile.write(b); file.write(b);
} }
public void write(final byte[] b, final int off, final int len) throws IOException { public void write(final byte[] b, final int off, final int len) throws IOException {
mFile.write(b, off, len); file.write(b, off, len);
} }
public void write(final int b) throws IOException { public void write(final int b) throws IOException {
mFile.write(b); file.write(b);
} }
/** /**
@@ -425,7 +447,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public void writeByte(int pByte) throws IOException { public void writeByte(int pByte) throws IOException {
mFile.write(pByte); file.write(pByte);
} }
/** /**
@@ -436,8 +458,8 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public void writeShort(int pShort) throws IOException { public void writeShort(int pShort) throws IOException {
mFile.write(pShort & 0xFF); file.write(pShort & 0xFF);
mFile.write((pShort >>> 8) & 0xFF); file.write((pShort >>> 8) & 0xFF);
} }
/** /**
@@ -448,8 +470,8 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public void writeChar(int pChar) throws IOException { public void writeChar(int pChar) throws IOException {
mFile.write(pChar & 0xFF); file.write(pChar & 0xFF);
mFile.write((pChar >>> 8) & 0xFF); file.write((pChar >>> 8) & 0xFF);
} }
/** /**
@@ -460,11 +482,10 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public void writeInt(int pInt) throws IOException { public void writeInt(int pInt) throws IOException {
mFile.write(pInt & 0xFF); file.write(pInt & 0xFF);
mFile.write((pInt >>> 8) & 0xFF); file.write((pInt >>> 8) & 0xFF);
mFile.write((pInt >>> 16) & 0xFF); file.write((pInt >>> 16) & 0xFF);
mFile.write((pInt >>> 24) & 0xFF); file.write((pInt >>> 24) & 0xFF);
} }
/** /**
@@ -475,14 +496,14 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
*/ */
public void writeLong(long pLong) throws IOException { public void writeLong(long pLong) throws IOException {
mFile.write((int) pLong & 0xFF); file.write((int) pLong & 0xFF);
mFile.write((int) (pLong >>> 8) & 0xFF); file.write((int) (pLong >>> 8) & 0xFF);
mFile.write((int) (pLong >>> 16) & 0xFF); file.write((int) (pLong >>> 16) & 0xFF);
mFile.write((int) (pLong >>> 24) & 0xFF); file.write((int) (pLong >>> 24) & 0xFF);
mFile.write((int) (pLong >>> 32) & 0xFF); file.write((int) (pLong >>> 32) & 0xFF);
mFile.write((int) (pLong >>> 40) & 0xFF); file.write((int) (pLong >>> 40) & 0xFF);
mFile.write((int) (pLong >>> 48) & 0xFF); file.write((int) (pLong >>> 48) & 0xFF);
mFile.write((int) (pLong >>> 56) & 0xFF); file.write((int) (pLong >>> 56) & 0xFF);
} }
/** /**
@@ -515,12 +536,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @param pString the {@code String} value to be written. * @param pString the {@code String} value to be written.
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
* @see #writeByte(int) * @see #writeByte(int)
* @see #mFile * @see #file
*/ */
public void writeBytes(String pString) throws IOException { public void writeBytes(String pString) throws IOException {
int length = pString.length(); int length = pString.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
mFile.write((byte) pString.charAt(i)); file.write((byte) pString.charAt(i));
} }
} }
@@ -532,14 +554,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @param pString a {@code String} value to be written. * @param pString a {@code String} value to be written.
* @throws IOException if the underlying stream throws an IOException. * @throws IOException if the underlying stream throws an IOException.
* @see #writeChar(int) * @see #writeChar(int)
* @see #mFile * @see #file
*/ */
public void writeChars(String pString) throws IOException { public void writeChars(String pString) throws IOException {
int length = pString.length(); int length = pString.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
int c = pString.charAt(i); int c = pString.charAt(i);
mFile.write(c & 0xFF); file.write(c & 0xFF);
mFile.write((c >>> 8) & 0xFF); file.write((c >>> 8) & 0xFF);
} }
} }
@@ -564,6 +587,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
for (int i = 0; i < numchars; i++) { for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i); int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) { if ((c >= 0x0001) && (c <= 0x007F)) {
numbytes++; numbytes++;
} }
@@ -579,21 +603,23 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
throw new UTFDataFormatException(); throw new UTFDataFormatException();
} }
mFile.write((numbytes >>> 8) & 0xFF); file.write((numbytes >>> 8) & 0xFF);
mFile.write(numbytes & 0xFF); file.write(numbytes & 0xFF);
for (int i = 0; i < numchars; i++) { for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i); int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) { if ((c >= 0x0001) && (c <= 0x007F)) {
mFile.write(c); file.write(c);
} }
else if (c > 0x07FF) { else if (c > 0x07FF) {
mFile.write(0xE0 | ((c >> 12) & 0x0F)); file.write(0xE0 | ((c >> 12) & 0x0F));
mFile.write(0x80 | ((c >> 6) & 0x3F)); file.write(0x80 | ((c >> 6) & 0x3F));
mFile.write(0x80 | (c & 0x3F)); file.write(0x80 | (c & 0x3F));
} }
else { else {
mFile.write(0xC0 | ((c >> 6) & 0x1F)); file.write(0xC0 | ((c >> 6) & 0x1F));
mFile.write(0x80 | (c & 0x3F)); file.write(0x80 | (c & 0x3F));
} }
} }
} }
@@ -65,13 +65,13 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
final static class MemoryCache extends StreamCache { final static class MemoryCache extends StreamCache {
final static int BLOCK_SIZE = 1 << 13; final static int BLOCK_SIZE = 1 << 13;
private final List<byte[]> mCache = new ArrayList<byte[]>(); private final List<byte[]> cache = new ArrayList<byte[]>();
private long mLength; private long length;
private long mPosition; private long position;
private long mStart; private long start;
private byte[] getBlock() throws IOException { private byte[] getBlock() throws IOException {
final long currPos = mPosition - mStart; final long currPos = position - start;
if (currPos < 0) { if (currPos < 0) {
throw new IOException("StreamCache flushed before read position"); throw new IOException("StreamCache flushed before read position");
} }
@@ -82,31 +82,31 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
throw new IOException("Memory cache max size exceeded"); throw new IOException("Memory cache max size exceeded");
} }
if (index >= mCache.size()) { if (index >= cache.size()) {
try { try {
mCache.add(new byte[BLOCK_SIZE]); cache.add(new byte[BLOCK_SIZE]);
// System.out.println("Allocating new block, size: " + BLOCK_SIZE); // System.out.println("Allocating new block, size: " + BLOCK_SIZE);
// System.out.println("New total size: " + mCache.size() * BLOCK_SIZE + " (" + mCache.size() + " blocks)"); // System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)");
} }
catch (OutOfMemoryError e) { catch (OutOfMemoryError e) {
throw new IOException("No more memory for cache: " + mCache.size() * BLOCK_SIZE); throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
} }
} }
//System.out.println("index: " + index); //System.out.println("index: " + index);
return mCache.get((int) index); return cache.get((int) index);
} }
public void write(final int pByte) throws IOException { public void write(final int pByte) throws IOException {
byte[] buffer = getBlock(); byte[] buffer = getBlock();
int idx = (int) (mPosition % BLOCK_SIZE); int idx = (int) (position % BLOCK_SIZE);
buffer[idx] = (byte) pByte; buffer[idx] = (byte) pByte;
mPosition++; position++;
if (mPosition > mLength) { if (position > length) {
mLength = mPosition; length = position;
} }
} }
@@ -115,28 +115,28 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
byte[] buffer = getBlock(); byte[] buffer = getBlock();
for (int i = 0; i < pLength; i++) { for (int i = 0; i < pLength; i++) {
int index = (int) mPosition % BLOCK_SIZE; int index = (int) position % BLOCK_SIZE;
if (index == 0) { if (index == 0) {
buffer = getBlock(); buffer = getBlock();
} }
buffer[index] = pBuffer[pOffset + i]; buffer[index] = pBuffer[pOffset + i];
mPosition++; position++;
} }
if (mPosition > mLength) { if (position > length) {
mLength = mPosition; length = position;
} }
} }
public int read() throws IOException { public int read() throws IOException {
if (mPosition >= mLength) { if (position >= length) {
return -1; return -1;
} }
byte[] buffer = getBlock(); byte[] buffer = getBlock();
int idx = (int) (mPosition % BLOCK_SIZE); int idx = (int) (position % BLOCK_SIZE);
mPosition++; position++;
return buffer[idx] & 0xff; return buffer[idx] & 0xff;
} }
@@ -144,33 +144,33 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
// TODO: OptimizeMe!!! // TODO: OptimizeMe!!!
@Override @Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (mPosition >= mLength) { if (position >= length) {
return -1; return -1;
} }
byte[] buffer = getBlock(); byte[] buffer = getBlock();
int bufferPos = (int) (mPosition % BLOCK_SIZE); int bufferPos = (int) (position % BLOCK_SIZE);
// Find maxIdx and simplify test in for-loop // Find maxIdx and simplify test in for-loop
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), mLength - mPosition); int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position);
int i; int i;
//for (i = 0; i < pLength && i < buffer.length - idx && i < mLength - mPosition; i++) { //for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) {
for (i = 0; i < maxLen; i++) { for (i = 0; i < maxLen; i++) {
pBytes[pOffset + i] = buffer[bufferPos + i]; pBytes[pOffset + i] = buffer[bufferPos + i];
} }
mPosition += i; position += i;
return i; return i;
} }
public void seek(final long pPosition) throws IOException { public void seek(final long pPosition) throws IOException {
if (pPosition < mStart) { if (pPosition < start) {
throw new IOException("Seek before flush position"); throw new IOException("Seek before flush position");
} }
mPosition = pPosition; position = pPosition;
} }
@Override @Override
@@ -178,14 +178,14 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1; int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
for (int i = 0; i < firstPos; i++) { for (int i = 0; i < firstPos; i++) {
mCache.remove(0); cache.remove(0);
} }
mStart = pPosition; start = pPosition;
} }
public long getPosition() { public long getPosition() {
return mPosition; return position;
} }
} }
} }
@@ -50,13 +50,12 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
// TODO: Package private SeekableDelegate? // TODO: Package private SeekableDelegate?
// TODO: Both read and write must update stream position // TODO: Both read and write must update stream position
//private int mPosition = -1; //private int position = -1;
/** This random access stream, wrapped in an {@code InputStream} */ /** This random access stream, wrapped in an {@code InputStream} */
SeekableInputStream mInputView = null; SeekableInputStream inputView = null;
/** This random access stream, wrapped in an {@code OutputStream} */ /** This random access stream, wrapped in an {@code OutputStream} */
SeekableOutputStream mOutputView = null; SeekableOutputStream outputView = null;
// TODO: Create an Input and an Output interface matching InputStream and OutputStream? // TODO: Create an Input and an Output interface matching InputStream and OutputStream?
public int read() throws IOException { public int read() throws IOException {
@@ -119,10 +118,10 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
* @return a {@code SeekableInputStream} reading from this stream * @return a {@code SeekableInputStream} reading from this stream
*/ */
public final SeekableInputStream asInputStream() { public final SeekableInputStream asInputStream() {
if (mInputView == null) { if (inputView == null) {
mInputView = new InputStreamView(this); inputView = new InputStreamView(this);
} }
return mInputView; return inputView;
} }
/** /**
@@ -134,15 +133,15 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
* @return a {@code SeekableOutputStream} writing to this stream * @return a {@code SeekableOutputStream} writing to this stream
*/ */
public final SeekableOutputStream asOutputStream() { public final SeekableOutputStream asOutputStream() {
if (mOutputView == null) { if (outputView == null) {
mOutputView = new OutputStreamView(this); outputView = new OutputStreamView(this);
} }
return mOutputView; return outputView;
} }
static final class InputStreamView extends SeekableInputStream { static final class InputStreamView extends SeekableInputStream {
// TODO: Consider adding synchonization (on mStream) for all operations // TODO: Consider adding synchonization (on stream) for all operations
// TODO: Is is a good thing that close/flush etc works on mStream? // TODO: Is is a good thing that close/flush etc works on stream?
// - Or should it rather just work on the views? // - Or should it rather just work on the views?
// - Allow multiple views? // - Allow multiple views?
@@ -190,8 +189,8 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
} }
static final class OutputStreamView extends SeekableOutputStream { static final class OutputStreamView extends SeekableOutputStream {
// TODO: Consider adding synchonization (on mStream) for all operations // TODO: Consider adding synchonization (on stream) for all operations
// TODO: Is is a good thing that close/flush etc works on mStream? // TODO: Is is a good thing that close/flush etc works on stream?
// - Or should it rather just work on the views? // - Or should it rather just work on the views?
// - Allow multiple views? // - Allow multiple views?
@@ -43,15 +43,15 @@ import java.util.Stack;
public abstract class SeekableInputStream extends InputStream implements Seekable { public abstract class SeekableInputStream extends InputStream implements Seekable {
// TODO: It's at the moment not possible to create subclasses outside this // TODO: It's at the moment not possible to create subclasses outside this
// package, as there's no access to mPosition. mPosition needs to be // package, as there's no access to position. position needs to be
// updated from the read/read/read methods... // updated from the read/read/read methods...
/** The stream position in this stream */ /** The stream position in this stream */
long mPosition; long position;
long mFlushedPosition; long flushedPosition;
boolean mClosed; boolean closed;
protected Stack<Long> mMarkedPositions = new Stack<Long>(); protected Stack<Long> markedPositions = new Stack<Long>();
/// InputStream overrides /// InputStream overrides
@Override @Override
@@ -69,17 +69,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
* @throws IOException if an I/O exception occurs during skip * @throws IOException if an I/O exception occurs during skip
*/ */
@Override @Override
public final long skip(long pLength) throws IOException { public final long skip(final long pLength) throws IOException {
long pos = mPosition; long pos = position;
if (pos + pLength < mFlushedPosition) { long wantedPosition = pos + pLength;
if (wantedPosition < flushedPosition) {
throw new IOException("position < flushedPosition"); throw new IOException("position < flushedPosition");
} }
// Stop at stream length for compatibility, even though it's allowed // Stop at stream length for compatibility, even though it might be allowed
// to seek past end of stream // to seek past end of stream
seek(Math.min(pos + pLength, pos + available())); int available = available();
if (available > 0) {
seek(Math.min(wantedPosition, pos + available));
}
// TODO: Add optimization for streams with known length!
else {
// Slow mode...
int toSkip = (int) Math.max(Math.min(pLength, 512), -512);
while (toSkip > 0 && read() >= 0) {
toSkip--;
}
}
return mPosition - pos; return position - pos;
} }
@Override @Override
@@ -88,7 +100,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
// TODO: We don't really need to do this.. Is it a good idea? // TODO: We don't really need to do this.. Is it a good idea?
try { try {
flushBefore(Math.max(mPosition - pLimit, mFlushedPosition)); flushBefore(Math.max(position - pLimit, flushedPosition));
} }
catch (IOException ignore) { catch (IOException ignore) {
// Ignore, as it's not really critical // Ignore, as it's not really critical
@@ -111,29 +123,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
// NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException), // NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException),
// but it's kind of inconsistent with reset that throws IOException... // but it's kind of inconsistent with reset that throws IOException...
if (pPosition < mFlushedPosition) { if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition"); throw new IndexOutOfBoundsException("position < flushedPosition");
} }
seekImpl(pPosition); seekImpl(pPosition);
mPosition = pPosition; position = pPosition;
} }
protected abstract void seekImpl(long pPosition) throws IOException; protected abstract void seekImpl(long pPosition) throws IOException;
public final void mark() { public final void mark() {
mMarkedPositions.push(mPosition); markedPositions.push(position);
} }
@Override @Override
public final void reset() throws IOException { public final void reset() throws IOException {
checkOpen(); checkOpen();
if (!mMarkedPositions.isEmpty()) { if (!markedPositions.isEmpty()) {
long newPos = mMarkedPositions.pop(); long newPos = markedPositions.pop();
// NOTE: This is correct according to javax.imageio (IOException), // NOTE: This is correct according to javax.imageio (IOException),
// but it's kind of inconsistent with seek that throws IndexOutOfBoundsException... // but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
if (newPos < mFlushedPosition) { if (newPos < flushedPosition) {
throw new IOException("Previous marked position has been discarded"); throw new IOException("Previous marked position has been discarded");
} }
@@ -150,7 +162,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
} }
public final void flushBefore(long pPosition) throws IOException { public final void flushBefore(long pPosition) throws IOException {
if (pPosition < mFlushedPosition) { if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition"); throw new IndexOutOfBoundsException("position < flushedPosition");
} }
if (pPosition > getStreamPosition()) { if (pPosition > getStreamPosition()) {
@@ -158,7 +170,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
} }
checkOpen(); checkOpen();
flushBeforeImpl(pPosition); flushBeforeImpl(pPosition);
mFlushedPosition = pPosition; flushedPosition = pPosition;
} }
/** /**
@@ -172,21 +184,21 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
protected abstract void flushBeforeImpl(long pPosition) throws IOException; protected abstract void flushBeforeImpl(long pPosition) throws IOException;
public final void flush() throws IOException { public final void flush() throws IOException {
flushBefore(mFlushedPosition); flushBefore(flushedPosition);
} }
public final long getFlushedPosition() throws IOException { public final long getFlushedPosition() throws IOException {
checkOpen(); checkOpen();
return mFlushedPosition; return flushedPosition;
} }
public final long getStreamPosition() throws IOException { public final long getStreamPosition() throws IOException {
checkOpen(); checkOpen();
return mPosition; return position;
} }
protected final void checkOpen() throws IOException { protected final void checkOpen() throws IOException {
if (mClosed) { if (closed) {
throw new IOException("closed"); throw new IOException("closed");
} }
} }
@@ -194,7 +206,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
@Override @Override
public final void close() throws IOException { public final void close() throws IOException {
checkOpen(); checkOpen();
mClosed = true; closed = true;
closeImpl(); closeImpl();
} }
@@ -211,7 +223,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
*/ */
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
if (!mClosed) { if (!closed) {
try { try {
close(); close();
} }
@@ -43,11 +43,11 @@ import java.util.Stack;
*/ */
public abstract class SeekableOutputStream extends OutputStream implements Seekable { public abstract class SeekableOutputStream extends OutputStream implements Seekable {
// TODO: Implement // TODO: Implement
long mPosition; long position;
long mFlushedPosition; long flushedPosition;
boolean mClosed; boolean closed;
protected Stack<Long> mMarkedPositions = new Stack<Long>(); protected Stack<Long> markedPositions = new Stack<Long>();
/// Outputstream overrides /// Outputstream overrides
@Override @Override
@@ -63,28 +63,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException), // TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
// but it's inconsistent with reset that throws IOException... // but it's inconsistent with reset that throws IOException...
if (pPosition < mFlushedPosition) { if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition!"); throw new IndexOutOfBoundsException("position < flushedPosition!");
} }
seekImpl(pPosition); seekImpl(pPosition);
mPosition = pPosition; position = pPosition;
} }
protected abstract void seekImpl(long pPosition) throws IOException; protected abstract void seekImpl(long pPosition) throws IOException;
public final void mark() { public final void mark() {
mMarkedPositions.push(mPosition); markedPositions.push(position);
} }
public final void reset() throws IOException { public final void reset() throws IOException {
checkOpen(); checkOpen();
if (!mMarkedPositions.isEmpty()) { if (!markedPositions.isEmpty()) {
long newPos = mMarkedPositions.pop(); long newPos = markedPositions.pop();
// TODO: This is correct according to javax.imageio (IOException), // TODO: This is correct according to javax.imageio (IOException),
// but it's inconsistent with seek that throws IndexOutOfBoundsException... // but it's inconsistent with seek that throws IndexOutOfBoundsException...
if (newPos < mFlushedPosition) { if (newPos < flushedPosition) {
throw new IOException("Previous marked position has been discarded!"); throw new IOException("Previous marked position has been discarded!");
} }
@@ -93,7 +93,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
} }
public final void flushBefore(long pPosition) throws IOException { public final void flushBefore(long pPosition) throws IOException {
if (pPosition < mFlushedPosition) { if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition!"); throw new IndexOutOfBoundsException("position < flushedPosition!");
} }
if (pPosition > getStreamPosition()) { if (pPosition > getStreamPosition()) {
@@ -101,28 +101,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
} }
checkOpen(); checkOpen();
flushBeforeImpl(pPosition); flushBeforeImpl(pPosition);
mFlushedPosition = pPosition; flushedPosition = pPosition;
} }
protected abstract void flushBeforeImpl(long pPosition) throws IOException; protected abstract void flushBeforeImpl(long pPosition) throws IOException;
@Override @Override
public final void flush() throws IOException { public final void flush() throws IOException {
flushBefore(mFlushedPosition); flushBefore(flushedPosition);
} }
public final long getFlushedPosition() throws IOException { public final long getFlushedPosition() throws IOException {
checkOpen(); checkOpen();
return mFlushedPosition; return flushedPosition;
} }
public final long getStreamPosition() throws IOException { public final long getStreamPosition() throws IOException {
checkOpen(); checkOpen();
return mPosition; return position;
} }
protected final void checkOpen() throws IOException { protected final void checkOpen() throws IOException {
if (mClosed) { if (closed) {
throw new IOException("closed"); throw new IOException("closed");
} }
} }
@@ -130,7 +130,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
@Override @Override
public final void close() throws IOException { public final void close() throws IOException {
checkOpen(); checkOpen();
mClosed = true; closed = true;
closeImpl(); closeImpl();
} }
@@ -28,6 +28,8 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.StringReader; import java.io.StringReader;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
@@ -42,13 +44,13 @@ import java.io.Reader;
*/ */
public class StringArrayReader extends StringReader { public class StringArrayReader extends StringReader {
private StringReader mCurrent; private StringReader current;
private String[] mStrings; private String[] strings;
protected final Object mLock; protected final Object finalLock;
private int mCurrentSting; private int currentSting;
private int mMarkedString; private int markedString;
private int mMark; private int mark;
private int mNext; private int next;
/** /**
* Create a new string array reader. * Create a new string array reader.
@@ -57,28 +59,28 @@ public class StringArrayReader extends StringReader {
*/ */
public StringArrayReader(final String[] pStrings) { public StringArrayReader(final String[] pStrings) {
super(""); super("");
if (pStrings == null) {
throw new NullPointerException("strings == null");
}
mLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the Validate.notNull(pStrings, "strings");
finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
// reference can't change, only it's elements // reference can't change, only it's elements
mStrings = pStrings.clone(); // Defensive copy for content strings = pStrings.clone(); // Defensive copy for content
nextReader(); nextReader();
} }
protected final Reader nextReader() { protected final Reader nextReader() {
if (mCurrentSting >= mStrings.length) { if (currentSting >= strings.length) {
mCurrent = new EmptyReader(); current = new EmptyReader();
} }
else { else {
mCurrent = new StringReader(mStrings[mCurrentSting++]); current = new StringReader(strings[currentSting++]);
} }
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
mNext = 0; // NOTE: Reset next for every reader, and record marked reader in mark/reset methods!
next = 0;
return mCurrent; return current;
} }
/** /**
@@ -87,15 +89,15 @@ public class StringArrayReader extends StringReader {
* @throws IOException if the stream is closed * @throws IOException if the stream is closed
*/ */
protected final void ensureOpen() throws IOException { protected final void ensureOpen() throws IOException {
if (mStrings == null) { if (strings == null) {
throw new IOException("Stream closed"); throw new IOException("Stream closed");
} }
} }
public void close() { public void close() {
super.close(); super.close();
mStrings = null; strings = null;
mCurrent.close(); current.close();
} }
public void mark(int pReadLimit) throws IOException { public void mark(int pReadLimit) throws IOException {
@@ -103,29 +105,29 @@ public class StringArrayReader extends StringReader {
throw new IllegalArgumentException("Read limit < 0"); throw new IllegalArgumentException("Read limit < 0");
} }
synchronized (mLock) { synchronized (finalLock) {
ensureOpen(); ensureOpen();
mMark = mNext; mark = next;
mMarkedString = mCurrentSting; markedString = currentSting;
mCurrent.mark(pReadLimit); current.mark(pReadLimit);
} }
} }
public void reset() throws IOException { public void reset() throws IOException {
synchronized (mLock) { synchronized (finalLock) {
ensureOpen(); ensureOpen();
if (mCurrentSting != mMarkedString) { if (currentSting != markedString) {
mCurrentSting = mMarkedString - 1; currentSting = markedString - 1;
nextReader(); nextReader();
mCurrent.skip(mMark); current.skip(mark);
} }
else { else {
mCurrent.reset(); current.reset();
} }
mNext = mMark; next = mark;
} }
} }
@@ -134,49 +136,49 @@ public class StringArrayReader extends StringReader {
} }
public int read() throws IOException { public int read() throws IOException {
synchronized (mLock) { synchronized (finalLock) {
int read = mCurrent.read(); int read = current.read();
if (read < 0 && mCurrentSting < mStrings.length) { if (read < 0 && currentSting < strings.length) {
nextReader(); nextReader();
return read(); // In case of empty strings return read(); // In case of empty strings
} }
mNext++; next++;
return read; return read;
} }
} }
public int read(char pBuffer[], int pOffset, int pLength) throws IOException { public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (mLock) { synchronized (finalLock) {
int read = mCurrent.read(pBuffer, pOffset, pLength); int read = current.read(pBuffer, pOffset, pLength);
if (read < 0 && mCurrentSting < mStrings.length) { if (read < 0 && currentSting < strings.length) {
nextReader(); nextReader();
return read(pBuffer, pOffset, pLength); // In case of empty strings return read(pBuffer, pOffset, pLength); // In case of empty strings
} }
mNext += read; next += read;
return read; return read;
} }
} }
public boolean ready() throws IOException { public boolean ready() throws IOException {
return mCurrent.ready(); return current.ready();
} }
public long skip(long pChars) throws IOException { public long skip(long pChars) throws IOException {
synchronized (mLock) { synchronized (finalLock) {
long skipped = mCurrent.skip(pChars); long skipped = current.skip(pChars);
if (skipped == 0 && mCurrentSting < mStrings.length) { if (skipped == 0 && currentSting < strings.length) {
nextReader(); nextReader();
return skip(pChars); return skip(pChars);
} }
mNext += skipped; next += skipped;
return skipped; return skipped;
} }
@@ -43,8 +43,8 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $
*/ */
public final class SubStream extends FilterInputStream { public final class SubStream extends FilterInputStream {
private long mLeft; private long bytesLeft;
private int mMarkLimit; private int markLimit;
/** /**
* Creates a {@code SubStream} of the given {@code pStream}. * Creates a {@code SubStream} of the given {@code pStream}.
@@ -54,7 +54,7 @@ public final class SubStream extends FilterInputStream {
*/ */
public SubStream(final InputStream pStream, final long pLength) { public SubStream(final InputStream pStream, final long pLength) {
super(Validate.notNull(pStream, "stream")); super(Validate.notNull(pStream, "stream"));
mLeft = pLength; bytesLeft = pLength;
} }
/** /**
@@ -64,32 +64,32 @@ public final class SubStream extends FilterInputStream {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// NOTE: Do not close the underlying stream // NOTE: Do not close the underlying stream
while (mLeft > 0) { while (bytesLeft > 0) {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
skip(mLeft); skip(bytesLeft);
} }
} }
@Override @Override
public int available() throws IOException { public int available() throws IOException {
return (int) Math.min(super.available(), mLeft); return (int) Math.min(super.available(), bytesLeft);
} }
@Override @Override
public void mark(int pReadLimit) { public void mark(int pReadLimit) {
super.mark(pReadLimit);// This either succeeds or does nothing... super.mark(pReadLimit);// This either succeeds or does nothing...
mMarkLimit = pReadLimit; markLimit = pReadLimit;
} }
@Override @Override
public void reset() throws IOException { public void reset() throws IOException {
super.reset();// This either succeeds or throws IOException super.reset();// This either succeeds or throws IOException
mLeft += mMarkLimit; bytesLeft += markLimit;
} }
@Override @Override
public int read() throws IOException { public int read() throws IOException {
if (mLeft-- <= 0) { if (bytesLeft-- <= 0) {
return -1; return -1;
} }
return super.read(); return super.read();
@@ -102,12 +102,12 @@ public final class SubStream extends FilterInputStream {
@Override @Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (mLeft <= 0) { if (bytesLeft <= 0) {
return -1; return -1;
} }
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength)); int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
mLeft = read < 0 ? 0 : mLeft - read; bytesLeft = read < 0 ? 0 : bytesLeft - read;
return read; return read;
} }
@@ -118,8 +118,8 @@ public final class SubStream extends FilterInputStream {
* @return the maximum number of bytes to read * @return the maximum number of bytes to read
*/ */
private long findMaxLen(long pLength) { private long findMaxLen(long pLength) {
if (mLeft < pLength) { if (bytesLeft < pLength) {
return (int) Math.max(mLeft, 0); return (int) Math.max(bytesLeft, 0);
} }
else { else {
return pLength; return pLength;
@@ -129,7 +129,7 @@ public final class SubStream extends FilterInputStream {
@Override @Override
public long skip(long pLength) throws IOException { public long skip(long pLength) throws IOException {
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1 long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
mLeft -= skipped; bytesLeft -= skipped;
return skipped; return skipped;
} }
} }
@@ -50,7 +50,8 @@ final class Win32Lnk extends File {
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F' (byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
}; };
private final File mTarget; private final File target;
private static final int FLAG_ITEM_ID_LIST = 0x01; private static final int FLAG_ITEM_ID_LIST = 0x01;
private static final int FLAG_FILE_LOC_INFO = 0x02; private static final int FLAG_FILE_LOC_INFO = 0x02;
private static final int FLAG_DESC_STRING = 0x04; private static final int FLAG_DESC_STRING = 0x04;
@@ -65,10 +66,10 @@ final class Win32Lnk extends File {
File target = parse(this); File target = parse(this);
if (target == this) { if (target == this) {
// NOTE: This is a workaround // NOTE: This is a workaround
// mTarget = this causes infinite loops in some methods // target = this causes infinite loops in some methods
target = new File(pPath); target = new File(pPath);
} }
mTarget = target; this.target = target;
} }
Win32Lnk(final File pPath) throws IOException { Win32Lnk(final File pPath) throws IOException {
@@ -336,24 +337,24 @@ final class Win32Lnk extends File {
*/ */
public File getTarget() { public File getTarget() {
return mTarget; return target;
} }
// java.io.File overrides below // java.io.File overrides below
@Override @Override
public boolean isDirectory() { public boolean isDirectory() {
return mTarget.isDirectory(); return target.isDirectory();
} }
@Override @Override
public boolean canRead() { public boolean canRead() {
return mTarget.canRead(); return target.canRead();
} }
@Override @Override
public boolean canWrite() { public boolean canWrite() {
return mTarget.canWrite(); return target.canWrite();
} }
// NOTE: equals is implemented using compareto == 0 // NOTE: equals is implemented using compareto == 0
@@ -362,7 +363,7 @@ final class Win32Lnk extends File {
// TODO: Verify this // TODO: Verify this
// Probably not a good idea, as it IS NOT THE SAME file // Probably not a good idea, as it IS NOT THE SAME file
// It's probably better to not override // It's probably better to not override
return mTarget.compareTo(pathname); return target.compareTo(pathname);
} }
*/ */
@@ -375,7 +376,7 @@ final class Win32Lnk extends File {
@Override @Override
public boolean exists() { public boolean exists() {
return mTarget.exists(); return target.exists();
} }
// A .lnk may be absolute // A .lnk may be absolute
@@ -385,12 +386,12 @@ final class Win32Lnk extends File {
// Theses should be resolved according to the API (for Unix). // Theses should be resolved according to the API (for Unix).
@Override @Override
public File getCanonicalFile() throws IOException { public File getCanonicalFile() throws IOException {
return mTarget.getCanonicalFile(); return target.getCanonicalFile();
} }
@Override @Override
public String getCanonicalPath() throws IOException { public String getCanonicalPath() throws IOException {
return mTarget.getCanonicalPath(); return target.getCanonicalPath();
} }
//public String getName() { //public String getName() {
@@ -402,47 +403,47 @@ final class Win32Lnk extends File {
// public boolean isAbsolute() { // public boolean isAbsolute() {
@Override @Override
public boolean isFile() { public boolean isFile() {
return mTarget.isFile(); return target.isFile();
} }
@Override @Override
public boolean isHidden() { public boolean isHidden() {
return mTarget.isHidden(); return target.isHidden();
} }
@Override @Override
public long lastModified() { public long lastModified() {
return mTarget.lastModified(); return target.lastModified();
} }
@Override @Override
public long length() { public long length() {
return mTarget.length(); return target.length();
} }
@Override @Override
public String[] list() { public String[] list() {
return mTarget.list(); return target.list();
} }
@Override @Override
public String[] list(final FilenameFilter filter) { public String[] list(final FilenameFilter filter) {
return mTarget.list(filter); return target.list(filter);
} }
@Override @Override
public File[] listFiles() { public File[] listFiles() {
return Win32File.wrap(mTarget.listFiles()); return Win32File.wrap(target.listFiles());
} }
@Override @Override
public File[] listFiles(final FileFilter filter) { public File[] listFiles(final FileFilter filter) {
return Win32File.wrap(mTarget.listFiles(filter)); return Win32File.wrap(target.listFiles(filter));
} }
@Override @Override
public File[] listFiles(final FilenameFilter filter) { public File[] listFiles(final FilenameFilter filter) {
return Win32File.wrap(mTarget.listFiles(filter)); return Win32File.wrap(target.listFiles(filter));
} }
// Makes no sense, does it? // Makes no sense, does it?
@@ -454,19 +455,19 @@ final class Win32Lnk extends File {
@Override @Override
public boolean setLastModified(long time) { public boolean setLastModified(long time) {
return mTarget.setLastModified(time); return target.setLastModified(time);
} }
@Override @Override
public boolean setReadOnly() { public boolean setReadOnly() {
return mTarget.setReadOnly(); return target.setReadOnly();
} }
@Override @Override
public String toString() { public String toString() {
if (mTarget.equals(this)) { if (target.equals(this)) {
return super.toString(); return super.toString();
} }
return super.toString() + " -> " + mTarget.toString(); return super.toString() + " -> " + target.toString();
} }
} }
@@ -51,10 +51,11 @@ import java.nio.CharBuffer;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java#2 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java#2 $
*/ */
public class WriterOutputStream extends OutputStream { public class WriterOutputStream extends OutputStream {
protected Writer mWriter; protected Writer writer;
final protected Decoder mDecoder; final protected Decoder decoder;
final ByteArrayOutputStream mBufferStream = new FastByteArrayOutputStream(1024); final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024);
private volatile boolean mIsFlushing = false; // Ugly but critical...
private volatile boolean isFlushing = false; // Ugly but critical...
private static final boolean NIO_AVAILABLE = isNIOAvailable(); private static final boolean NIO_AVAILABLE = isNIOAvailable();
@@ -71,8 +72,8 @@ public class WriterOutputStream extends OutputStream {
} }
public WriterOutputStream(final Writer pWriter, final String pCharset) { public WriterOutputStream(final Writer pWriter, final String pCharset) {
mWriter = pWriter; writer = pWriter;
mDecoder = getDecoder(pCharset); decoder = getDecoder(pCharset);
} }
public WriterOutputStream(final Writer pWriter) { public WriterOutputStream(final Writer pWriter) {
@@ -94,14 +95,14 @@ public class WriterOutputStream extends OutputStream {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
flush(); flush();
mWriter.close(); writer.close();
mWriter = null; writer = null;
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
flushBuffer(); flushBuffer();
mWriter.flush(); writer.flush();
} }
@Override @Override
@@ -115,22 +116,22 @@ public class WriterOutputStream extends OutputStream {
@Override @Override
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException { public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
flushBuffer(); flushBuffer();
mDecoder.decodeTo(mWriter, pBytes, pOffset, pLength); decoder.decodeTo(writer, pBytes, pOffset, pLength);
} }
@Override @Override
public final void write(int pByte) { public final void write(int pByte) {
// TODO: Is it possible to know if this is a good place in the stream to // TODO: Is it possible to know if this is a good place in the stream to
// flush? It might be in the middle of a multi-byte encoded character.. // flush? It might be in the middle of a multi-byte encoded character..
mBufferStream.write(pByte); bufferStream.write(pByte);
} }
private void flushBuffer() throws IOException { private void flushBuffer() throws IOException {
if (!mIsFlushing && mBufferStream.size() > 0) { if (!isFlushing && bufferStream.size() > 0) {
mIsFlushing = true; isFlushing = true;
mBufferStream.writeTo(this); // NOTE: Avoids cloning buffer array bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
mBufferStream.reset(); bufferStream.reset();
mIsFlushing = false; isFlushing = false;
} }
} }
@@ -138,7 +139,7 @@ public class WriterOutputStream extends OutputStream {
public static void main(String[] pArgs) throws IOException { public static void main(String[] pArgs) throws IOException {
int iterations = 1000000; int iterations = 1000000;
byte[] bytes = "åøæÅØÆ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8"); byte[] bytes = " klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
Decoder d; Decoder d;
long start; long start;
@@ -41,12 +41,12 @@ import java.io.InputStream;
*/ */
// TODO: Move to other package or make public // TODO: Move to other package or make public
abstract class AbstractRLEDecoder implements Decoder { abstract class AbstractRLEDecoder implements Decoder {
protected final byte[] mRow; protected final byte[] row;
protected final int mWidth; protected final int width;
protected int mSrcX; protected int srcX;
protected int mSrcY; protected int srcY;
protected int mDstX; protected int dstX;
protected int mDstY; protected int dstY;
/** /**
* Creates an RLEDecoder. As RLE encoded BMP's may contain x and y deltas, * Creates an RLEDecoder. As RLE encoded BMP's may contain x and y deltas,
@@ -56,21 +56,21 @@ abstract class AbstractRLEDecoder implements Decoder {
* @param pHeight heigth of the image * @param pHeight heigth of the image
*/ */
AbstractRLEDecoder(int pWidth, int pHeight) { AbstractRLEDecoder(int pWidth, int pHeight) {
mWidth = pWidth; width = pWidth;
int bytesPerRow = mWidth; int bytesPerRow = width;
int mod = bytesPerRow % 4; int mod = bytesPerRow % 4;
if (mod != 0) { if (mod != 0) {
bytesPerRow += 4 - mod; bytesPerRow += 4 - mod;
} }
mRow = new byte[bytesPerRow]; row = new byte[bytesPerRow];
mSrcX = 0; srcX = 0;
mSrcY = pHeight - 1; srcY = pHeight - 1;
mDstX = mSrcX; dstX = srcX;
mDstY = mSrcY; dstY = srcY;
} }
/** /**
@@ -95,26 +95,26 @@ abstract class AbstractRLEDecoder implements Decoder {
public final int decode(InputStream pStream, byte[] pBuffer) throws IOException { public final int decode(InputStream pStream, byte[] pBuffer) throws IOException {
int decoded = 0; int decoded = 0;
while (decoded < pBuffer.length && mDstY >= 0) { while (decoded < pBuffer.length && dstY >= 0) {
// NOTE: Decode only full rows, don't decode if y delta // NOTE: Decode only full rows, don't decode if y delta
if (mDstX == 0 && mSrcY == mDstY) { if (dstX == 0 && srcY == dstY) {
decodeRow(pStream); decodeRow(pStream);
} }
int length = Math.min(mRow.length - mDstX, pBuffer.length - decoded); int length = Math.min(row.length - dstX, pBuffer.length - decoded);
System.arraycopy(mRow, mDstX, pBuffer, decoded, length); System.arraycopy(row, dstX, pBuffer, decoded, length);
mDstX += length; dstX += length;
decoded += length; decoded += length;
if (mDstX == mRow.length) { if (dstX == row.length) {
mDstX = 0; dstX = 0;
mDstY--; dstY--;
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the // NOTE: If src Y is < dst Y, we have a delta, and have to fill the
// gap with zero-bytes // gap with zero-bytes
if (mDstY > mSrcY) { if (dstY > srcY) {
for (int i = 0; i < mRow.length; i++) { for (int i = 0; i < row.length; i++) {
mRow[i] = 0x00; row[i] = 0x00;
} }
} }
} }
@@ -61,9 +61,9 @@ public final class Base64Decoder implements Decoder {
final static byte[] PEM_CONVERT_ARRAY; final static byte[] PEM_CONVERT_ARRAY;
private byte[] mDecodeBuffer = new byte[4]; private byte[] decodeBuffer = new byte[4];
private ByteArrayOutputStream mWrapped; private ByteArrayOutputStream wrapped;
private Object mWrappedObject; private Object wrappedObject;
static { static {
PEM_CONVERT_ARRAY = new byte[256]; PEM_CONVERT_ARRAY = new byte[256];
@@ -116,8 +116,8 @@ public final class Base64Decoder implements Decoder {
} }
} while (read == 10 || read == 13); } while (read == 10 || read == 13);
mDecodeBuffer[0] = (byte) read; decodeBuffer[0] = (byte) read;
read = readFully(pInput, mDecodeBuffer, 1, pLength - 1); read = readFully(pInput, decodeBuffer, 1, pLength - 1);
if (read == -1) { if (read == -1) {
return false; return false;
@@ -125,24 +125,24 @@ public final class Base64Decoder implements Decoder {
int length = pLength; int length = pLength;
if (length > 3 && mDecodeBuffer[3] == 61) { if (length > 3 && decodeBuffer[3] == 61) {
length = 3; length = 3;
} }
if (length > 2 && mDecodeBuffer[2] == 61) { if (length > 2 && decodeBuffer[2] == 61) {
length = 2; length = 2;
} }
switch (length) { switch (length) {
case 4: case 4:
byte3 = PEM_CONVERT_ARRAY[mDecodeBuffer[3] & 255]; byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255];
// fall through // fall through
case 3: case 3:
byte2 = PEM_CONVERT_ARRAY[mDecodeBuffer[2] & 255]; byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255];
// fall through // fall through
case 2: case 2:
byte1 = PEM_CONVERT_ARRAY[mDecodeBuffer[1] & 255]; byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255];
byte0 = PEM_CONVERT_ARRAY[mDecodeBuffer[0] & 255]; byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255];
// fall through // fall through
default: default:
switch (length) { switch (length) {
@@ -185,15 +185,15 @@ public final class Base64Decoder implements Decoder {
} }
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
if (mWrappedObject != pBuffer) { if (wrappedObject != pBuffer) {
// NOTE: Array not cloned in FastByteArrayOutputStream // NOTE: Array not cloned in FastByteArrayOutputStream
mWrapped = new FastByteArrayOutputStream(pBuffer); wrapped = new FastByteArrayOutputStream(pBuffer);
mWrappedObject = pBuffer; wrappedObject = pBuffer;
} }
mWrapped.reset(); // NOTE: This only resets count to 0 wrapped.reset(); // NOTE: This only resets count to 0
decodeBuffer(pStream, mWrapped, pBuffer.length); decodeBuffer(pStream, wrapped, pBuffer.length);
return mWrapped.size(); return wrapped.size();
} }
} }
@@ -31,7 +31,7 @@ package com.twelvemonkeys.io.enc;
import java.io.IOException; import java.io.IOException;
/** /**
* Thrown by {@code Decoder}s when encoded data can not be decocded. * Thrown by {@code Decoder}s when encoded data can not be decoded.
* <p/> * <p/>
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@@ -47,13 +47,13 @@ import java.io.IOException;
public interface Decoder { public interface Decoder {
/** /**
* Decodes up to {@code pBuffer.length} bytes from the given inputstream, * Decodes up to {@code pBuffer.length} bytes from the given input stream,
* into the given buffer. * into the given buffer.
* *
* @param pStream the inputstream to decode data from * @param pStream the input stream to decode data from
* @param pBuffer buffer to store the read data * @param pBuffer buffer to store the read data
* *
* @return the total number of bytes read into the buffer, or {@code -1} * @return the total number of bytes read into the buffer, or {@code 0}
* if there is no more data because the end of the stream has been reached. * if there is no more data because the end of the stream has been reached.
* *
* @throws DecodeException if encoded data is corrupt * @throws DecodeException if encoded data is corrupt
@@ -44,10 +44,10 @@ import java.io.FilterInputStream;
*/ */
public final class DecoderStream extends FilterInputStream { public final class DecoderStream extends FilterInputStream {
protected int mBufferPos; protected int bufferPos;
protected int mBufferLimit; protected int bufferLimit;
protected final byte[] mBuffer; protected final byte[] buffer;
protected final Decoder mDecoder; protected final Decoder decoder;
/** /**
* Creates a new decoder stream and chains it to the * Creates a new decoder stream and chains it to the
@@ -76,26 +76,26 @@ public final class DecoderStream extends FilterInputStream {
*/ */
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) { public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
super(pStream); super(pStream);
mDecoder = pDecoder; decoder = pDecoder;
mBuffer = new byte[pBufferSize]; buffer = new byte[pBufferSize];
mBufferPos = 0; bufferPos = 0;
mBufferLimit = 0; bufferLimit = 0;
} }
public int available() throws IOException { public int available() throws IOException {
return mBufferLimit - mBufferPos + super.available(); return bufferLimit - bufferPos + super.available();
} }
public int read() throws IOException { public int read() throws IOException {
if (mBufferPos == mBufferLimit) { if (bufferPos == bufferLimit) {
mBufferLimit = fill(); bufferLimit = fill();
} }
if (mBufferLimit < 0) { if (bufferLimit < 0) {
return -1; return -1;
} }
return mBuffer[mBufferPos++] & 0xff; return buffer[bufferPos++] & 0xff;
} }
public int read(final byte pBytes[]) throws IOException { public int read(final byte pBytes[]) throws IOException {
@@ -115,7 +115,7 @@ public final class DecoderStream extends FilterInputStream {
} }
// End of file? // End of file?
if ((mBufferLimit - mBufferPos) < 0) { if ((bufferLimit - bufferPos) < 0) {
return -1; return -1;
} }
@@ -124,26 +124,26 @@ public final class DecoderStream extends FilterInputStream {
int off = pOffset; int off = pOffset;
while (pLength > count) { while (pLength > count) {
int avail = mBufferLimit - mBufferPos; int avail = bufferLimit - bufferPos;
if (avail <= 0) { if (avail <= 0) {
mBufferLimit = fill(); bufferLimit = fill();
if (mBufferLimit < 0) { if (bufferLimit < 0) {
break; break;
} }
} }
// Copy as many bytes as possible // Copy as many bytes as possible
int dstLen = Math.min(pLength - count, avail); int dstLen = Math.min(pLength - count, avail);
System.arraycopy(mBuffer, mBufferPos, pBytes, off, dstLen); System.arraycopy(buffer, bufferPos, pBytes, off, dstLen);
mBufferPos += dstLen; bufferPos += dstLen;
// Update offset (rest) // Update offset (rest)
off += dstLen; off += dstLen;
// Inrease count // Increase count
count += dstLen; count += dstLen;
} }
@@ -152,7 +152,7 @@ public final class DecoderStream extends FilterInputStream {
public long skip(final long pLength) throws IOException { public long skip(final long pLength) throws IOException {
// End of file? // End of file?
if (mBufferLimit - mBufferPos < 0) { if (bufferLimit - bufferPos < 0) {
return 0; return 0;
} }
@@ -160,12 +160,12 @@ public final class DecoderStream extends FilterInputStream {
long total = 0; long total = 0;
while (total < pLength) { while (total < pLength) {
int avail = mBufferLimit - mBufferPos; int avail = bufferLimit - bufferPos;
if (avail == 0) { if (avail == 0) {
mBufferLimit = fill(); bufferLimit = fill();
if (mBufferLimit < 0) { if (bufferLimit < 0) {
break; break;
} }
} }
@@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream {
// an int, so the cast is safe // an int, so the cast is safe
int skipped = (int) Math.min(pLength - total, avail); int skipped = (int) Math.min(pLength - total, avail);
mBufferPos += skipped; // Just skip these bytes bufferPos += skipped; // Just skip these bytes
total += skipped; total += skipped;
} }
@@ -190,19 +190,19 @@ public final class DecoderStream extends FilterInputStream {
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
protected int fill() throws IOException { protected int fill() throws IOException {
int read = mDecoder.decode(in, mBuffer); int read = decoder.decode(in, buffer);
// TODO: Enforce this in test case, leave here to aid debugging // TODO: Enforce this in test case, leave here to aid debugging
if (read > mBuffer.length) { if (read > buffer.length) {
throw new AssertionError( throw new AssertionError(
String.format( String.format(
"Decode beyond buffer (%d): %d (using %s decoder)", "Decode beyond buffer (%d): %d (using %s decoder)",
mBuffer.length, read, mDecoder.getClass().getName() buffer.length, read, decoder.getClass().getName()
) )
); );
} }
mBufferPos = 0; bufferPos = 0;
if (read == 0) { if (read == 0) {
return -1; return -1;
@@ -57,8 +57,7 @@ public interface Encoder {
* *
* @throws java.io.IOException if an I/O error occurs * @throws java.io.IOException if an I/O error occurs
*/ */
void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException;
throws IOException;
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size //TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
// void flush()? // void flush()?
@@ -44,11 +44,11 @@ import java.io.IOException;
*/ */
public final class EncoderStream extends FilterOutputStream { public final class EncoderStream extends FilterOutputStream {
protected final Encoder mEncoder; protected final Encoder encoder;
private final boolean mFlushOnWrite; private final boolean flushOnWrite;
protected int mBufferPos; protected int bufferPos;
protected final byte[] mBuffer; protected final byte[] buffer;
/** /**
* Creates an output stream filter built on top of the specified * Creates an output stream filter built on top of the specified
@@ -73,11 +73,11 @@ public final class EncoderStream extends FilterOutputStream {
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) { public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
super(pStream); super(pStream);
mEncoder = pEncoder; encoder = pEncoder;
mFlushOnWrite = pFlushOnWrite; flushOnWrite = pFlushOnWrite;
mBuffer = new byte[1024]; buffer = new byte[1024];
mBufferPos = 0; bufferPos = 0;
} }
public void close() throws IOException { public void close() throws IOException {
@@ -91,12 +91,12 @@ public final class EncoderStream extends FilterOutputStream {
} }
private void encodeBuffer() throws IOException { private void encodeBuffer() throws IOException {
if (mBufferPos != 0) { if (bufferPos != 0) {
// Make sure all remaining data in buffer is written to the stream // Make sure all remaining data in buffer is written to the stream
mEncoder.encode(out, mBuffer, 0, mBufferPos); encoder.encode(out, buffer, 0, bufferPos);
// Reset buffer // Reset buffer
mBufferPos = 0; bufferPos = 0;
} }
} }
@@ -109,27 +109,25 @@ public final class EncoderStream extends FilterOutputStream {
// that the encoder can't buffer. In that case, the encoder should probably // that the encoder can't buffer. In that case, the encoder should probably
// tell the EncoderStream how large buffer it prefers... // tell the EncoderStream how large buffer it prefers...
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (!mFlushOnWrite && mBufferPos + pLength < mBuffer.length) { if (!flushOnWrite && bufferPos + pLength < buffer.length) {
// Buffer data // Buffer data
System.arraycopy(pBytes, pOffset, mBuffer, mBufferPos, pLength); System.arraycopy(pBytes, pOffset, buffer, bufferPos, pLength);
mBufferPos += pLength; bufferPos += pLength;
} }
else { else {
// Encode data already in the buffer // Encode data already in the buffer
if (mBufferPos != 0) { encodeBuffer();
encodeBuffer();
}
// Encode rest without buffering // Encode rest without buffering
mEncoder.encode(out, pBytes, pOffset, pLength); encoder.encode(out, pBytes, pOffset, pLength);
} }
} }
public void write(final int pByte) throws IOException { public void write(final int pByte) throws IOException {
if (mBufferPos >= mBuffer.length - 1) { if (bufferPos >= buffer.length - 1) {
encodeBuffer(); // Resets mBufferPos to 0 encodeBuffer(); // Resets bufferPos to 0
} }
mBuffer[mBufferPos++] = (byte) pByte; buffer[bufferPos++] = (byte) pByte;
} }
} }
@@ -46,11 +46,11 @@ import java.io.EOFException;
*/ */
public final class PackBits16Decoder implements Decoder { public final class PackBits16Decoder implements Decoder {
// TODO: Refactor this into an option for the PackBitsDecoder? // TODO: Refactor this into an option for the PackBitsDecoder?
private final boolean mDisableNoop; private final boolean disableNoop;
private int mLeftOfRun; private int leftOfRun;
private boolean mSplitRun; private boolean splitRun;
private boolean mEOF; private boolean reachedEOF;
/** /**
* Creates a {@code PackBitsDecoder}. * Creates a {@code PackBitsDecoder}.
@@ -71,7 +71,7 @@ public final class PackBits16Decoder implements Decoder {
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op * @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
*/ */
public PackBits16Decoder(final boolean pDisableNoop) { public PackBits16Decoder(final boolean pDisableNoop) {
mDisableNoop = pDisableNoop; disableNoop = pDisableNoop;
} }
/** /**
@@ -85,7 +85,7 @@ public final class PackBits16Decoder implements Decoder {
* @throws java.io.IOException * @throws java.io.IOException
*/ */
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
if (mEOF) { if (reachedEOF) {
return -1; return -1;
} }
@@ -95,16 +95,16 @@ public final class PackBits16Decoder implements Decoder {
while (read < max) { while (read < max) {
int n; int n;
if (mSplitRun) { if (splitRun) {
// Continue run // Continue run
n = mLeftOfRun; n = leftOfRun;
mSplitRun = false; splitRun = false;
} }
else { else {
// Start new run // Start new run
int b = pStream.read(); int b = pStream.read();
if (b < 0) { if (b < 0) {
mEOF = true; reachedEOF = true;
break; break;
} }
n = (byte) b; n = (byte) b;
@@ -112,13 +112,13 @@ public final class PackBits16Decoder implements Decoder {
// Split run at or before max // Split run at or before max
if (n >= 0 && 2 * (n + 1) + read > max) { if (n >= 0 && 2 * (n + 1) + read > max) {
mLeftOfRun = n; leftOfRun = n;
mSplitRun = true; splitRun = true;
break; break;
} }
else if (n < 0 && 2 * (-n + 1) + read > max) { else if (n < 0 && 2 * (-n + 1) + read > max) {
mLeftOfRun = n; leftOfRun = n;
mSplitRun = true; splitRun = true;
break; break;
} }
@@ -130,7 +130,7 @@ public final class PackBits16Decoder implements Decoder {
read += len; read += len;
} }
// Allow -128 for compatibility, see above // Allow -128 for compatibility, see above
else if (mDisableNoop || n != -128) { else if (disableNoop || n != -128) {
// Replicate the next short -n + 1 times // Replicate the next short -n + 1 times
byte value1 = readByte(pStream); byte value1 = readByte(pStream);
byte value2 = readByte(pStream); byte value2 = readByte(pStream);
@@ -28,9 +28,9 @@
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.io.enc;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.EOFException;
/** /**
* Decoder implementation for Apple PackBits run-length encoding. * Decoder implementation for Apple PackBits run-length encoding.
@@ -63,11 +63,13 @@ import java.io.EOFException;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $
*/ */
public final class PackBitsDecoder implements Decoder { public final class PackBitsDecoder implements Decoder {
private final boolean mDisableNoop; // TODO: Look at ICNSImageReader#unpackbits... What is this weirdness?
private int mLeftOfRun; private final boolean disableNoop;
private boolean mSplitRun;
private boolean mEOF; private int leftOfRun;
private boolean splitRun;
private boolean reachedEOF;
/** Creates a {@code PackBitsDecoder}. */ /** Creates a {@code PackBitsDecoder}. */
public PackBitsDecoder() { public PackBitsDecoder() {
@@ -78,29 +80,26 @@ public final class PackBitsDecoder implements Decoder {
* Creates a {@code PackBitsDecoder}, with optional compatibility mode. * Creates a {@code PackBitsDecoder}, with optional compatibility mode.
* <p/> * <p/>
* As some implementations of PackBits-like encoders treat {@code -128} as length of * As some implementations of PackBits-like encoders treat {@code -128} as length of
* a compressed run, instead of a no-op, it's possible to disable no-ops * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility.
* for compatibility. * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams.
* Should be used with caution, even though, most known encoders never write
* no-ops in the compressed streams.
* *
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op * @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
*/ */
public PackBitsDecoder(final boolean pDisableNoop) { public PackBitsDecoder(final boolean pDisableNoop) {
mDisableNoop = pDisableNoop; disableNoop = pDisableNoop;
} }
/** /**
* Decodes bytes from the given input stream, to the given buffer. * Decodes bytes from the given input stream, to the given buffer.
* *
* @param pStream the stream to decode from * @param pStream the stream to decode from
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled) * @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled) bytes long
* bytes long
* @return The number of bytes decoded * @return The number of bytes decoded
* *
* @throws IOException * @throws java.io.IOException
*/ */
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
if (mEOF) { if (reachedEOF) {
return -1; return -1;
} }
@@ -111,16 +110,16 @@ public final class PackBitsDecoder implements Decoder {
while (read < max) { while (read < max) {
int n; int n;
if (mSplitRun) { if (splitRun) {
// Continue run // Continue run
n = mLeftOfRun; n = leftOfRun;
mSplitRun = false; splitRun = false;
} }
else { else {
// Start new run // Start new run
int b = pStream.read(); int b = pStream.read();
if (b < 0) { if (b < 0) {
mEOF = true; reachedEOF = true;
break; break;
} }
n = (byte) b; n = (byte) b;
@@ -128,13 +127,13 @@ public final class PackBitsDecoder implements Decoder {
// Split run at or before max // Split run at or before max
if (n >= 0 && n + 1 + read > max) { if (n >= 0 && n + 1 + read > max) {
mLeftOfRun = n; leftOfRun = n;
mSplitRun = true; splitRun = true;
break; break;
} }
else if (n < 0 && -n + 1 + read > max) { else if (n < 0 && -n + 1 + read > max) {
mLeftOfRun = n; leftOfRun = n;
mSplitRun = true; splitRun = true;
break; break;
} }
@@ -146,7 +145,7 @@ public final class PackBitsDecoder implements Decoder {
read += n + 1; read += n + 1;
} }
// Allow -128 for compatibility, see above // Allow -128 for compatibility, see above
else if (mDisableNoop || n != -128) { else if (disableNoop || n != -128) {
// Replicate the next byte -n + 1 times // Replicate the next byte -n + 1 times
byte value = readByte(pStream); byte value = readByte(pStream);
@@ -164,7 +163,7 @@ public final class PackBitsDecoder implements Decoder {
return read; return read;
} }
private static byte readByte(final InputStream pStream) throws IOException { static byte readByte(final InputStream pStream) throws IOException {
int read = pStream.read(); int read = pStream.read();
if (read < 0) { if (read < 0) {
@@ -174,21 +173,21 @@ public final class PackBitsDecoder implements Decoder {
return (byte) read; return (byte) read;
} }
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
if (pLength < 0) { if (pLength < 0) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength));
} }
int read = 0; int total = 0;
while (read < pLength) { while (total < pLength) {
int count = pStream.read(pBuffer, pOffset + read, pLength - read); int count = pStream.read(pBuffer, pOffset + total, pLength - total);
if (count < 0) { if (count < 0) {
throw new EOFException("Unexpected end of PackBits stream"); throw new EOFException("Unexpected end of PackBits stream");
} }
read += count; total += count;
} }
} }
} }
@@ -63,7 +63,7 @@ import java.io.IOException;
*/ */
public final class PackBitsEncoder implements Encoder { public final class PackBitsEncoder implements Encoder {
final private byte[] mBuffer = new byte[128]; final private byte[] buffer = new byte[128];
/** /**
* Creates a {@code PackBitsEncoder}. * Creates a {@code PackBitsEncoder}.
@@ -101,17 +101,17 @@ public final class PackBitsEncoder implements Encoder {
run = 0; run = 0;
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1]) while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|| (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) { || (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) {
mBuffer[run++] = pBuffer[offset++]; buffer[run++] = pBuffer[offset++];
} }
// If last byte, include it in literal run, if space // If last byte, include it in literal run, if space
if (offset == max && run > 0 && run < 128) { if (offset == max && run > 0 && run < 128) {
mBuffer[run++] = pBuffer[offset++]; buffer[run++] = pBuffer[offset++];
} }
if (run > 0) { if (run > 0) {
pStream.write(run - 1); pStream.write(run - 1);
pStream.write(mBuffer, 0, run); pStream.write(buffer, 0, run);
} }
// If last byte, and not space, start new literal run // If last byte, and not space, start new literal run
@@ -49,7 +49,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
int deltaX = 0; int deltaX = 0;
int deltaY = 0; int deltaY = 0;
while (mSrcY >= 0) { while (srcY >= 0) {
int byte1 = pInput.read(); int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read()); int byte2 = checkEOF(pInput.read());
@@ -58,20 +58,20 @@ final class RLE4Decoder extends AbstractRLEDecoder {
case 0x00: case 0x00:
// End of line // End of line
// NOTE: Some BMPs have double EOLs.. // NOTE: Some BMPs have double EOLs..
if (mSrcX != 0) { if (srcX != 0) {
mSrcX = mRow.length; srcX = row.length;
} }
break; break;
case 0x01: case 0x01:
// End of bitmap // End of bitmap
mSrcX = mRow.length; srcX = row.length;
mSrcY = 0; srcY = 0;
break; break;
case 0x02: case 0x02:
// Delta // Delta
deltaX = mSrcX + pInput.read(); deltaX = srcX + pInput.read();
deltaY = mSrcY - checkEOF(pInput.read()); deltaY = srcY - checkEOF(pInput.read());
mSrcX = mRow.length; srcX = row.length;
break; break;
default: default:
// Absolute mode // Absolute mode
@@ -82,13 +82,13 @@ final class RLE4Decoder extends AbstractRLEDecoder {
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0; boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
while (byte2 > 1) { while (byte2 > 1) {
int packed = checkEOF(pInput.read()); int packed = checkEOF(pInput.read());
mRow[mSrcX++] = (byte) packed; row[srcX++] = (byte) packed;
byte2 -= 2; byte2 -= 2;
} }
if (byte2 == 1) { if (byte2 == 1) {
// TODO: Half byte alignment? Seems to be ok... // TODO: Half byte alignment? Seems to be ok...
int packed = checkEOF(pInput.read()); int packed = checkEOF(pInput.read());
mRow[mSrcX++] = (byte) (packed & 0xf0); row[srcX++] = (byte) (packed & 0xf0);
} }
if (paddingByte) { if (paddingByte) {
checkEOF(pInput.read()); checkEOF(pInput.read());
@@ -100,24 +100,24 @@ final class RLE4Decoder extends AbstractRLEDecoder {
// Encoded mode // Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says // Replicate the two samples in byte2 as many times as byte1 says
while (byte1 > 1) { while (byte1 > 1) {
mRow[mSrcX++] = (byte) byte2; row[srcX++] = (byte) byte2;
byte1 -= 2; byte1 -= 2;
} }
if (byte1 == 1) { if (byte1 == 1) {
// TODO: Half byte alignment? Seems to be ok... // TODO: Half byte alignment? Seems to be ok...
mRow[mSrcX++] = (byte) (byte2 & 0xf0); row[srcX++] = (byte) (byte2 & 0xf0);
} }
} }
// If we're done with a complete row, copy the data // If we're done with a complete row, copy the data
if (mSrcX == mRow.length) { if (srcX == row.length) {
// Move to new position, either absolute (delta) or next line // Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) { if (deltaX != 0 || deltaY != 0) {
mSrcX = (deltaX + 1) / 2; srcX = (deltaX + 1) / 2;
if (deltaY > mSrcY) { if (deltaY > srcY) {
mSrcY = deltaY; srcY = deltaY;
break; break;
} }
@@ -125,8 +125,8 @@ final class RLE4Decoder extends AbstractRLEDecoder {
deltaY = 0; deltaY = 0;
} }
else { else {
mSrcX = 0; srcX = 0;
mSrcY--; srcY--;
break; break;
} }
} }
@@ -49,7 +49,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
int deltaX = 0; int deltaX = 0;
int deltaY = 0; int deltaY = 0;
while (mSrcY >= 0) { while (srcY >= 0) {
int byte1 = pInput.read(); int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read()); int byte2 = checkEOF(pInput.read());
@@ -58,27 +58,27 @@ final class RLE8Decoder extends AbstractRLEDecoder {
case 0x00: case 0x00:
// End of line // End of line
// NOTE: Some BMPs have double EOLs.. // NOTE: Some BMPs have double EOLs..
if (mSrcX != 0) { if (srcX != 0) {
mSrcX = mRow.length; srcX = row.length;
} }
break; break;
case 0x01: case 0x01:
// End of bitmap // End of bitmap
mSrcX = mRow.length; srcX = row.length;
mSrcY = 0; srcY = 0;
break; break;
case 0x02: case 0x02:
// Delta // Delta
deltaX = mSrcX + pInput.read(); deltaX = srcX + pInput.read();
deltaY = mSrcY - checkEOF(pInput.read()); deltaY = srcY - checkEOF(pInput.read());
mSrcX = mRow.length; srcX = row.length;
break; break;
default: default:
// Absolute mode // Absolute mode
// Copy the next byte2 (3..255) bytes from file to output // Copy the next byte2 (3..255) bytes from file to output
boolean paddingByte = (byte2 % 2) != 0; boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) { while (byte2-- > 0) {
mRow[mSrcX++] = (byte) checkEOF(pInput.read()); row[srcX++] = (byte) checkEOF(pInput.read());
} }
if (paddingByte) { if (paddingByte) {
checkEOF(pInput.read()); checkEOF(pInput.read());
@@ -90,26 +90,26 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// Replicate byte2 as many times as byte1 says // Replicate byte2 as many times as byte1 says
byte value = (byte) byte2; byte value = (byte) byte2;
while (byte1-- > 0) { while (byte1-- > 0) {
mRow[mSrcX++] = value; row[srcX++] = value;
} }
} }
// If we're done with a complete row, copy the data // If we're done with a complete row, copy the data
if (mSrcX == mRow.length) { if (srcX == row.length) {
// Move to new position, either absolute (delta) or next line // Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) { if (deltaX != 0 || deltaY != 0) {
mSrcX = deltaX; srcX = deltaX;
if (deltaY != mSrcY) { if (deltaY != srcY) {
mSrcY = deltaY; srcY = deltaY;
break; break;
} }
deltaX = 0; deltaX = 0;
deltaY = 0; deltaY = 0;
} }
else { else {
mSrcX = 0; srcX = 0;
mSrcY--; srcY--;
break; break;
} }
} }
@@ -54,41 +54,44 @@ public final class CompoundDocument {
// TODO: Write support... // TODO: Write support...
// TODO: Properties: http://support.microsoft.com/kb/186898 // TODO: Properties: http://support.microsoft.com/kb/186898
private static final byte[] MAGIC = new byte[]{ static final byte[] MAGIC = new byte[]{
(byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0, (byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
(byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1, (byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
}; };
public static final int HEADER_SIZE = 512;
private final DataInput mInput;
private UUID mUID;
private int mSectorSize;
private int mShortSectorSize;
private int mDirectorySId;
private int mMinStreamSize;
private int mShortSATSID;
private int mShortSATSize;
// Master Sector Allocation Table
private int[] mMasterSAT;
private int[] mSAT;
private int[] mShortSAT;
private Entry mRootEntry;
private SIdChain mShortStreamSIdChain;
private SIdChain mDirectorySIdChain;
private static final int END_OF_CHAIN_SID = -2;
private static final int FREE_SID = -1; private static final int FREE_SID = -1;
private static final int END_OF_CHAIN_SID = -2;
private static final int SAT_SECTOR_SID = -3; // Sector used by SAT
private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT
public static final int HEADER_SIZE = 512;
/** The epoch offset of CompoundDocument time stamps */ /** The epoch offset of CompoundDocument time stamps */
public final static long EPOCH_OFFSET = -11644477200000L; public final static long EPOCH_OFFSET = -11644477200000L;
private final DataInput input;
private UUID uUID;
private int sectorSize;
private int shortSectorSize;
private int directorySId;
private int minStreamSize;
private int shortSATSId;
private int shortSATSize;
// Master Sector Allocation Table
private int[] masterSAT;
private int[] SAT;
private int[] shortSAT;
private Entry rootEntry;
private SIdChain shortStreamSIdChain;
private SIdChain directorySIdChain;
/** /**
* Creates a (for now) read only {@code CompoundDocument}. * Creates a (for now) read only {@code CompoundDocument}.
* *
@@ -97,7 +100,7 @@ public final class CompoundDocument {
* @throws IOException if an I/O exception occurs while reading the header * @throws IOException if an I/O exception occurs while reading the header
*/ */
public CompoundDocument(final File pFile) throws IOException { public CompoundDocument(final File pFile) throws IOException {
mInput = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r"); input = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
// TODO: Might be better to read header on first read operation?! // TODO: Might be better to read header on first read operation?!
// OTOH: It's also good to be fail-fast, so at least we should make // OTOH: It's also good to be fail-fast, so at least we should make
@@ -118,7 +121,7 @@ public final class CompoundDocument {
// For testing only, consider exposing later // For testing only, consider exposing later
CompoundDocument(final SeekableInputStream pInput) throws IOException { CompoundDocument(final SeekableInputStream pInput) throws IOException {
mInput = new SeekableLittleEndianDataInputStream(pInput); input = new SeekableLittleEndianDataInputStream(pInput);
// TODO: Might be better to read header on first read operation?! // TODO: Might be better to read header on first read operation?!
// OTOH: It's also good to be fail-fast, so at least we should make // OTOH: It's also good to be fail-fast, so at least we should make
@@ -134,7 +137,7 @@ public final class CompoundDocument {
* @throws IOException if an I/O exception occurs while reading the header * @throws IOException if an I/O exception occurs while reading the header
*/ */
public CompoundDocument(final ImageInputStream pInput) throws IOException { public CompoundDocument(final ImageInputStream pInput) throws IOException {
mInput = pInput; input = pInput;
// TODO: Might be better to read header on first read operation?! // TODO: Might be better to read header on first read operation?!
// OTOH: It's also good to be fail-fast, so at least we should make // OTOH: It's also good to be fail-fast, so at least we should make
@@ -210,74 +213,81 @@ public final class CompoundDocument {
} }
private void readHeader() throws IOException { private void readHeader() throws IOException {
if (mMasterSAT != null) { if (masterSAT != null) {
return; return;
} }
if (!canRead(mInput, false)) { if (!canRead(input, false)) {
throw new CorruptDocumentException("Not an OLE 2 Compound Document"); throw new CorruptDocumentException("Not an OLE 2 Compound Document");
} }
// UID (seems to be all 0s) // UID (seems to be all 0s)
mUID = new UUID(mInput.readLong(), mInput.readLong()); uUID = new UUID(input.readLong(), input.readLong());
// System.out.println("uUID: " + uUID);
/*int version = */mInput.readUnsignedShort(); // int version =
//System.out.println("version: " + version); input.readUnsignedShort();
/*int revision = */mInput.readUnsignedShort(); // System.out.println("version: " + version);
//System.out.println("revision: " + revision); // int revision =
input.readUnsignedShort();
// System.out.println("revision: " + revision);
int byteOrder = mInput.readUnsignedShort(); int byteOrder = input.readUnsignedShort();
if (byteOrder != 0xfffe) { // System.out.printf("byteOrder: 0x%04x\n", byteOrder);
// Reversed, as I'm allready reading little-endian if (byteOrder == 0xffff) {
throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents"); throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
} }
else if (byteOrder != 0xfffe) {
// Reversed, as I'm already reading little-endian
throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder));
}
mSectorSize = 1 << mInput.readUnsignedShort(); sectorSize = 1 << input.readUnsignedShort();
//System.out.println("sectorSize: " + mSectorSize + " bytes"); // System.out.println("sectorSize: " + sectorSize + " bytes");
mShortSectorSize = 1 << mInput.readUnsignedShort(); shortSectorSize = 1 << input.readUnsignedShort();
//System.out.println("shortSectorSize: " + mShortSectorSize + " bytes"); // System.out.println("shortSectorSize: " + shortSectorSize + " bytes");
// Reserved // Reserved
if (mInput.skipBytes(10) != 10) { if (skipBytesFully(10) != 10) {
throw new CorruptDocumentException(); throw new CorruptDocumentException();
} }
int SATSize = mInput.readInt(); int SATSize = input.readInt();
//System.out.println("normalSATSize: " + mSATSize); // System.out.println("normalSATSize: " + SATSize);
mDirectorySId = mInput.readInt(); directorySId = input.readInt();
//System.out.println("directorySId: " + mDirectorySId); // System.out.println("directorySId: " + directorySId);
// Reserved // Reserved
if (mInput.skipBytes(4) != 4) { if (skipBytesFully(4) != 4) {
throw new CorruptDocumentException(); throw new CorruptDocumentException();
} }
mMinStreamSize = mInput.readInt(); minStreamSize = input.readInt();
//System.out.println("minStreamSize: " + mMinStreamSize + " bytes"); // System.out.println("minStreamSize: " + minStreamSize + " bytes");
mShortSATSID = mInput.readInt(); shortSATSId = input.readInt();
//System.out.println("shortSATSID: " + mShortSATSID); // System.out.println("shortSATSId: " + shortSATSId);
mShortSATSize = mInput.readInt(); shortSATSize = input.readInt();
//System.out.println("shortSATSize: " + mShortSATSize); // System.out.println("shortSATSize: " + shortSATSize);
int masterSATSId = mInput.readInt(); int masterSATSId = input.readInt();
//System.out.println("masterSATSId: " + mMasterSATSID); // System.out.println("masterSATSId: " + masterSATSId);
int masterSATSize = mInput.readInt(); int masterSATSize = input.readInt();
//System.out.println("masterSATSize: " + mMasterSATSize); // System.out.println("masterSATSize: " + masterSATSize);
// Read masterSAT: 436 bytes, containing up to 109 SIDs // Read masterSAT: 436 bytes, containing up to 109 SIDs
//System.out.println("MSAT:"); //System.out.println("MSAT:");
mMasterSAT = new int[SATSize]; masterSAT = new int[SATSize];
final int headerSIds = Math.min(SATSize, 109); final int headerSIds = Math.min(SATSize, 109);
for (int i = 0; i < headerSIds; i++) { for (int i = 0; i < headerSIds; i++) {
mMasterSAT[i] = mInput.readInt(); masterSAT[i] = input.readInt();
//System.out.println("\tSID(" + i + "): " + mMasterSAT[i]); //System.out.println("\tSID(" + i + "): " + masterSAT[i]);
} }
if (masterSATSId == END_OF_CHAIN_SID) { if (masterSATSId == END_OF_CHAIN_SID) {
// End of chain // End of chain
int freeSIdLength = 436 - (SATSize * 4); int freeSIdLength = 436 - (SATSize * 4);
if (mInput.skipBytes(freeSIdLength) != freeSIdLength) { if (skipBytesFully(freeSIdLength) != freeSIdLength) {
throw new CorruptDocumentException(); throw new CorruptDocumentException();
} }
} }
@@ -288,17 +298,17 @@ public final class CompoundDocument {
int index = headerSIds; int index = headerSIds;
for (int i = 0; i < masterSATSize; i++) { for (int i = 0; i < masterSATSize; i++) {
for (int j = 0; j < 127; j++) { for (int j = 0; j < 127; j++) {
int sid = mInput.readInt(); int sid = input.readInt();
switch (sid) { switch (sid) {
case FREE_SID:// Free case FREE_SID:// Free
break; break;
default: default:
mMasterSAT[index++] = sid; masterSAT[index++] = sid;
break; break;
} }
} }
int next = mInput.readInt(); int next = input.readInt();
if (next == END_OF_CHAIN_SID) {// End of chain if (next == END_OF_CHAIN_SID) {// End of chain
break; break;
} }
@@ -308,38 +318,53 @@ public final class CompoundDocument {
} }
} }
private int skipBytesFully(final int n) throws IOException {
int toSkip = n;
while (toSkip > 0) {
int skipped = input.skipBytes(n);
if (skipped <= 0) {
break;
}
toSkip -= skipped;
}
return n - toSkip;
}
private void readSAT() throws IOException { private void readSAT() throws IOException {
if (mSAT != null) { if (SAT != null) {
return; return;
} }
final int intsPerSector = mSectorSize / 4; final int intsPerSector = sectorSize / 4;
// Read the Sector Allocation Table // Read the Sector Allocation Table
mSAT = new int[mMasterSAT.length * intsPerSector]; SAT = new int[masterSAT.length * intsPerSector];
for (int i = 0; i < mMasterSAT.length; i++) { for (int i = 0; i < masterSAT.length; i++) {
seekToSId(mMasterSAT[i], FREE_SID); seekToSId(masterSAT[i], FREE_SID);
for (int j = 0; j < intsPerSector; j++) { for (int j = 0; j < intsPerSector; j++) {
int nextSID = mInput.readInt(); int nextSID = input.readInt();
int index = (j + (i * intsPerSector)); int index = (j + (i * intsPerSector));
mSAT[index] = nextSID; SAT[index] = nextSID;
} }
} }
// Read the short-stream Sector Allocation Table // Read the short-stream Sector Allocation Table
SIdChain chain = getSIdChain(mShortSATSID, FREE_SID); SIdChain chain = getSIdChain(shortSATSId, FREE_SID);
mShortSAT = new int[mShortSATSize * intsPerSector]; shortSAT = new int[shortSATSize * intsPerSector];
for (int i = 0; i < mShortSATSize; i++) { for (int i = 0; i < shortSATSize; i++) {
seekToSId(chain.get(i), FREE_SID); seekToSId(chain.get(i), FREE_SID);
for (int j = 0; j < intsPerSector; j++) { for (int j = 0; j < intsPerSector; j++) {
int nextSID = mInput.readInt(); int nextSID = input.readInt();
int index = (j + (i * intsPerSector)); int index = (j + (i * intsPerSector));
mShortSAT[index] = nextSID; shortSAT[index] = nextSID;
} }
} }
} }
@@ -355,7 +380,7 @@ public final class CompoundDocument {
private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException { private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException {
SIdChain chain = new SIdChain(); SIdChain chain = new SIdChain();
int[] sat = isShortStream(pStreamSize) ? mShortSAT : mSAT; int[] sat = isShortStream(pStreamSize) ? shortSAT : SAT;
int sid = pSId; int sid = pSId;
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) { while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
@@ -367,7 +392,7 @@ public final class CompoundDocument {
} }
private boolean isShortStream(final long pStreamSize) { private boolean isShortStream(final long pStreamSize) {
return pStreamSize != FREE_SID && pStreamSize < mMinStreamSize; return pStreamSize != FREE_SID && pStreamSize < minStreamSize;
} }
/** /**
@@ -381,70 +406,76 @@ public final class CompoundDocument {
long pos; long pos;
if (isShortStream(pStreamSize)) { if (isShortStream(pStreamSize)) {
// The short-stream is not continouos... // The short stream is not continuous...
Entry root = getRootEntry(); Entry root = getRootEntry();
if (mShortStreamSIdChain == null) { if (shortStreamSIdChain == null) {
mShortStreamSIdChain = getSIdChain(root.startSId, root.streamSize); shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
} }
int shortPerStd = mSectorSize / mShortSectorSize; // System.err.println("pSId: " + pSId);
int offset = pSId / shortPerStd; int shortPerSId = sectorSize / shortSectorSize;
int shortOffset = pSId - (offset * shortPerStd); // System.err.println("shortPerSId: " + shortPerSId);
int offset = pSId / shortPerSId;
// System.err.println("offset: " + offset);
int shortOffset = pSId - (offset * shortPerSId);
// System.err.println("shortOffset: " + shortOffset);
// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset));
pos = HEADER_SIZE pos = HEADER_SIZE
+ (mShortStreamSIdChain.get(offset) * (long) mSectorSize) + (shortStreamSIdChain.get(offset) * (long) sectorSize)
+ (shortOffset * (long) mShortSectorSize); + (shortOffset * (long) shortSectorSize);
// System.err.println("pos: " + pos);
} }
else { else {
pos = HEADER_SIZE + pSId * (long) mSectorSize; pos = HEADER_SIZE + pSId * (long) sectorSize;
} }
if (mInput instanceof LittleEndianRandomAccessFile) { if (input instanceof LittleEndianRandomAccessFile) {
((LittleEndianRandomAccessFile) mInput).seek(pos); ((LittleEndianRandomAccessFile) input).seek(pos);
} }
else if (mInput instanceof ImageInputStream) { else if (input instanceof ImageInputStream) {
((ImageInputStream) mInput).seek(pos); ((ImageInputStream) input).seek(pos);
} }
else { else {
((SeekableLittleEndianDataInputStream) mInput).seek(pos); ((SeekableLittleEndianDataInputStream) input).seek(pos);
} }
} }
private void seekToDId(final int pDId) throws IOException { private void seekToDId(final int pDId) throws IOException {
if (mDirectorySIdChain == null) { if (directorySIdChain == null) {
mDirectorySIdChain = getSIdChain(mDirectorySId, FREE_SID); directorySIdChain = getSIdChain(directorySId, FREE_SID);
} }
int dIdsPerSId = mSectorSize / Entry.LENGTH; int dIdsPerSId = sectorSize / Entry.LENGTH;
int sIdOffset = pDId / dIdsPerSId; int sIdOffset = pDId / dIdsPerSId;
int dIdOffset = pDId - (sIdOffset * dIdsPerSId); int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
int sId = mDirectorySIdChain.get(sIdOffset); int sId = directorySIdChain.get(sIdOffset);
seekToSId(sId, FREE_SID); seekToSId(sId, FREE_SID);
if (mInput instanceof LittleEndianRandomAccessFile) { if (input instanceof LittleEndianRandomAccessFile) {
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) mInput; LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input;
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH); input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
} }
else if (mInput instanceof ImageInputStream) { else if (input instanceof ImageInputStream) {
ImageInputStream input = (ImageInputStream) mInput; ImageInputStream input = (ImageInputStream) this.input;
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH); input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
} }
else { else {
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) mInput; SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input;
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH); input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
} }
} }
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException { SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
SIdChain chain = getSIdChain(pStreamId, pStreamSize); SIdChain chain = getSIdChain(pStreamId, pStreamSize);
// TODO: Detach? Means, we have to copy to a byte buffer, or keep track of // TODO: Detach? Means, we have to copy to a byte buffer, or keep track of
// positions, and seek back and forth (would be cool, but difficult).. // positions, and seek back and forth (would be cool, but difficult)..
int sectorSize = pStreamSize < mMinStreamSize ? mShortSectorSize : mSectorSize; int sectorSize = pStreamSize < minStreamSize ? shortSectorSize : this.sectorSize;
return new Stream(chain, pStreamSize, sectorSize, this); return new MemoryCacheSeekableStream(new Stream(chain, pStreamSize, sectorSize, this));
} }
private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException { private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException {
@@ -453,7 +484,7 @@ public final class CompoundDocument {
byte[] bytes = new byte[Entry.LENGTH]; byte[] bytes = new byte[Entry.LENGTH];
seekToDId(pDirectoryId); seekToDId(pDirectoryId);
mInput.readFully(bytes); input.readFully(bytes);
return new ByteArrayInputStream(bytes); return new ByteArrayInputStream(bytes);
} }
@@ -462,8 +493,8 @@ public final class CompoundDocument {
Entry entry = Entry.readEntry(new LittleEndianDataInputStream( Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
getDirectoryStreamForDId(pDirectoryId) getDirectoryStreamForDId(pDirectoryId)
)); ));
entry.mParent = pParent; entry.parent = pParent;
entry.mDocument = this; entry.document = this;
return entry; return entry;
} }
@@ -527,21 +558,23 @@ public final class CompoundDocument {
} }
public Entry getRootEntry() throws IOException { public Entry getRootEntry() throws IOException {
if (mRootEntry == null) { if (rootEntry == null) {
readSAT(); readSAT();
mRootEntry = getEntry(0, null); rootEntry = getEntry(0, null);
if (mRootEntry.type != Entry.ROOT_STORAGE) { if (rootEntry.type != Entry.ROOT_STORAGE) {
throw new CorruptDocumentException("Invalid root storage type: " + mRootEntry.type); throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type);
} }
} }
return mRootEntry;
return rootEntry;
} }
// This is useless, as most documents on file have all-zero UUIDs...
// @Override // @Override
// public int hashCode() { // public int hashCode() {
// return mUID.hashCode(); // return uUID.hashCode();
// } // }
// //
// @Override // @Override
@@ -555,7 +588,7 @@ public final class CompoundDocument {
// } // }
// //
// if (pOther.getClass() == getClass()) { // if (pOther.getClass() == getClass()) {
// return mUID.equals(((CompoundDocument) pOther).mUID); // return uUID.equals(((CompoundDocument) pOther).uUID);
// } // }
// //
// return false; // return false;
@@ -565,7 +598,7 @@ public final class CompoundDocument {
public String toString() { public String toString() {
return String.format( return String.format(
"%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]", "%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]",
getClass().getSimpleName(), mUID, mSectorSize, mShortSectorSize, mDirectorySId, mMasterSAT.length getClass().getSimpleName(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.length
); );
} }
@@ -601,29 +634,29 @@ public final class CompoundDocument {
return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET; return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET;
} }
// TODO: Enforce stream length! static class Stream extends InputStream {
static class Stream extends SeekableInputStream { private final SIdChain chain;
private SIdChain mChain; private final CompoundDocument document;
int mNextSectorPos; private final long length;
byte[] mBuffer;
int mBufferPos;
private final CompoundDocument mDocument; private long streamPos;
private final long mLength; private int nextSectorPos;
private byte[] buffer;
private int bufferPos;
public Stream(final SIdChain pChain, final long pLength, final int pSectorSize, final CompoundDocument pDocument) { public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) {
mChain = pChain; this.chain = chain;
mLength = pLength; this.length = streamSize;
mBuffer = new byte[pSectorSize]; this.buffer = new byte[sectorSize];
mBufferPos = mBuffer.length; this.bufferPos = buffer.length;
mDocument = pDocument; this.document = document;
} }
@Override @Override
public int available() throws IOException { public int available() throws IOException {
return (int) Math.min(mBuffer.length - mBufferPos, mLength - getStreamPosition()); return (int) Math.min(buffer.length - bufferPos, length - streamPos);
} }
public int read() throws IOException { public int read() throws IOException {
@@ -633,20 +666,23 @@ public final class CompoundDocument {
} }
} }
return mBuffer[mBufferPos++] & 0xff; streamPos++;
return buffer[bufferPos++] & 0xff;
} }
private boolean fillBuffer() throws IOException { private boolean fillBuffer() throws IOException {
if (mNextSectorPos < mChain.length()) { if (streamPos < length && nextSectorPos < chain.length()) {
// TODO: Sync on mDocument.mInput here, and we are completely detached... :-) // TODO: Sync on document.input here, and we are completely detached... :-)
// TODO: We also need to sync other places... // TODO: Update: We also need to sync other places... :-P
synchronized (mDocument) { synchronized (document) {
mDocument.seekToSId(mChain.get(mNextSectorPos), mLength); document.seekToSId(chain.get(nextSectorPos), length);
mDocument.mInput.readFully(mBuffer); document.input.readFully(buffer);
} }
mNextSectorPos++; nextSectorPos++;
mBufferPos = 0; bufferPos = 0;
return true; return true;
} }
@@ -663,99 +699,66 @@ public final class CompoundDocument {
int toRead = Math.min(len, available()); int toRead = Math.min(len, available());
System.arraycopy(mBuffer, mBufferPos, b, off, toRead); System.arraycopy(buffer, bufferPos, b, off, toRead);
mBufferPos += toRead; bufferPos += toRead;
streamPos += toRead;
return toRead; return toRead;
} }
public boolean isCached() { @Override
return true; public void close() throws IOException {
} buffer = null;
public boolean isCachedMemory() {
return false;
}
public boolean isCachedFile() {
return true;
}
protected void closeImpl() throws IOException {
mBuffer = null;
mChain = null;
}
protected void seekImpl(final long pPosition) throws IOException {
long pos = getStreamPosition();
if (pos - mBufferPos >= pPosition && pPosition <= pos + available()) {
// Skip inside buffer only
mBufferPos += (pPosition - pos);
}
else {
// Skip outside buffer
mNextSectorPos = (int) (pPosition / mBuffer.length);
if (!fillBuffer()) {
throw new EOFException();
}
mBufferPos = (int) (pPosition % mBuffer.length);
}
}
protected void flushBeforeImpl(long pPosition) throws IOException {
// No need to do anything here
} }
} }
// TODO: Add test case for this class!!!
static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable { static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
private final SeekableInputStream mSeekable; private final SeekableInputStream seekable;
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) { public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
super(pInput); super(pInput);
mSeekable = pInput; seekable = pInput;
} }
public void seek(final long pPosition) throws IOException { public void seek(final long pPosition) throws IOException {
mSeekable.seek(pPosition); seekable.seek(pPosition);
} }
public boolean isCachedFile() { public boolean isCachedFile() {
return mSeekable.isCachedFile(); return seekable.isCachedFile();
} }
public boolean isCachedMemory() { public boolean isCachedMemory() {
return mSeekable.isCachedMemory(); return seekable.isCachedMemory();
} }
public boolean isCached() { public boolean isCached() {
return mSeekable.isCached(); return seekable.isCached();
} }
public long getStreamPosition() throws IOException { public long getStreamPosition() throws IOException {
return mSeekable.getStreamPosition(); return seekable.getStreamPosition();
} }
public long getFlushedPosition() throws IOException { public long getFlushedPosition() throws IOException {
return mSeekable.getFlushedPosition(); return seekable.getFlushedPosition();
} }
public void flushBefore(final long pPosition) throws IOException { public void flushBefore(final long pPosition) throws IOException {
mSeekable.flushBefore(pPosition); seekable.flushBefore(pPosition);
} }
public void flush() throws IOException { public void flush() throws IOException {
mSeekable.flush(); seekable.flush();
} }
@Override @Override
public void reset() throws IOException { public void reset() throws IOException {
mSeekable.reset(); seekable.reset();
} }
public void mark() { public void mark() {
mSeekable.mark(); seekable.mark();
} }
} }
} }
@@ -32,6 +32,7 @@ import com.twelvemonkeys.io.SeekableInputStream;
import java.io.DataInput; import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections; import java.util.Collections;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
@@ -61,9 +62,9 @@ public final class Entry implements Comparable<Entry> {
int startSId; int startSId;
int streamSize; int streamSize;
CompoundDocument mDocument; CompoundDocument document;
Entry mParent; Entry parent;
SortedSet<Entry> mChildren; SortedSet<Entry> children;
public final static int LENGTH = 128; public final static int LENGTH = 128;
@@ -99,28 +100,26 @@ public final class Entry implements Comparable<Entry> {
* @throws IOException if an i/o exception occurs during reading * @throws IOException if an i/o exception occurs during reading
*/ */
private void read(final DataInput pInput) throws IOException { private void read(final DataInput pInput) throws IOException {
char[] chars = new char[32]; byte[] bytes = new byte[64];
for (int i = 0; i < chars.length; i++) { pInput.readFully(bytes);
chars[i] = pInput.readChar();
}
// NOTE: Length is in bytes, including the null-terminator... // NOTE: Length is in bytes, including the null-terminator...
int nameLength = pInput.readShort(); int nameLength = pInput.readShort();
name = new String(chars, 0, (nameLength - 1) / 2); name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE"));
//System.out.println("name: " + name); // System.out.println("name: " + name);
type = pInput.readByte(); type = pInput.readByte();
//System.out.println("type: " + type); // System.out.println("type: " + type);
nodeColor = pInput.readByte(); nodeColor = pInput.readByte();
//System.out.println("nodeColor: " + nodeColor); // System.out.println("nodeColor: " + nodeColor);
prevDId = pInput.readInt(); prevDId = pInput.readInt();
//System.out.println("prevDID: " + prevDID); // System.out.println("prevDId: " + prevDId);
nextDId = pInput.readInt(); nextDId = pInput.readInt();
//System.out.println("nextDID: " + nextDID); // System.out.println("nextDId: " + nextDId);
rootNodeDId = pInput.readInt(); rootNodeDId = pInput.readInt();
//System.out.println("rootNodeDID: " + rootNodeDID); // System.out.println("rootNodeDId: " + rootNodeDId);
// UID (16) + user flags (4), ignored // UID (16) + user flags (4), ignored
if (pInput.skipBytes(20) != 20) { if (pInput.skipBytes(20) != 20) {
@@ -131,9 +130,9 @@ public final class Entry implements Comparable<Entry> {
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong()); modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
startSId = pInput.readInt(); startSId = pInput.readInt();
//System.out.println("startSID: " + startSID); // System.out.println("startSId: " + startSId);
streamSize = pInput.readInt(); streamSize = pInput.readInt();
//System.out.println("streamSize: " + streamSize); // System.out.println("streamSize: " + streamSize);
// Reserved // Reserved
pInput.readInt(); pInput.readInt();
@@ -186,11 +185,11 @@ public final class Entry implements Comparable<Entry> {
* @see #length() * @see #length()
*/ */
public SeekableInputStream getInputStream() throws IOException { public SeekableInputStream getInputStream() throws IOException {
if (isDirectory()) { if (!isFile()) {
return null; return null;
} }
return mDocument.getInputStreamForSId(startSId, streamSize); return document.getInputStreamForSId(startSId, streamSize);
} }
/** /**
@@ -201,9 +200,10 @@ public final class Entry implements Comparable<Entry> {
* @see #getInputStream() * @see #getInputStream()
*/ */
public long length() { public long length() {
if (isDirectory()) { if (!isFile()) {
return 0L; return 0L;
} }
return streamSize; return streamSize;
} }
@@ -248,7 +248,7 @@ public final class Entry implements Comparable<Entry> {
* the root {@code Entry} * the root {@code Entry}
*/ */
public Entry getParentEntry() { public Entry getParentEntry() {
return mParent; return parent;
} }
/** /**
@@ -266,7 +266,7 @@ public final class Entry implements Comparable<Entry> {
Entry dummy = new Entry(); Entry dummy = new Entry();
dummy.name = pName; dummy.name = pName;
dummy.mParent = this; dummy.parent = this;
SortedSet child = getChildEntries().tailSet(dummy); SortedSet child = getChildEntries().tailSet(dummy);
return (Entry) child.first(); return (Entry) child.first();
@@ -279,26 +279,26 @@ public final class Entry implements Comparable<Entry> {
* @throws java.io.IOException if an I/O exception occurs * @throws java.io.IOException if an I/O exception occurs
*/ */
public SortedSet<Entry> getChildEntries() throws IOException { public SortedSet<Entry> getChildEntries() throws IOException {
if (mChildren == null) { if (children == null) {
if (isFile() || rootNodeDId == -1) { if (isFile() || rootNodeDId == -1) {
mChildren = NO_CHILDREN; children = NO_CHILDREN;
} }
else { else {
// Start at root node in R/B tree, and raed to the left and right, // Start at root node in R/B tree, and read to the left and right,
// re-build tree, according to the docs // re-build tree, according to the docs
mChildren = mDocument.getEntries(rootNodeDId, this); children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this));
} }
} }
return mChildren; return children;
} }
@Override @Override
public String toString() { public String toString() {
return "\"" + name + "\"" return "\"" + name + "\""
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root")) + " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
+ (mParent != null ? ", parent: \"" + mParent.getName() + "\"" : "") + (parent != null ? ", parent: \"" + parent.getName() + "\"" : "")
+ (isFile() ? "" : ", children: " + (mChildren != null ? String.valueOf(mChildren.size()) : "(unknown)")) + (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.size()) : "(unknown)"))
+ ", SId=" + startSId + ", length=" + streamSize + ")"; + ", SId=" + startSId + ", length=" + streamSize + ")";
} }
@@ -312,8 +312,8 @@ public final class Entry implements Comparable<Entry> {
} }
Entry other = (Entry) pOther; Entry other = (Entry) pOther;
return name.equals(other.name) && (mParent == other.mParent return name.equals(other.name) && (parent == other.parent
|| (mParent != null && mParent.equals(other.mParent))); || (parent != null && parent.equals(other.parent)));
} }
@Override @Override
@@ -37,7 +37,7 @@ import java.util.NoSuchElementException;
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java#1 $
*/ */
class SIdChain { final class SIdChain {
int[] chain; int[] chain;
int size = 0; int size = 0;
int next = 0; int next = 0;
@@ -37,11 +37,10 @@ import java.net.*;
* @see SimpleAuthenticator * @see SimpleAuthenticator
* @see java.net.Authenticator * @see java.net.Authenticator
* *
* @author Harald Kuhr (haraldk@iconmedialab.no), * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version 1.0 * @version 1.0
*/ */
public interface AuthenticatorFilter { public interface AuthenticatorFilter {
public boolean accept(InetAddress pAddress, int pPort, String pProtocol, public boolean accept(InetAddress pAddress, int pPort, String pProtocol, String pPrompt, String pScheme);
String pPrompt, String pScheme);
} }
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.util; package com.twelvemonkeys.net;
import com.twelvemonkeys.io.*; import com.twelvemonkeys.io.*;
import com.twelvemonkeys.io.enc.Base64Decoder; import com.twelvemonkeys.io.enc.Base64Decoder;
@@ -41,8 +41,9 @@ import java.io.*;
* @author unascribed * @author unascribed
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BASE64.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BASE64.java#1 $
* @deprecated Use {@link com.twelvemonkeys.io.enc.Base64Encoder}/{@link Base64Decoder} instead
*/ */
public class BASE64 { class BASE64 {
/** /**
* This array maps the characters to their 6 bit values * This array maps the characters to their 6 bit values
@@ -29,7 +29,6 @@
package com.twelvemonkeys.net; package com.twelvemonkeys.net;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.BASE64;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@@ -65,18 +64,18 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
private final static String HTTP_HEADER_END = "\r\n\r\n"; private final static String HTTP_HEADER_END = "\r\n\r\n";
private static final String HEADER_WWW_AUTH = "WWW-Authenticate"; private static final String HEADER_WWW_AUTH = "WWW-Authenticate";
private final static int BUF_SIZE = 8192; private final static int BUF_SIZE = 8192;
private int mMaxRedirects = (System.getProperty("http.maxRedirects") != null) private int maxRedirects = (System.getProperty("http.maxRedirects") != null)
? Integer.parseInt(System.getProperty("http.maxRedirects")) ? Integer.parseInt(System.getProperty("http.maxRedirects"))
: 20; : 20;
protected int mTimeout = -1; protected int timeout = -1;
protected int mConnectTimeout = -1; protected int connectTimeout = -1;
private Socket mSocket = null; private Socket socket = null;
protected InputStream mErrorStream = null; protected InputStream errorStream = null;
protected InputStream mInputStream = null; protected InputStream inputStream = null;
protected OutputStream mOutputStream = null; protected OutputStream outputStream = null;
private String[] mResponseHeaders = null; private String[] responseHeaders = null;
protected Properties mResponseHeaderFields = null; protected Properties responseHeaderFields = null;
protected Properties mRequestProperties = new Properties(); protected Properties requestProperties = new Properties();
/** /**
* Creates a HttpURLConnection. * Creates a HttpURLConnection.
@@ -114,7 +113,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
protected HttpURLConnection(URL pURL, int pTimeout, int pConnectTimeout) { protected HttpURLConnection(URL pURL, int pTimeout, int pConnectTimeout) {
super(pURL); super(pURL);
setTimeout(pTimeout); setTimeout(pTimeout);
mConnectTimeout = pConnectTimeout; connectTimeout = pConnectTimeout;
} }
/** /**
@@ -135,13 +134,13 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
if (connected) { if (connected) {
throw new IllegalAccessError("Already connected"); throw new IllegalAccessError("Already connected");
} }
String oldValue = mRequestProperties.getProperty(pKey); String oldValue = requestProperties.getProperty(pKey);
if (oldValue == null) { if (oldValue == null) {
mRequestProperties.setProperty(pKey, pValue); requestProperties.setProperty(pKey, pValue);
} }
else { else {
mRequestProperties.setProperty(pKey, oldValue + ", " + pValue); requestProperties.setProperty(pKey, oldValue + ", " + pValue);
} }
} }
@@ -158,7 +157,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
if (connected) { if (connected) {
throw new IllegalAccessError("Already connected"); throw new IllegalAccessError("Already connected");
} }
return mRequestProperties.getProperty(pKey); return requestProperties.getProperty(pKey);
} }
/** /**
@@ -212,7 +211,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* if there is no such field in the header. * if there is no such field in the header.
*/ */
public String getHeaderField(String pName) { public String getHeaderField(String pName) {
return mResponseHeaderFields.getProperty(StringUtil.toLowerCase(pName)); return responseHeaderFields.getProperty(StringUtil.toLowerCase(pName));
} }
/** /**
@@ -230,10 +229,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
*/ */
public String getHeaderField(int pIndex) { public String getHeaderField(int pIndex) {
// TODO: getInputStream() first, to make sure we have header fields // TODO: getInputStream() first, to make sure we have header fields
if (pIndex >= mResponseHeaders.length) { if (pIndex >= responseHeaders.length) {
return null; return null;
} }
String field = mResponseHeaders[pIndex]; String field = responseHeaders[pIndex];
// pIndex == 0, means the response code etc (i.e. "HTTP/1.1 200 OK"). // pIndex == 0, means the response code etc (i.e. "HTTP/1.1 200 OK").
if ((pIndex == 0) || (field == null)) { if ((pIndex == 0) || (field == null)) {
@@ -256,10 +255,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
*/ */
public String getHeaderFieldKey(int pIndex) { public String getHeaderFieldKey(int pIndex) {
// TODO: getInputStream() first, to make sure we have header fields // TODO: getInputStream() first, to make sure we have header fields
if (pIndex >= mResponseHeaders.length) { if (pIndex >= responseHeaders.length) {
return null; return null;
} }
String field = mResponseHeaders[pIndex]; String field = responseHeaders[pIndex];
if (StringUtil.isEmpty(field)) { if (StringUtil.isEmpty(field)) {
return null; return null;
@@ -283,10 +282,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
if (pTimeout < 0) { // Must be positive if (pTimeout < 0) { // Must be positive
throw new IllegalArgumentException("Timeout must be positive."); throw new IllegalArgumentException("Timeout must be positive.");
} }
mTimeout = pTimeout; timeout = pTimeout;
if (mSocket != null) { if (socket != null) {
try { try {
mSocket.setSoTimeout(pTimeout); socket.setSoTimeout(pTimeout);
} }
catch (SocketException se) { catch (SocketException se) {
// Not much to do about that... // Not much to do about that...
@@ -305,12 +304,12 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
public int getTimeout() { public int getTimeout() {
try { try {
return ((mSocket != null) return ((socket != null)
? mSocket.getSoTimeout() ? socket.getSoTimeout()
: mTimeout); : timeout);
} }
catch (SocketException se) { catch (SocketException se) {
return mTimeout; return timeout;
} }
} }
@@ -332,24 +331,24 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
} }
int length; int length;
if (mInputStream == null) { if (inputStream == null) {
return null; return null;
} }
// "De-chunk" the output stream // "De-chunk" the output stream
else if ("chunked".equalsIgnoreCase(getHeaderField("Transfer-Encoding"))) { else if ("chunked".equalsIgnoreCase(getHeaderField("Transfer-Encoding"))) {
if (!(mInputStream instanceof ChunkedInputStream)) { if (!(inputStream instanceof ChunkedInputStream)) {
mInputStream = new ChunkedInputStream(mInputStream); inputStream = new ChunkedInputStream(inputStream);
} }
} }
// Make sure we don't wait forever, if the content-length is known // Make sure we don't wait forever, if the content-length is known
else if ((length = getHeaderFieldInt("Content-Length", -1)) >= 0) { else if ((length = getHeaderFieldInt("Content-Length", -1)) >= 0) {
if (!(mInputStream instanceof FixedLengthInputStream)) { if (!(inputStream instanceof FixedLengthInputStream)) {
mInputStream = new FixedLengthInputStream(mInputStream, length); inputStream = new FixedLengthInputStream(inputStream, length);
} }
} }
return mInputStream; return inputStream;
} }
/** /**
@@ -364,7 +363,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
if (!connected) { if (!connected) {
connect(); connect();
} }
return mOutputStream; return outputStream;
} }
/** /**
@@ -374,15 +373,15 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* instance can be reused for other requests. * instance can be reused for other requests.
*/ */
public void disconnect() { public void disconnect() {
if (mSocket != null) { if (socket != null) {
try { try {
mSocket.close(); socket.close();
} }
catch (IOException ioe) { catch (IOException ioe) {
// Does not matter, I guess. // Does not matter, I guess.
} }
mSocket = null; socket = null;
} }
connected = false; connected = false;
} }
@@ -397,37 +396,37 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
: HTTP_DEFAULT_PORT; : HTTP_DEFAULT_PORT;
// Create socket if we don't have one // Create socket if we don't have one
if (mSocket == null) { if (socket == null) {
//mSocket = new Socket(pURL.getHost(), port); // Blocks... //socket = new Socket(pURL.getHost(), port); // Blocks...
mSocket = createSocket(pURL, port, mConnectTimeout); socket = createSocket(pURL, port, connectTimeout);
mSocket.setSoTimeout(mTimeout); socket.setSoTimeout(timeout);
} }
// Get Socket output stream // Get Socket output stream
OutputStream os = mSocket.getOutputStream(); OutputStream os = socket.getOutputStream();
// Connect using HTTP // Connect using HTTP
writeRequestHeaders(os, pURL, method, mRequestProperties, usingProxy(), pAuth, pAuthType); writeRequestHeaders(os, pURL, method, requestProperties, usingProxy(), pAuth, pAuthType);
// Get response input stream // Get response input stream
InputStream sis = mSocket.getInputStream(); InputStream sis = socket.getInputStream();
BufferedInputStream is = new BufferedInputStream(sis); BufferedInputStream is = new BufferedInputStream(sis);
// Detatch reponse headers from reponse input stream // Detatch reponse headers from reponse input stream
InputStream header = detatchResponseHeader(is); InputStream header = detatchResponseHeader(is);
// Parse headers and set response code/message // Parse headers and set response code/message
mResponseHeaders = parseResponseHeader(header); responseHeaders = parseResponseHeader(header);
mResponseHeaderFields = parseHeaderFields(mResponseHeaders); responseHeaderFields = parseHeaderFields(responseHeaders);
//System.err.println("Headers fields:"); //System.err.println("Headers fields:");
//mResponseHeaderFields.list(System.err); //responseHeaderFields.list(System.err);
// Test HTTP response code, to see if further action is needed // Test HTTP response code, to see if further action is needed
switch (getResponseCode()) { switch (getResponseCode()) {
case HTTP_OK: case HTTP_OK:
// 200 OK // 200 OK
mInputStream = is; inputStream = is;
mErrorStream = null; errorStream = null;
break; break;
/* /*
@@ -472,7 +471,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
// Avoid infinite loop // Avoid infinite loop
if (pRetries++ <= 0) { if (pRetries++ <= 0) {
throw new ProtocolException("Server redirected too many times (" + mMaxRedirects + ") (Authentication required: " + auth + ")"); // This is what sun.net.www.protocol.http.HttpURLConnection does throw new ProtocolException("Server redirected too many times (" + maxRedirects + ") (Authentication required: " + auth + ")"); // This is what sun.net.www.protocol.http.HttpURLConnection does
} }
else if (pa != null) { else if (pa != null) {
connect(pURL, pa, method, pRetries); connect(pURL, pa, method, pRetries);
@@ -506,8 +505,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
// Test if we can reuse the Socket // Test if we can reuse the Socket
if (!(newLoc.getAuthority().equals(pURL.getAuthority()) && (newLoc.getPort() == pURL.getPort()))) { if (!(newLoc.getAuthority().equals(pURL.getAuthority()) && (newLoc.getPort() == pURL.getPort()))) {
mSocket.close(); // Close the socket, won't need it anymore socket.close(); // Close the socket, won't need it anymore
mSocket = null; socket = null;
} }
if (location != null) { if (location != null) {
//System.err.println("Redirecting to " + location); //System.err.println("Redirecting to " + location);
@@ -526,22 +525,22 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
default : default :
// Not 200 OK, or any of the redirect responses // Not 200 OK, or any of the redirect responses
// Probably an error... // Probably an error...
mErrorStream = is; errorStream = is;
mInputStream = null; inputStream = null;
} }
// --- Need rethinking... // --- Need rethinking...
// No further questions, let the Socket wait forever (until the server // No further questions, let the Socket wait forever (until the server
// closes the connection) // closes the connection)
//mSocket.setSoTimeout(0); //socket.setSoTimeout(0);
// Probably not... The timeout should only kick if the read BLOCKS. // Probably not... The timeout should only kick if the read BLOCKS.
// Shutdown output, meaning any writes to the outputstream below will // Shutdown output, meaning any writes to the outputstream below will
// probably fail... // probably fail...
//mSocket.shutdownOutput(); //socket.shutdownOutput();
// Not a good idea at all... POSTs need the outputstream to send the // Not a good idea at all... POSTs need the outputstream to send the
// form-data. // form-data.
// --- /Need rethinking. // --- /Need rethinking.
mOutputStream = os; outputStream = os;
} }
private static interface SocketConnector extends Runnable { private static interface SocketConnector extends Runnable {
@@ -663,7 +662,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
return; // Ignore return; // Ignore
} }
connected = true; connected = true;
connect(url, null, null, mMaxRedirects); connect(url, null, null, maxRedirects);
} }
/** /**
@@ -3,7 +3,6 @@ package com.twelvemonkeys.net;
import com.twelvemonkeys.io.FileUtil; import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.DateUtil; import com.twelvemonkeys.lang.DateUtil;
import com.twelvemonkeys.util.BASE64;
import com.twelvemonkeys.util.CollectionUtil; import com.twelvemonkeys.util.CollectionUtil;
import java.io.*; import java.io.*;
@@ -28,29 +28,31 @@
package com.twelvemonkeys.net; package com.twelvemonkeys.net;
import com.twelvemonkeys.lang.Validate;
import java.net.Authenticator; import java.net.Authenticator;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.PasswordAuthentication; import java.net.PasswordAuthentication;
import java.net.URL; import java.net.URL;
import java.util.Hashtable; import java.util.HashMap;
import java.util.Map;
/** /**
* A simple Authenticator implementation. * A simple Authenticator implementation.
* Singleton class, obtain reference through the static * Singleton class, obtain reference through the static
* {@code getInstance} method. * {@code getInstance} method.
* <P> * <p/>
* <EM>After swearing, sweating, pulling my hair, banging my head repeatedly * <EM>After swearing, sweating, pulling my hair, banging my head repeatedly
* into the walls and reading the java.net.Authenticator API documentation * into the walls and reading the java.net.Authenticator API documentation
* once more, an idea came to my mind. This is the result. I hope you find it * once more, an idea came to my mind. This is the result. I hope you find it
* useful. -- Harald K.</EM> * useful. -- Harald K.</EM>
* *
* @see java.net.Authenticator
*
* @author Harald Kuhr (haraldk@iconmedialab.no) * @author Harald Kuhr (haraldk@iconmedialab.no)
* @version 1.0 * @version 1.0
* @see java.net.Authenticator
*/ */
public class SimpleAuthenticator extends Authenticator { public class SimpleAuthenticator extends Authenticator {
/** The reference to the single instance of this class. */ /** The reference to the single instance of this class. */
private static SimpleAuthenticator sInstance = null; private static SimpleAuthenticator sInstance = null;
/** Keeps track of the state of this class. */ /** Keeps track of the state of this class. */
@@ -63,237 +65,179 @@ public class SimpleAuthenticator extends Authenticator {
/** Basic authentication scheme. */ /** Basic authentication scheme. */
public final static String BASIC = "Basic"; public final static String BASIC = "Basic";
/** /** The hastable that keeps track of the PasswordAuthentications. */
* The hastable that keeps track of the PasswordAuthentications. protected Map<AuthKey, PasswordAuthentication> passwordAuthentications = null;
*/
protected Hashtable mPasswordAuthentications = null; /** The hastable that keeps track of the Authenticators. */
protected Map<PasswordAuthenticator, AuthenticatorFilter> authenticators = null;
/**
* The hastable that keeps track of the Authenticators.
*/
protected Hashtable mAuthenticators = null;
/**
* Creates a SimpleAuthenticator.
*/
/** Creates a SimpleAuthenticator. */
private SimpleAuthenticator() { private SimpleAuthenticator() {
mPasswordAuthentications = new Hashtable(); passwordAuthentications = new HashMap<AuthKey, PasswordAuthentication>();
mAuthenticators = new Hashtable(); authenticators = new HashMap<PasswordAuthenticator, AuthenticatorFilter>();
} }
/** /**
* Gets the SimpleAuthenticator instance and registers it through the * Gets the SimpleAuthenticator instance and registers it through the
* Authenticator.setDefault(). If there is no current instance * Authenticator.setDefault(). If there is no current instance
* of the SimpleAuthenticator in the VM, one is created. This method will * of the SimpleAuthenticator in the VM, one is created. This method will
* try to figure out if the setDefault() succeeded (a hack), and will * try to figure out if the setDefault() succeeded (a hack), and will
* return null if it was not able to register the instance as default. * return null if it was not able to register the instance as default.
* *
* @return The single instance of this class, or null, if another * @return The single instance of this class, or null, if another
* Authenticator is allready registered as default. * Authenticator is allready registered as default.
*/ */
public static synchronized SimpleAuthenticator getInstance() { public static synchronized SimpleAuthenticator getInstance() {
if (!sInitialized) { if (!sInitialized) {
// Create an instance // Create an instance
sInstance = new SimpleAuthenticator(); sInstance = new SimpleAuthenticator();
// Try to set default (this may quietly fail...) // Try to set default (this may quietly fail...)
Authenticator.setDefault(sInstance); Authenticator.setDefault(sInstance);
// A hack to figure out if we really did set the authenticator // A hack to figure out if we really did set the authenticator
PasswordAuthentication pa = PasswordAuthentication pa = Authenticator.requestPasswordAuthentication(null, FOURTYTWO, null, null, MAGIC);
Authenticator.requestPasswordAuthentication(null, FOURTYTWO,
null, null, MAGIC);
// If this test returns false, we didn't succeed, so we set the // If this test returns false, we didn't succeed, so we set the
// instance back to null. // instance back to null.
if (pa == null || !MAGIC.equals(pa.getUserName()) || if (pa == null || !MAGIC.equals(pa.getUserName()) || !("" + FOURTYTWO).equals(new String(pa.getPassword()))) {
!("" + FOURTYTWO).equals(new String(pa.getPassword()))) sInstance = null;
sInstance = null; }
// Done // Done
sInitialized = true; sInitialized = true;
} }
return sInstance; return sInstance;
} }
/** /**
* Gets the PasswordAuthentication for the request. Called when password * Gets the PasswordAuthentication for the request. Called when password
* authorization is needed. * authorization is needed.
* *
* @return The PasswordAuthentication collected from the user, or null if * @return The PasswordAuthentication collected from the user, or null if
* none is provided. * none is provided.
*/ */
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
// Don't worry, this is just a hack to figure out if we were able // Don't worry, this is just a hack to figure out if we were able
// to set this Authenticator through the setDefault method. // to set this Authenticator through the setDefault method.
if (!sInitialized && MAGIC.equals(getRequestingScheme()) if (!sInitialized && MAGIC.equals(getRequestingScheme()) && getRequestingPort() == FOURTYTWO) {
&& getRequestingPort() == FOURTYTWO) return new PasswordAuthentication(MAGIC, ("" + FOURTYTWO).toCharArray());
return new PasswordAuthentication(MAGIC, ("" + FOURTYTWO) }
.toCharArray()); /*
/* System.err.println("getPasswordAuthentication");
System.err.println("getPasswordAuthentication"); System.err.println(getRequestingSite());
System.err.println(getRequestingSite()); System.err.println(getRequestingPort());
System.err.println(getRequestingPort()); System.err.println(getRequestingProtocol());
System.err.println(getRequestingProtocol()); System.err.println(getRequestingPrompt());
System.err.println(getRequestingPrompt()); System.err.println(getRequestingScheme());
System.err.println(getRequestingScheme()); */
*/
// TODO: // TODO:
// Look for a more specific PasswordAuthenticatior before using // Look for a more specific PasswordAuthenticatior before using
// Default: // Default:
// //
// if (...) // if (...)
// return pa.requestPasswordAuthentication(getRequestingSite(), // return pa.requestPasswordAuthentication(getRequestingSite(),
// getRequestingPort(), // getRequestingPort(),
// getRequestingProtocol(), // getRequestingProtocol(),
// getRequestingPrompt(), // getRequestingPrompt(),
// getRequestingScheme()); // getRequestingScheme());
return (PasswordAuthentication) return passwordAuthentications.get(new AuthKey(getRequestingSite(),
mPasswordAuthentications.get(new AuthKey(getRequestingSite(), getRequestingPort(),
getRequestingPort(), getRequestingProtocol(),
getRequestingProtocol(), getRequestingPrompt(),
getRequestingPrompt(), getRequestingScheme()));
getRequestingScheme()));
} }
/** /** Registers a PasswordAuthentication with a given URL address. */
* Registers a PasswordAuthentication with a given URL address. public PasswordAuthentication registerPasswordAuthentication(URL pURL, PasswordAuthentication pPA) {
* return registerPasswordAuthentication(NetUtil.createInetAddressFromURL(pURL),
*/ pURL.getPort(),
pURL.getProtocol(),
public PasswordAuthentication registerPasswordAuthentication(URL pURL, null, // Prompt/Realm
PasswordAuthentication pPA) { BASIC,
return registerPasswordAuthentication(NetUtil.createInetAddressFromURL(pURL), pPA);
pURL.getPort(),
pURL.getProtocol(),
null, // Prompt/Realm
BASIC,
pPA);
} }
/** /** Registers a PasswordAuthentication with a given net address. */
* Registers a PasswordAuthentication with a given net address. public PasswordAuthentication registerPasswordAuthentication(InetAddress pAddress, int pPort, String pProtocol, String pPrompt, String pScheme, PasswordAuthentication pPA) {
* /*
*/ System.err.println("registerPasswordAuthentication");
System.err.println(pAddress);
System.err.println(pPort);
System.err.println(pProtocol);
System.err.println(pPrompt);
System.err.println(pScheme);
*/
public PasswordAuthentication registerPasswordAuthentication( return passwordAuthentications.put(new AuthKey(pAddress, pPort, pProtocol, pPrompt, pScheme), pPA);
InetAddress pAddress, int pPort, String pProtocol,
String pPrompt, String pScheme, PasswordAuthentication pPA)
{
/*
System.err.println("registerPasswordAuthentication");
System.err.println(pAddress);
System.err.println(pPort);
System.err.println(pProtocol);
System.err.println(pPrompt);
System.err.println(pScheme);
*/
return (PasswordAuthentication)
mPasswordAuthentications.put(new AuthKey(pAddress, pPort,
pProtocol, pPrompt,
pScheme),
pPA);
} }
/** /** Unregisters a PasswordAuthentication with a given URL address. */
* Unregisters a PasswordAuthentication with a given URL address.
*
*/
public PasswordAuthentication unregisterPasswordAuthentication(URL pURL) { public PasswordAuthentication unregisterPasswordAuthentication(URL pURL) {
return unregisterPasswordAuthentication(NetUtil.createInetAddressFromURL(pURL), return unregisterPasswordAuthentication(NetUtil.createInetAddressFromURL(pURL), pURL.getPort(), pURL.getProtocol(), null, BASIC);
pURL.getPort(),
pURL.getProtocol(),
null,
BASIC);
} }
/** /** Unregisters a PasswordAuthentication with a given net address. */
* Unregisters a PasswordAuthentication with a given net address. public PasswordAuthentication unregisterPasswordAuthentication(InetAddress pAddress, int pPort, String pProtocol, String pPrompt, String pScheme) {
* return passwordAuthentications.remove(new AuthKey(pAddress, pPort, pProtocol, pPrompt, pScheme));
*/
public PasswordAuthentication unregisterPasswordAuthentication(
InetAddress pAddress, int pPort, String pProtocol,
String pPrompt, String pScheme)
{
return (PasswordAuthentication)
mPasswordAuthentications.remove(new AuthKey(pAddress, pPort,
pProtocol, pPrompt,
pScheme));
} }
/** /**
* TODO: Registers a PasswordAuthenticator that can answer authentication * TODO: Registers a PasswordAuthenticator that can answer authentication
* requests. * requests.
* *
* @see PasswordAuthenticator * @see PasswordAuthenticator
*/ */
public void registerPasswordAuthenticator(PasswordAuthenticator pPA, AuthenticatorFilter pFilter) {
public void registerPasswordAuthenticator(PasswordAuthenticator pPA, authenticators.put(pPA, pFilter);
AuthenticatorFilter pFilter) {
mAuthenticators.put(pPA, pFilter);
} }
/** /**
* TODO: Unregisters a PasswordAuthenticator that can answer authentication * TODO: Unregisters a PasswordAuthenticator that can answer authentication
* requests. * requests.
* *
* @see PasswordAuthenticator * @see PasswordAuthenticator
*/ */
public void unregisterPasswordAuthenticator(PasswordAuthenticator pPA) { public void unregisterPasswordAuthenticator(PasswordAuthenticator pPA) {
mAuthenticators.remove(pPA); authenticators.remove(pPA);
} }
} }
/** /**
* Utility class, used for caching the PasswordAuthentication objects. * Utility class, used for caching the PasswordAuthentication objects.
* Everything but address may be null * Everything but address may be null
*/ */
class AuthKey { class AuthKey {
InetAddress mAddress = null;
int mPort = -1;
String mProtocol = null;
String mPrompt = null;
String mScheme = null;
AuthKey(InetAddress pAddress, int pPort, String pProtocol, InetAddress address = null;
String pPrompt, String pScheme) { int port = -1;
if (pAddress == null) String protocol = null;
throw new IllegalArgumentException("Address argument can't be null!"); String prompt = null;
String scheme = null;
mAddress = pAddress; AuthKey(InetAddress pAddress, int pPort, String pProtocol, String pPrompt, String pScheme) {
mPort = pPort; Validate.notNull(pAddress, "address");
mProtocol = pProtocol;
mPrompt = pPrompt;
mScheme = pScheme;
// System.out.println("Created: " + this); address = pAddress;
port = pPort;
protocol = pProtocol;
prompt = pPrompt;
scheme = pScheme;
// System.out.println("Created: " + this);
} }
/** /** Creates a string representation of this object. */
* Creates a string representation of this object.
*/
public String toString() { public String toString() {
return "AuthKey[" + mAddress + ":" + mPort + "/" + mProtocol + " \"" + mPrompt + "\" (" + mScheme + ")]"; return "AuthKey[" + address + ":" + port + "/" + protocol + " \"" + prompt + "\" (" + scheme + ")]";
} }
public boolean equals(Object pObj) { public boolean equals(Object pObj) {
return (pObj instanceof AuthKey ? equals((AuthKey) pObj) : false); return (pObj instanceof AuthKey && equals((AuthKey) pObj));
} }
// Ahem.. Breaks the rule from Object.equals(Object): // Ahem.. Breaks the rule from Object.equals(Object):
@@ -302,25 +246,25 @@ class AuthKey {
// should return true. // should return true.
public boolean equals(AuthKey pKey) { public boolean equals(AuthKey pKey) {
// Maybe allow nulls, and still be equal? // Maybe allow nulls, and still be equal?
return (mAddress.equals(pKey.mAddress) return (address.equals(pKey.address)
&& (mPort == -1 && (port == -1
|| pKey.mPort == -1 || pKey.port == -1
|| mPort == pKey.mPort) || port == pKey.port)
&& (mProtocol == null && (protocol == null
|| pKey.mProtocol == null || pKey.protocol == null
|| mProtocol.equals(pKey.mProtocol)) || protocol.equals(pKey.protocol))
&& (mPrompt == null && (prompt == null
|| pKey.mPrompt == null || pKey.prompt == null
|| mPrompt.equals(pKey.mPrompt)) || prompt.equals(pKey.prompt))
&& (mScheme == null && (scheme == null
|| pKey.mScheme == null || pKey.scheme == null
|| mScheme.equalsIgnoreCase(pKey.mScheme))); || scheme.equalsIgnoreCase(pKey.scheme)));
} }
public int hashCode() { public int hashCode() {
// There won't be too many pr address, will it? ;-) // There won't be too many pr address, will it? ;-)
return mAddress.hashCode(); return address.hashCode();
} }
} }
@@ -52,13 +52,13 @@ public final class DOMSerializer {
private static final String PARAM_PRETTY_PRINT = "format-pretty-print"; private static final String PARAM_PRETTY_PRINT = "format-pretty-print";
private static final String PARAM_XML_DECLARATION = "xml-declaration"; private static final String PARAM_XML_DECLARATION = "xml-declaration";
private final LSSerializer mSerializer; private final LSSerializer serializer;
private final LSOutput mOutput; private final LSOutput output;
private DOMSerializer() { private DOMSerializer() {
DOMImplementationLS domImpl = Support.getImplementation(); DOMImplementationLS domImpl = Support.getImplementation();
mSerializer = domImpl.createLSSerializer(); serializer = domImpl.createLSSerializer();
mOutput = domImpl.createLSOutput(); output = domImpl.createLSOutput();
} }
/** /**
@@ -71,8 +71,8 @@ public final class DOMSerializer {
public DOMSerializer(final OutputStream pStream, final String pEncoding) { public DOMSerializer(final OutputStream pStream, final String pEncoding) {
this(); this();
mOutput.setByteStream(pStream); output.setByteStream(pStream);
mOutput.setEncoding(pEncoding); output.setEncoding(pEncoding);
} }
/** /**
@@ -84,17 +84,17 @@ public final class DOMSerializer {
public DOMSerializer(final Writer pStream) { public DOMSerializer(final Writer pStream) {
this(); this();
mOutput.setCharacterStream(pStream); output.setCharacterStream(pStream);
} }
/* /*
// TODO: Is it useful? // TODO: Is it useful?
public void setNewLine(final String pNewLine) { public void setNewLine(final String pNewLine) {
mSerializer.setNewLine(pNewLine); serializer.setNewLine(pNewLine);
} }
public String getNewLine() { public String getNewLine() {
return mSerializer.getNewLine(); return serializer.getNewLine();
} }
*/ */
@@ -107,18 +107,18 @@ public final class DOMSerializer {
* @param pPrettyPrint {@code true} to enable pretty printing * @param pPrettyPrint {@code true} to enable pretty printing
*/ */
public void setPrettyPrint(final boolean pPrettyPrint) { public void setPrettyPrint(final boolean pPrettyPrint) {
DOMConfiguration configuration = mSerializer.getDomConfig(); DOMConfiguration configuration = serializer.getDomConfig();
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) { if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint); configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
} }
} }
public boolean getPrettyPrint() { public boolean getPrettyPrint() {
return Boolean.TRUE.equals(mSerializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT)); return Boolean.TRUE.equals(serializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
} }
private void setXMLDeclaration(boolean pXMLDeclaration) { private void setXMLDeclaration(boolean pXMLDeclaration) {
mSerializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration); serializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
} }
/** /**
@@ -142,7 +142,7 @@ public final class DOMSerializer {
private void serializeImpl(final Node pNode, final boolean pOmitDecl) { private void serializeImpl(final Node pNode, final boolean pOmitDecl) {
setXMLDeclaration(pOmitDecl); setXMLDeclaration(pOmitDecl);
mSerializer.write(pNode, mOutput); serializer.write(pNode, output);
} }
private static class Support { private static class Support {
@@ -59,28 +59,22 @@ public class XMLSerializer {
// TODO: Consider using IOException to communicate trouble, rather than RTE, // TODO: Consider using IOException to communicate trouble, rather than RTE,
// to be more compatible... // to be more compatible...
// TODO: Idea: Create a SerializationContext that stores attributes on private final OutputStream output;
// serialization, to keep the serialization thread-safe private final Charset encoding;
// Store preserveSpace attribute in this context, to avoid costly traversals private final SerializationContext context;
// Store user options here too
// TODO: Push/pop?
private final OutputStream mOutput;
private final Charset mEncoding;
private final SerializationContext mContext;
public XMLSerializer(final OutputStream pOutput, final String pEncoding) { public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
mOutput = pOutput; output = pOutput;
mEncoding = Charset.forName(pEncoding); encoding = Charset.forName(pEncoding);
mContext = new SerializationContext(); context = new SerializationContext();
} }
public final void setIndentation(String pIndent) { public final void setIndentation(String pIndent) {
mContext.indent = pIndent != null ? pIndent : " "; context.indent = pIndent != null ? pIndent : "\t";
} }
public final void setStripComments(boolean pStrip) { public final void setStripComments(boolean pStrip) {
mContext.stripComments = pStrip; context.stripComments = pStrip;
} }
/** /**
@@ -101,12 +95,12 @@ public class XMLSerializer {
* @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}. * @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}.
*/ */
public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) { public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) {
PrintWriter out = new PrintWriter(new OutputStreamWriter(mOutput, mEncoding)); PrintWriter out = new PrintWriter(new OutputStreamWriter(output, encoding));
try { try {
if (pWriteXMLDeclaration) { if (pWriteXMLDeclaration) {
writeXMLDeclaration(out); writeXMLDeclaration(out);
} }
writeXML(out, pRootNode, mContext.copy()); writeXML(out, pRootNode, context.copy());
} }
finally { finally {
out.flush(); out.flush();
@@ -115,7 +109,7 @@ public class XMLSerializer {
private void writeXMLDeclaration(final PrintWriter pOut) { private void writeXMLDeclaration(final PrintWriter pOut) {
pOut.print("<?xml version=\"1.0\" encoding=\""); pOut.print("<?xml version=\"1.0\" encoding=\"");
pOut.print(mEncoding.name()); pOut.print(encoding.name());
pOut.println("\"?>"); pOut.println("\"?>");
} }
@@ -279,11 +273,7 @@ public class XMLSerializer {
pos = appendAndEscape(pValue, pos, i, builder, "&gt;"); pos = appendAndEscape(pValue, pos, i, builder, "&gt;");
break; break;
//case '\'': //case '\'':
// pos = appendAndEscape(pString, pos, i, builder, "&apos;");
// break;
//case '"': //case '"':
// pos = appendAndEscape(pString, pos, i, builder, "&quot;");
// break;
default: default:
break; break;
} }
@@ -347,17 +337,6 @@ public class XMLSerializer {
} }
} }
//StringBuilder builder = new StringBuilder(pValue.length() + 30);
//
//int start = 0;
//while (end >= 0) {
// builder.append(pValue.substring(start, end));
// builder.append("&quot;");
// start = end + 1;
// end = pValue.indexOf('"', start);
//}
//builder.append(pValue.substring(start));
builder.append(pValue.substring(pos)); builder.append(pValue.substring(pos));
return builder.toString(); return builder.toString();
@@ -389,14 +368,14 @@ public class XMLSerializer {
} }
private static String validateCDataValue(final String pValue) { private static String validateCDataValue(final String pValue) {
if (pValue.indexOf("]]>") >= 0) { if (pValue.contains("]]>")) {
throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'"); throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'");
} }
return pValue; return pValue;
} }
private static String validateCommentValue(final String pValue) { private static String validateCommentValue(final String pValue) {
if (pValue.indexOf("--") >= 0) { if (pValue.contains("--")) {
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'"); throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
} }
return pValue; return pValue;
@@ -420,8 +399,6 @@ public class XMLSerializer {
// even if the document was created using attributes instead of namespaces... // even if the document was created using attributes instead of namespaces...
// In that case, prefix will be null... // In that case, prefix will be null...
// TODO: Don't insert duplicate/unnecessary namesspace declarations
// Handle namespace // Handle namespace
String namespace = pNode.getNamespaceURI(); String namespace = pNode.getNamespaceURI();
if (namespace != null && !namespace.equals(pContext.defaultNamespace)) { if (namespace != null && !namespace.equals(pContext.defaultNamespace)) {
@@ -570,6 +547,11 @@ public class XMLSerializer {
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n ")); pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
test.appendChild(pre); test.appendChild(pre);
Element pre2 = document.createElementNS("http://www.twelvemonkeys.com/xml/test", "tight");
pre2.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
pre2.appendChild(document.createTextNode("no-space-around-me"));
test.appendChild(pre2);
// Create serializer and output document // Create serializer and output document
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true)); //XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
System.out.println("XMLSerializer:"); System.out.println("XMLSerializer:");
@@ -612,7 +594,7 @@ public class XMLSerializer {
} }
static class SerializationContext implements Cloneable { static class SerializationContext implements Cloneable {
String indent = " "; String indent = "\t";
int level = 0; int level = 0;
boolean preserveSpace = false; boolean preserveSpace = false;
boolean stripComments = false; boolean stripComments = false;
@@ -2,6 +2,7 @@ package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.CollectionUtil; import com.twelvemonkeys.util.CollectionUtil;
import org.junit.Test;
import java.io.Reader; import java.io.Reader;
import java.io.IOException; import java.io.IOException;
@@ -9,6 +10,8 @@ import java.io.StringReader;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import static org.junit.Assert.*;
/** /**
* CompoundReaderTestCase * CompoundReaderTestCase
* <p/> * <p/>
@@ -18,7 +21,6 @@ import java.util.ArrayList;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java#2 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java#2 $
*/ */
public class CompoundReaderTestCase extends ReaderAbstractTestCase { public class CompoundReaderTestCase extends ReaderAbstractTestCase {
protected Reader makeReader(String pInput) { protected Reader makeReader(String pInput) {
// Split // Split
String[] input = StringUtil.toStringArray(pInput, " "); String[] input = StringUtil.toStringArray(pInput, " ");
@@ -36,6 +38,7 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
return new CompoundReader(readers.iterator()); return new CompoundReader(readers.iterator());
} }
@Test
public void testNullConstructor() { public void testNullConstructor() {
try { try {
new CompoundReader(null); new CompoundReader(null);
@@ -46,11 +49,13 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
} }
} }
@Test
public void testEmptyIteratorConstructor() throws IOException { public void testEmptyIteratorConstructor() throws IOException {
Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0])); Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0]));
assertEquals(-1, reader.read()); assertEquals(-1, reader.read());
} }
@Test
public void testIteratorWithNullConstructor() throws IOException { public void testIteratorWithNullConstructor() throws IOException {
try { try {
new CompoundReader(CollectionUtil.iterator(new Reader[] {null})); new CompoundReader(CollectionUtil.iterator(new Reader[] {null}));
@@ -1,8 +1,12 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static org.junit.Assert.assertEquals;
/** /**
* FastByteArrayOutputStreamTestCase * FastByteArrayOutputStreamTestCase
* <p/> * <p/>
@@ -16,6 +20,7 @@ public class FastByteArrayOutputStreamTestCase extends OutputStreamAbstractTestC
return new FastByteArrayOutputStream(256); return new FastByteArrayOutputStream(256);
} }
@Test
public void testCreateInputStream() throws IOException { public void testCreateInputStream() throws IOException {
FastByteArrayOutputStream out = makeObject(); FastByteArrayOutputStream out = makeObject();
@@ -11,10 +11,6 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java#3 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java#3 $
*/ */
public class FileCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase { public class FileCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
public FileCacheSeekableStreamTestCase(String name) {
super(name);
}
protected SeekableInputStream makeInputStream(final InputStream pStream) { protected SeekableInputStream makeInputStream(final InputStream pStream) {
try { try {
return new FileCacheSeekableStream(pStream); return new FileCacheSeekableStream(pStream);
@@ -1,7 +1,11 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.*; import java.io.*;
import static org.junit.Assert.*;
/** /**
* MemoryCacheSeekableStreamTestCase * MemoryCacheSeekableStreamTestCase
* <p/> * <p/>
@@ -10,10 +14,6 @@ import java.io.*;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java#3 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java#3 $
*/ */
public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase { public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
public FileSeekableStreamTestCase(String name) {
super(name);
}
protected SeekableInputStream makeInputStream(final InputStream pStream) { protected SeekableInputStream makeInputStream(final InputStream pStream) {
try { try {
return new FileSeekableStream(createFileWithContent(pStream)); return new FileSeekableStream(createFileWithContent(pStream));
@@ -37,11 +37,13 @@ public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestC
return temp; return temp;
} }
@Test
@Override @Override
public void testCloseUnderlyingStream() throws IOException { public void testCloseUnderlyingStream() throws IOException {
// There is no underlying stream here... // There is no underlying stream here...
} }
@Test
public void testCloseUnderlyingFile() throws IOException { public void testCloseUnderlyingFile() throws IOException {
final boolean[] closed = new boolean[1]; final boolean[] closed = new boolean[1];
@@ -17,12 +17,15 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.ObjectAbstractTestCase; import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Random; import java.util.Random;
import static org.junit.Assert.*;
/** /**
* InputStreamAbstractTestCase * InputStreamAbstractTestCase
* <p/> * <p/>
@@ -38,10 +41,6 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
final static private long SEED = 29487982745l; final static private long SEED = 29487982745l;
final static Random sRandom = new Random(SEED); final static Random sRandom = new Random(SEED);
public InputStreamAbstractTestCase(String name) {
super(name);
}
protected final Object makeObject() { protected final Object makeObject() {
return makeInputStream(); return makeInputStream();
} }
@@ -71,11 +70,12 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
return bytes; return bytes;
} }
@Test
public void testRead() throws Exception { public void testRead() throws Exception {
int size = 5; int size = 5;
InputStream input = makeInputStream(makeOrderedArray(size)); InputStream input = makeInputStream(makeOrderedArray(size));
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
assertEquals("Check Size [" + i + "]", (size - i), input.available()); assertTrue("Check Size [" + i + "]", (size - i) >= input.available());
assertEquals("Check Value [" + i + "]", i, input.read()); assertEquals("Check Value [" + i + "]", i, input.read());
} }
assertEquals("Available after contents all read", 0, input.available()); assertEquals("Available after contents all read", 0, input.available());
@@ -90,6 +90,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testAvailable() throws Exception { public void testAvailable() throws Exception {
InputStream input = makeInputStream(1); InputStream input = makeInputStream(1);
assertFalse("Unexpected EOF", input.read() < 0); assertFalse("Unexpected EOF", input.read() < 0);
@@ -100,6 +101,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
assertEquals("Available after End of File", 0, input.available()); assertEquals("Available after End of File", 0, input.available());
} }
@Test
public void testReadByteArray() throws Exception { public void testReadByteArray() throws Exception {
byte[] bytes = new byte[10]; byte[] bytes = new byte[10];
byte[] data = makeOrderedArray(15); byte[] data = makeOrderedArray(15);
@@ -145,6 +147,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testEOF() throws Exception { public void testEOF() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(2)); InputStream input = makeInputStream(makeOrderedArray(2));
assertEquals("Read 1", 0, input.read()); assertEquals("Read 1", 0, input.read());
@@ -154,6 +157,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
assertEquals("Read 5", -1, input.read()); assertEquals("Read 5", -1, input.read());
} }
@Test
public void testMarkResetUnsupported() throws IOException { public void testMarkResetUnsupported() throws IOException {
InputStream input = makeInputStream(10); InputStream input = makeInputStream(10);
if (input.markSupported()) { if (input.markSupported()) {
@@ -176,6 +180,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testResetNoMark() throws Exception { public void testResetNoMark() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(10)); InputStream input = makeInputStream(makeOrderedArray(10));
@@ -196,6 +201,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testMarkReset() throws Exception { public void testMarkReset() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25)); InputStream input = makeInputStream(makeOrderedArray(25));
@@ -226,6 +232,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testResetAfterReadLimit() throws Exception { public void testResetAfterReadLimit() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25)); InputStream input = makeInputStream(makeOrderedArray(25));
@@ -257,6 +264,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testResetAfterReset() throws Exception { public void testResetAfterReset() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25)); InputStream input = makeInputStream(makeOrderedArray(25));
@@ -264,7 +272,8 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
return; // Not supported, skip test return; // Not supported, skip test
} }
assertTrue("Expected to read positive value", input.read() >= 0); int first = input.read();
assertTrue("Expected to read positive value", first >= 0);
int readlimit = 5; int readlimit = 5;
@@ -273,19 +282,24 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
int read = input.read(); int read = input.read();
assertTrue("Expected to read positive value", read >= 0); assertTrue("Expected to read positive value", read >= 0);
input.reset(); assertTrue(input.read() >= 0);
assertEquals("Expected value read differes from actual", read, input.read()); assertTrue(input.read() >= 0);
// Reset after read limit passed, may either throw exception, or reset to last mark input.reset();
assertEquals("Expected value read differs from actual", read, input.read());
// Reset after read limit passed, may either throw exception, or reset to last good mark
try { try {
input.reset(); input.reset();
assertEquals("Re-read of reset data should be same", read, input.read()); int reRead = input.read();
assertTrue("Re-read of reset data should be same as initially marked or first", reRead == read || reRead == first);
} }
catch (Exception e) { catch (Exception e) {
assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark")); assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark"));
} }
} }
@Test
public void testSkip() throws Exception { public void testSkip() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(10)); InputStream input = makeInputStream(makeOrderedArray(10));
@@ -302,6 +316,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
assertEquals("Unexpected value read after EOF", -1, input.read()); assertEquals("Unexpected value read after EOF", -1, input.read());
} }
@Test
public void testSanityOrdered() throws IOException { public void testSanityOrdered() throws IOException {
// This is to sanity check that the test itself is correct... // This is to sanity check that the test itself is correct...
byte[] bytes = makeOrderedArray(25); byte[] bytes = makeOrderedArray(25);
@@ -314,6 +329,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testSanityOrdered2() throws IOException { public void testSanityOrdered2() throws IOException {
// This is to sanity check that the test itself is correct... // This is to sanity check that the test itself is correct...
byte[] bytes = makeOrderedArray(25); byte[] bytes = makeOrderedArray(25);
@@ -332,6 +348,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testSanityNegative() throws IOException { public void testSanityNegative() throws IOException {
// This is to sanity check that the test itself is correct... // This is to sanity check that the test itself is correct...
byte[] bytes = new byte[25]; byte[] bytes = new byte[25];
@@ -347,6 +364,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testSanityNegative2() throws IOException { public void testSanityNegative2() throws IOException {
// This is to sanity check that the test itself is correct... // This is to sanity check that the test itself is correct...
byte[] bytes = new byte[25]; byte[] bytes = new byte[25];
@@ -368,6 +386,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testSanityRandom() throws IOException { public void testSanityRandom() throws IOException {
// This is to sanity check that the test itself is correct... // This is to sanity check that the test itself is correct...
byte[] bytes = makeRandomArray(25); byte[] bytes = makeRandomArray(25);
@@ -380,6 +399,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
} }
} }
@Test
public void testSanityRandom2() throws IOException { public void testSanityRandom2() throws IOException {
// This is to sanity check that the test itself is correct... // This is to sanity check that the test itself is correct...
byte[] bytes = makeRandomArray(25); byte[] bytes = makeRandomArray(25);
@@ -10,10 +10,6 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java#2 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java#2 $
*/ */
public class MemoryCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase { public class MemoryCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
public MemoryCacheSeekableStreamTestCase(String name) {
super(name);
}
protected SeekableInputStream makeInputStream(final InputStream pStream) { protected SeekableInputStream makeInputStream(final InputStream pStream) {
return new MemoryCacheSeekableStream(pStream); return new MemoryCacheSeekableStream(pStream);
} }
@@ -1,10 +1,13 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.ObjectAbstractTestCase; import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.IOException; import java.io.IOException;
import static org.junit.Assert.*;
/** /**
* InputStreamAbstractTestCase * InputStreamAbstractTestCase
* <p/> * <p/>
@@ -15,6 +18,7 @@ import java.io.IOException;
public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase { public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase {
protected abstract OutputStream makeObject(); protected abstract OutputStream makeObject();
@Test
public void testWrite() throws IOException { public void testWrite() throws IOException {
OutputStream os = makeObject(); OutputStream os = makeObject();
@@ -23,12 +27,14 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testWriteByteArray() throws IOException { public void testWriteByteArray() throws IOException {
OutputStream os = makeObject(); OutputStream os = makeObject();
os.write(new byte[256]); os.write(new byte[256]);
} }
@Test
public void testWriteByteArrayNull() { public void testWriteByteArrayNull() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
@@ -46,7 +52,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
public void testWriteByteArrayOffsetLenght() throws IOException { @Test
public void testWriteByteArrayOffsetLength() throws IOException {
byte[] input = new byte[256]; byte[] input = new byte[256];
OutputStream os = makeObject(); OutputStream os = makeObject();
@@ -65,7 +72,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
public void testWriteByteArrayZeroLenght() { @Test
public void testWriteByteArrayZeroLength() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
os.write(new byte[1], 0, 0); os.write(new byte[1], 0, 0);
@@ -75,7 +83,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
public void testWriteByteArrayOffsetLenghtNull() { @Test
public void testWriteByteArrayOffsetLengthNull() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
os.write(null, 5, 10); os.write(null, 5, 10);
@@ -92,6 +101,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testWriteByteArrayNegativeOffset() { public void testWriteByteArrayNegativeOffset() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
@@ -109,6 +119,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testWriteByteArrayNegativeLength() { public void testWriteByteArrayNegativeLength() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
@@ -126,6 +137,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testWriteByteArrayOffsetOutOfBounds() { public void testWriteByteArrayOffsetOutOfBounds() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
@@ -143,6 +155,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testWriteByteArrayLengthOutOfBounds() { public void testWriteByteArrayLengthOutOfBounds() {
OutputStream os = makeObject(); OutputStream os = makeObject();
try { try {
@@ -160,14 +173,17 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testFlush() { public void testFlush() {
// TODO: Implement // TODO: Implement
} }
@Test
public void testClose() { public void testClose() {
// TODO: Implement // TODO: Implement
} }
@Test
public void testWriteAfterClose() throws IOException { public void testWriteAfterClose() throws IOException {
OutputStream os = makeObject(); OutputStream os = makeObject();
@@ -200,6 +216,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testFlushAfterClose() throws IOException { public void testFlushAfterClose() throws IOException {
OutputStream os = makeObject(); OutputStream os = makeObject();
@@ -221,6 +238,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
} }
} }
@Test
public void testCloseAfterClose() throws IOException { public void testCloseAfterClose() throws IOException {
OutputStream os = makeObject(); OutputStream os = makeObject();
@@ -1,10 +1,13 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.ObjectAbstractTestCase; import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.Reader; import java.io.Reader;
import java.io.IOException; import java.io.IOException;
import static org.junit.Assert.*;
/** /**
* ReaderAbstractTestCase * ReaderAbstractTestCase
* <p/> * <p/>
@@ -36,6 +39,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
protected abstract Reader makeReader(String pInput); protected abstract Reader makeReader(String pInput);
@Test
public void testRead() throws IOException { public void testRead() throws IOException {
Reader reader = makeReader(); Reader reader = makeReader();
@@ -51,6 +55,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
assertEquals(mInput, buffer.toString()); assertEquals(mInput, buffer.toString());
} }
@Test
public void testReadBuffer() throws IOException { public void testReadBuffer() throws IOException {
Reader reader = makeReader(); Reader reader = makeReader();
@@ -70,6 +75,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
assertEquals(mInput, new String(chars)); assertEquals(mInput, new String(chars));
} }
@Test
public void testSkipToEnd() throws IOException { public void testSkipToEnd() throws IOException {
Reader reader = makeReader(); Reader reader = makeReader();
@@ -83,6 +89,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
assertEquals(0, toSkip); assertEquals(0, toSkip);
} }
@Test
public void testSkipToEndAndRead() throws IOException { public void testSkipToEndAndRead() throws IOException {
Reader reader = makeReader(); Reader reader = makeReader();
@@ -95,6 +102,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
} }
// TODO: It's possible to support reset and not mark (resets to beginning of stream, for example) // TODO: It's possible to support reset and not mark (resets to beginning of stream, for example)
@Test
public void testResetMarkSupported() throws IOException { public void testResetMarkSupported() throws IOException {
Reader reader = makeReader(); Reader reader = makeReader();
@@ -154,6 +162,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
} }
} }
@Test
public void testResetMarkNotSupported() throws IOException { public void testResetMarkNotSupported() throws IOException {
Reader reader = makeReader(); Reader reader = makeReader();
@@ -198,7 +207,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
} }
} }
@Test
public void testReadAfterClose() throws IOException { public void testReadAfterClose() throws IOException {
Reader reader = makeReader("foo bar"); Reader reader = makeReader("foo bar");
@@ -1,6 +1,8 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import junit.framework.TestCase; import org.junit.Test;
import static org.junit.Assert.*;
/** /**
* SeekableAbstractTestCase * SeekableAbstractTestCase
@@ -9,14 +11,16 @@ import junit.framework.TestCase;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java#1 $
*/ */
public abstract class SeekableAbstractTestCase extends TestCase implements SeekableInterfaceTest { public abstract class SeekableAbstractTestCase implements SeekableInterfaceTest {
protected abstract Seekable createSeekable(); protected abstract Seekable createSeekable();
@Test
public void testFail() { public void testFail() {
fail(); fail("Do not create stand-alone test classes based on this class. Instead, create an inner class and delegate to it.");
} }
@Test
public void testSeekable() { public void testSeekable() {
assertTrue(createSeekable() instanceof Seekable); assertTrue(createSeekable() instanceof Seekable);
} }
@@ -1,10 +1,14 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static org.junit.Assert.*;
/** /**
* SeekableInputStreamAbstractTestCase * SeekableInputStreamAbstractTestCase
* <p/> * <p/>
@@ -13,13 +17,8 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java#4 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java#4 $
*/ */
public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest { public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest {
public SeekableInputStreamAbstractTestCase(String name) {
super(name);
}
//// TODO: Figure out a better way of creating interface tests without duplicating code //// TODO: Figure out a better way of creating interface tests without duplicating code
final SeekableAbstractTestCase mSeekableTestCase = new SeekableAbstractTestCase() { final SeekableAbstractTestCase seekableTestCase = new SeekableAbstractTestCase() {
protected Seekable createSeekable() { protected Seekable createSeekable() {
return makeInputStream(); return makeInputStream();
} }
@@ -41,6 +40,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
protected abstract SeekableInputStream makeInputStream(InputStream pStream); protected abstract SeekableInputStream makeInputStream(InputStream pStream);
@Test
@Override @Override
public void testResetAfterReset() throws Exception { public void testResetAfterReset() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25)); InputStream input = makeInputStream(makeOrderedArray(25));
@@ -59,9 +59,9 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertTrue("Expected to read positive value", read >= 0); assertTrue("Expected to read positive value", read >= 0);
input.reset(); input.reset();
assertEquals("Expected value read differes from actual", read, input.read()); assertEquals("Expected value read differs from actual", read, input.read());
// Reset after read limit passed, may either throw exception, or reset to last mark // Reset after read limit passed, may either throw exception, or reset to last good mark
try { try {
input.reset(); input.reset();
assertEquals("Re-read of reset data should be first", 0, input.read()); assertEquals("Re-read of reset data should be first", 0, input.read());
@@ -71,10 +71,12 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
} }
} }
@Test
public void testSeekable() { public void testSeekable() {
mSeekableTestCase.testSeekable(); seekableTestCase.testSeekable();
} }
@Test
public void testFlushBeyondCurrentPos() throws Exception { public void testFlushBeyondCurrentPos() throws Exception {
SeekableInputStream seekable = makeInputStream(20); SeekableInputStream seekable = makeInputStream(20);
@@ -88,6 +90,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
} }
} }
@Test
public void testSeek() throws Exception { public void testSeek() throws Exception {
SeekableInputStream seekable = makeInputStream(55); SeekableInputStream seekable = makeInputStream(55);
int pos = 37; int pos = 37;
@@ -97,6 +100,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertEquals("Stream positon should match seeked position", pos, streamPos); assertEquals("Stream positon should match seeked position", pos, streamPos);
} }
@Test
public void testSeekFlush() throws Exception { public void testSeekFlush() throws Exception {
SeekableInputStream seekable = makeInputStream(133); SeekableInputStream seekable = makeInputStream(133);
int pos = 45; int pos = 45;
@@ -114,6 +118,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
} }
} }
@Test
public void testMarkFlushReset() throws Exception { public void testMarkFlushReset() throws Exception {
SeekableInputStream seekable = makeInputStream(77); SeekableInputStream seekable = makeInputStream(77);
@@ -134,6 +139,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertEquals(position, seekable.getStreamPosition()); assertEquals(position, seekable.getStreamPosition());
} }
@Test
public void testSeekSkipRead() throws Exception { public void testSeekSkipRead() throws Exception {
SeekableInputStream seekable = makeInputStream(133); SeekableInputStream seekable = makeInputStream(133);
int pos = 45; int pos = 45;
@@ -147,7 +153,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
} }
} }
public void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException { protected void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException {
System.out.println(); System.out.println();
pSeekable.seek(pStr.length()); pSeekable.seek(pStr.length());
FileUtil.read(pSeekable); FileUtil.read(pSeekable);
@@ -330,6 +336,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
} }
*/ */
@Test
public void testReadResetReadDirectBufferBug() throws IOException { public void testReadResetReadDirectBufferBug() throws IOException {
// Make sure we use the exact size of the buffer // Make sure we use the exact size of the buffer
final int size = 1024; final int size = 1024;
@@ -365,6 +372,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertTrue(rangeEquals(bytes, size, result, 0, size)); assertTrue(rangeEquals(bytes, size, result, 0, size));
} }
@Test
public void testReadAllByteValuesRegression() throws IOException { public void testReadAllByteValuesRegression() throws IOException {
final int size = 128; final int size = 128;
@@ -401,6 +409,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
} }
@Test
public void testCloseUnderlyingStream() throws IOException { public void testCloseUnderlyingStream() throws IOException {
final boolean[] closed = new boolean[1]; final boolean[] closed = new boolean[1];
@@ -476,5 +485,4 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
return true; return true;
} }
} }
@@ -1,10 +1,13 @@
package com.twelvemonkeys.io; package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
import org.junit.Test;
import java.io.Reader; import java.io.Reader;
import java.io.IOException; import java.io.IOException;
import static org.junit.Assert.*;
/** /**
* StringArrayReaderTestCase * StringArrayReaderTestCase
* <p/> * <p/>
@@ -28,6 +31,7 @@ public class StringArrayReaderTestCase extends ReaderAbstractTestCase {
return new StringArrayReader(input); return new StringArrayReader(input);
} }
@Test
public void testNullConstructor() { public void testNullConstructor() {
try { try {
new StringArrayReader(null); new StringArrayReader(null);
@@ -38,15 +42,15 @@ public class StringArrayReaderTestCase extends ReaderAbstractTestCase {
} }
} }
@Test
public void testEmptyArrayConstructor() throws IOException { public void testEmptyArrayConstructor() throws IOException {
Reader reader = new StringArrayReader(new String[0]); Reader reader = new StringArrayReader(new String[0]);
assertEquals(-1, reader.read()); assertEquals(-1, reader.read());
} }
@Test
public void testEmptyStringConstructor() throws IOException { public void testEmptyStringConstructor() throws IOException {
Reader reader = new StringArrayReader(new String[] {""}); Reader reader = new StringArrayReader(new String[] {""});
assertEquals(-1, reader.read()); assertEquals(-1, reader.read());
} }
} }
@@ -2,9 +2,12 @@ package com.twelvemonkeys.io.enc;
import com.twelvemonkeys.io.FileUtil; import com.twelvemonkeys.io.FileUtil;
import org.junit.Test;
import java.io.*; import java.io.*;
import static org.junit.Assert.*;
/** /**
* Base64DecoderTest * Base64DecoderTest
* <p/> * <p/>
@@ -22,6 +25,7 @@ public class Base64DecoderTestCase extends DecoderAbstractTestCase {
return new Base64Encoder(); return new Base64Encoder();
} }
@Test
public void testEmptyDecode2() throws IOException { public void testEmptyDecode2() throws IOException {
String data = ""; String data = "";
@@ -33,6 +37,7 @@ public class Base64DecoderTestCase extends DecoderAbstractTestCase {
assertEquals("Strings does not match", "", new String(bytes.toByteArray())); assertEquals("Strings does not match", "", new String(bytes.toByteArray()));
} }
@Test
public void testShortDecode() throws IOException { public void testShortDecode() throws IOException {
String data = "dGVzdA=="; String data = "dGVzdA==";
@@ -44,6 +49,7 @@ public class Base64DecoderTestCase extends DecoderAbstractTestCase {
assertEquals("Strings does not match", "test", new String(bytes.toByteArray())); assertEquals("Strings does not match", "test", new String(bytes.toByteArray()));
} }
@Test
public void testLongDecode() throws IOException { public void testLongDecode() throws IOException {
String data = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIEZ1" + String data = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIEZ1" +
"c2NlIGVzdC4gTW9yYmkgbHVjdHVzIGNvbnNlY3RldHVlciBqdXN0by4gVml2YW11cyBkYXBpYnVzIGxh" + "c2NlIGVzdC4gTW9yYmkgbHVjdHVzIGNvbnNlY3RldHVlciBqdXN0by4gVml2YW11cyBkYXBpYnVzIGxh" +
@@ -62,4 +68,4 @@ public class Base64DecoderTestCase extends DecoderAbstractTestCase {
"ullamcorper, nisi in dictum amet.", "ullamcorper, nisi in dictum amet.",
new String(bytes.toByteArray())); new String(bytes.toByteArray()));
} }
} }
@@ -1,7 +1,11 @@
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.io.enc;
import org.junit.Test;
import java.io.*; import java.io.*;
import static org.junit.Assert.*;
/** /**
* Base64EncoderTest * Base64EncoderTest
* <p/> * <p/>
@@ -19,6 +23,7 @@ public class Base64EncoderTestCase extends EncoderAbstractTestCase {
return new Base64Decoder(); return new Base64Decoder();
} }
@Test
public void testNegativeEncode() throws IOException { public void testNegativeEncode() throws IOException {
Encoder encoder = createEncoder(); Encoder encoder = createEncoder();
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -31,6 +36,7 @@ public class Base64EncoderTestCase extends EncoderAbstractTestCase {
} }
} }
@Test
public void testEmptyEncode() throws IOException { public void testEmptyEncode() throws IOException {
String data = ""; String data = "";
@@ -41,6 +47,7 @@ public class Base64EncoderTestCase extends EncoderAbstractTestCase {
assertEquals("Strings does not match", "", new String(bytes.toByteArray())); assertEquals("Strings does not match", "", new String(bytes.toByteArray()));
} }
@Test
public void testShortEncode() throws IOException { public void testShortEncode() throws IOException {
String data = "test"; String data = "test";
@@ -51,6 +58,7 @@ public class Base64EncoderTestCase extends EncoderAbstractTestCase {
assertEquals("Strings does not match", "dGVzdA==", new String(bytes.toByteArray())); assertEquals("Strings does not match", "dGVzdA==", new String(bytes.toByteArray()));
} }
@Test
public void testLongEncode() throws IOException { public void testLongEncode() throws IOException {
String data = "Lorem ipsum dolor sit amet, consectetuer adipiscing " + String data = "Lorem ipsum dolor sit amet, consectetuer adipiscing " +
"elit. Fusce est. Morbi luctus consectetuer justo. Vivamus " + "elit. Fusce est. Morbi luctus consectetuer justo. Vivamus " +
@@ -2,9 +2,11 @@ package com.twelvemonkeys.io.enc;
import com.twelvemonkeys.io.FileUtil; import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.ObjectAbstractTestCase; import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.*; import java.io.*;
import java.util.Arrays;
import static org.junit.Assert.*;
/** /**
* AbstractDecoderTest * AbstractDecoderTest
@@ -22,24 +24,22 @@ public abstract class DecoderAbstractTestCase extends ObjectAbstractTestCase {
return createDecoder(); return createDecoder();
} }
@Test(expected = NullPointerException.class)
public final void testNullDecode() throws IOException { public final void testNullDecode() throws IOException {
Decoder decoder = createDecoder(); Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[20]); ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[20]);
try { decoder.decode(bytes, null);
decoder.decode(bytes, null); fail("null should throw NullPointerException");
fail("null should throw NullPointerException");
}
catch (NullPointerException e) {
}
} }
@Test
public final void testEmptyDecode() throws IOException { public final void testEmptyDecode() throws IOException {
Decoder decoder = createDecoder(); Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]); ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]);
try { try {
int count = decoder.decode(bytes, new byte[2]); int count = decoder.decode(bytes, new byte[128]);
assertEquals("Should not be able to read any bytes", 0, count); assertEquals("Should not be able to read any bytes", 0, count);
} }
catch (EOFException allowed) { catch (EOFException allowed) {
@@ -63,26 +63,25 @@ public abstract class DecoderAbstractTestCase extends ObjectAbstractTestCase {
byte[] encoded = outBytes.toByteArray(); byte[] encoded = outBytes.toByteArray();
byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createDecoder())); byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createDecoder()));
assertTrue(Arrays.equals(data, decoded)); assertArrayEquals(String.format("Data %d", pLength), data, decoded);
InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createDecoder()); InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createDecoder());
outBytes = new ByteArrayOutputStream(); outBytes = new ByteArrayOutputStream();
/*
byte[] buffer = new byte[3];
for (int n = in.read(buffer); n > 0; n = in.read(buffer)) {
outBytes.write(buffer, 0, n);
}
*/
FileUtil.copy(in, outBytes); FileUtil.copy(in, outBytes);
outBytes.close(); outBytes.close();
in.close(); in.close();
decoded = outBytes.toByteArray(); decoded = outBytes.toByteArray();
assertTrue(Arrays.equals(data, decoded)); assertArrayEquals(String.format("Data %d", pLength), data, decoded);
} }
@Test
public final void testStreams() throws Exception { public final void testStreams() throws Exception {
for (int i = 0; i < 100; i++) { if (createCompatibleEncoder() == null) {
return;
}
for (int i = 1; i < 100; i++) {
try { try {
runStreamTest(i); runStreamTest(i);
} }
@@ -2,11 +2,14 @@ package com.twelvemonkeys.io.enc;
import com.twelvemonkeys.io.FileUtil; import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.ObjectAbstractTestCase; import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.*; import java.io.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Random; import java.util.Random;
import static org.junit.Assert.*;
/** /**
* AbstractEncoderTest * AbstractEncoderTest
* <p/> * <p/>
@@ -26,6 +29,7 @@ public abstract class EncoderAbstractTestCase extends ObjectAbstractTestCase {
return createEncoder(); return createEncoder();
} }
@Test
public final void testNullEncode() throws IOException { public final void testNullEncode() throws IOException {
Encoder encoder = createEncoder(); Encoder encoder = createEncoder();
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -79,6 +83,7 @@ public abstract class EncoderAbstractTestCase extends ObjectAbstractTestCase {
assertTrue(Arrays.equals(data, decoded)); assertTrue(Arrays.equals(data, decoded));
} }
@Test
public final void testStreams() throws Exception { public final void testStreams() throws Exception {
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
try { try {
@@ -1,10 +1,5 @@
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.io.enc;
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.Encoder;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
/** /**
* PackBitsDecoderTest * PackBitsDecoderTest
* <p/> * <p/>
@@ -1,6 +1,7 @@
package com.twelvemonkeys.io.ole2; package com.twelvemonkeys.io.ole2;
import junit.framework.TestCase; import com.twelvemonkeys.io.MemoryCacheSeekableStream;
import org.junit.Test;
import javax.imageio.stream.MemoryCacheImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream;
import java.io.File; import java.io.File;
@@ -9,6 +10,10 @@ import java.io.InputStream;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.SortedSet;
import java.util.TreeSet;
import static org.junit.Assert.*;
/** /**
* CompoundDocumentTestCase * CompoundDocumentTestCase
@@ -17,9 +22,89 @@ import java.nio.ByteOrder;
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTestCase.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/ole2/CompoundDocumentTestCase.java#1 $
*/ */
public class CompoundDocumentTestCase extends TestCase { public class CompoundDocumentTestCase {
private static final String SAMPLE_DATA = "/Thumbs-camera.db";
protected final CompoundDocument createTestDocument() throws IOException {
URL input = getClass().getResource(SAMPLE_DATA);
assertNotNull("Missing test resource!", input);
assertEquals("Test resource not a file:// resource", "file", input.getProtocol());
try {
return new CompoundDocument(new File(input.toURI()));
}
catch (URISyntaxException e) {
throw new AssertionError(e);
}
}
@Test
public void testRoot() throws IOException {
CompoundDocument document = createTestDocument();
Entry root = document.getRootEntry();
assertNotNull(root);
assertEquals("Root Entry", root.getName());
assertTrue(root.isRoot());
assertFalse(root.isFile());
assertFalse(root.isDirectory());
assertEquals(0, root.length());
assertNull(root.getInputStream());
}
@Test
public void testContents() throws IOException {
CompoundDocument document = createTestDocument();
Entry root = document.getRootEntry();
assertNotNull(root);
SortedSet<Entry> children = new TreeSet<Entry>(root.getChildEntries());
assertEquals(25, children.size());
// Weirdness in the file format, name is *written backwards* 1-24 + Catalog
for (String name : "1,2,3,4,5,6,7,8,9,01,02,11,12,21,22,31,32,41,42,51,61,71,81,91,Catalog".split(",")) {
assertEquals(name, children.first().getName());
children.remove(children.first());
}
}
@Test(expected = UnsupportedOperationException.class)
public void testChildEntriesUnmodifiable() throws IOException {
CompoundDocument document = createTestDocument();
Entry root = document.getRootEntry();
assertNotNull(root);
SortedSet<Entry> children = root.getChildEntries();
// Should not be allowed, as it modifies the internal structure
children.remove(children.first());
}
@Test
public void testReadThumbsCatalogFile() throws IOException {
CompoundDocument document = createTestDocument();
Entry root = document.getRootEntry();
assertNotNull(root);
assertEquals(25, root.getChildEntries().size());
Entry catalog = root.getChildEntry("Catalog");
assertNotNull(catalog);
assertNotNull("Input stream may not be null", catalog.getInputStream());
}
@Test
public void testReadCatalogInputStream() throws IOException { public void testReadCatalogInputStream() throws IOException {
InputStream input = getClass().getResourceAsStream("/Thumbs-camera.db"); InputStream input = getClass().getResourceAsStream(SAMPLE_DATA);
assertNotNull("Missing test resource!", input); assertNotNull("Missing test resource!", input);
@@ -33,8 +118,25 @@ public class CompoundDocumentTestCase extends TestCase {
assertNotNull("Input stream may not be null", catalog.getInputStream()); assertNotNull("Input stream may not be null", catalog.getInputStream());
} }
@Test
public void testReadCatalogSeekableStream() throws IOException {
InputStream input = getClass().getResourceAsStream(SAMPLE_DATA);
assertNotNull("Missing test resource!", input);
CompoundDocument document = new CompoundDocument(new MemoryCacheSeekableStream(input));
Entry root = document.getRootEntry();
assertNotNull(root);
assertEquals(25, root.getChildEntries().size());
Entry catalog = root.getChildEntry("Catalog");
assertNotNull(catalog);
assertNotNull("Input stream may not be null", catalog.getInputStream());
}
@Test
public void testReadCatalogImageInputStream() throws IOException { public void testReadCatalogImageInputStream() throws IOException {
InputStream input = getClass().getResourceAsStream("/Thumbs-camera.db"); InputStream input = getClass().getResourceAsStream(SAMPLE_DATA);
assertNotNull("Missing test resource!", input); assertNotNull("Missing test resource!", input);
@@ -53,25 +155,4 @@ public class CompoundDocumentTestCase extends TestCase {
assertNotNull(catalog); assertNotNull(catalog);
assertNotNull("Input stream may not be null", catalog.getInputStream()); assertNotNull("Input stream may not be null", catalog.getInputStream());
} }
public void testReadThumbsCatalogFile() throws IOException, URISyntaxException {
URL input = getClass().getResource("/Thumbs-camera.db");
assertNotNull("Missing test resource!", input);
assertEquals("Test resource not a file:// resource", "file", input.getProtocol());
File file = new File(input.toURI());
CompoundDocument document = new CompoundDocument(file);
Entry root = document.getRootEntry();
assertNotNull(root);
assertEquals(25, root.getChildEntries().size());
Entry catalog = root.getChildEntry("Catalog");
assertNotNull(catalog);
assertNotNull("Input stream may not be null", catalog.getInputStream());
}
} }
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2011, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.ole2;
import com.twelvemonkeys.io.*;
import org.junit.Test;
import java.io.ByteArrayInputStream;
/**
* CompoundDocument_SeekableLittleEndianDataInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CompoundDocument_SeekableLittleEndianDataInputStreamTestCase.java,v 1.0 18.10.11 16:35 haraldk Exp$
*/
public class CompoundDocument_SeekableLittleEndianDataInputStreamTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest {
private final SeekableInterfaceTest seekableTest = new SeekableAbstractTestCase() {
@Override
protected Seekable createSeekable() {
return (Seekable) makeInputStream();
}
};
@Override
protected CompoundDocument.SeekableLittleEndianDataInputStream makeInputStream(byte[] pBytes) {
return new CompoundDocument.SeekableLittleEndianDataInputStream(new MemoryCacheSeekableStream(new ByteArrayInputStream(pBytes)));
}
@Test
public void testSeekable() {
seekableTest.testSeekable();
}
}
@@ -0,0 +1,207 @@
/*
* Copyright (c) 2011, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.ole2;
import com.twelvemonkeys.io.InputStreamAbstractTestCase;
import com.twelvemonkeys.io.LittleEndianDataOutputStream;
import com.twelvemonkeys.io.MemoryCacheSeekableStream;
import com.twelvemonkeys.io.SeekableInputStream;
import org.junit.Test;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* CompoundDocument_StreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CompoundDocument_StreamTestCase.java,v 1.0 13.10.11 12:01 haraldk Exp$
*/
//@Ignore("Need proper in-memory creation of CompoundDocuments")
public class CompoundDocument_StreamTestCase extends InputStreamAbstractTestCase {
private static final String SAMPLE_DATA = "/Thumbs-camera.db";
protected final CompoundDocument createTestDocument() throws IOException {
URL input = getClass().getResource(SAMPLE_DATA);
assertNotNull("Missing test resource!", input);
assertEquals("Test resource not a file:// resource", "file", input.getProtocol());
try {
return new CompoundDocument(new File(input.toURI()));
}
catch (URISyntaxException e) {
throw new AssertionError(e);
}
}
private SeekableInputStream createRealInputStream() {
try {
Entry first = createTestDocument().getRootEntry().getChildEntries().first();
assertNotNull(first);
return first.getInputStream();
}
catch (IOException e) {
throw new AssertionError(e);
}
}
@Override
protected InputStream makeInputStream(byte[] data) {
try {
// Set up fake document
ByteArrayOutputStream stream = new ByteArrayOutputStream();
LittleEndianDataOutputStream dataStream = new LittleEndianDataOutputStream(stream);
dataStream.write(CompoundDocument.MAGIC); // 8 bytes magic
dataStream.write(new byte[16]); // UUID 16 bytes, all zero
dataStream.write(new byte[]{0x3E, 0, 3, 0}); // version (62), rev (3)
// 28
dataStream.write(new byte[]{(byte) 0xfe, (byte) 0xff}); // Byte order
dataStream.write(new byte[]{9, 0, 6, 0}); // Sector size (1 << x), short sector size
dataStream.write(new byte[10]); // Reserved 10 bytes
// 44
dataStream.writeInt(1); // SAT size (1)
dataStream.writeInt(1); // Directory SId
dataStream.write(new byte[4]); // Reserved 4 bytes
// 56
dataStream.writeInt(4096); // Min stream size (4096)
dataStream.writeInt(3); // Short SAT SId
dataStream.writeInt(1); // Short SAT size
dataStream.writeInt(-2); // Master SAT SId (-2, end of chain)
// 72
dataStream.writeInt(0); // Master SAT size
dataStream.writeInt(0); // Master SAT entry 0 (0)
dataStream.writeInt(128); // Master SAT entry 1 (128)
// 84
dataStream.write(createPad(428, (byte) -1)); // Pad (until 512 bytes)
// 512 -- end header
// SId 0
// SAT
dataStream.writeInt(-3); // SAT entry 0 (SAT)
dataStream.writeInt(-2); // SAT entry 1 (EOS)
dataStream.write(createPad(512 - 8, (byte) -1)); // Pad (until 512 bytes)
// 1024 -- end SAT
// SId 1
// Directory
// 64 bytes UTF16LE ("Root Entry" + null-termination)
byte[] name = "Root Entry".getBytes(Charset.forName("UTF-16LE"));
dataStream.write(name); // Name
dataStream.write(createPad(64 - name.length, (byte) 0)); // Pad name to 64 bytes
dataStream.writeShort((short) (name.length + 2)); // 2 byte length (incl null-term)
dataStream.write(new byte[]{5, 0}); // type (root), node color
dataStream.writeInt(-1); // prevDId, -1
dataStream.writeInt(-1); // nextDId, -1
dataStream.writeInt(1); // rootNodeDId
dataStream.write(createPad(36, (byte) 0)); // UID + flags + 2 x long timestamps
dataStream.writeInt(2); // Start SId
dataStream.writeInt(8); // Stream size
dataStream.writeInt(0); // Reserved
name = "data".getBytes(Charset.forName("UTF-16LE"));
dataStream.write(name); // Name
dataStream.write(createPad(64 - name.length, (byte) 0)); // Pad name to 64 bytes
dataStream.writeShort((short) (name.length + 2)); // 2 byte length (incl null-term)
dataStream.write(new byte[]{2, 0}); // type (user stream), node color
dataStream.writeInt(-1); // prevDId, -1
dataStream.writeInt(-1); // nextDId, -1
dataStream.writeInt(-1); // rootNodeDId
dataStream.write(createPad(36, (byte) 0)); // UID + flags + 2 x long timestamps
dataStream.writeInt(0); // Start SId
dataStream.writeInt(data.length); // Stream size
dataStream.writeInt(0); // Reserved
dataStream.write(createPad(512 - 256, (byte) -1)); // Pad to full sector (512 bytes)
// 1536 -- end Directory
// SId 2
// Data
dataStream.write(data); // The data
dataStream.write(createPad(512 - data.length, (byte) -1)); // Pad to full sector (512 bytes)
// 2048 -- end Data
// SId 3
// Short SAT
dataStream.writeInt(2); // Short SAT entry 0
dataStream.writeInt(-2); // Short SAT entry 1 (EOS)
dataStream.write(createPad(512 - 8, (byte) -1)); // Pad to full sector (512 bytes)
// 2560 -- end Short SAT
InputStream input = new ByteArrayInputStream(stream.toByteArray());
CompoundDocument document = new CompoundDocument(new MemoryCacheSeekableStream(input));
Entry entry = document.getRootEntry().getChildEntries().first();
return entry.getInputStream();
}
catch (IOException e) {
throw new AssertionError(e);
}
}
private byte[] createPad(final int length, final byte val) {
byte[] pad = new byte[length];
Arrays.fill(pad, val);
return pad;
}
// @Ignore
@Test
public void testDev() throws IOException {
InputStream stream = makeInputStream(makeOrderedArray(32));
int read;
int count = 0;
while ((read = stream.read()) >= 0) {
// System.out.printf("read %02d: 0x%02x%n", count, read & 0xFF);
assertEquals(count, read);
count++;
}
assertFalse("Short stream", count < 32);
assertFalse("Stream overrun", count > 32);
}
@Test
public void testInputStreamSkip() throws IOException {
InputStream stream = makeInputStream();
// BUGFIX: Would skip and return 0 for first skip
assertTrue(stream.skip(10) > 0);
}
}
@@ -276,19 +276,19 @@ public final class BeanUtil {
} }
try { try {
// If this does not throw an excption, it works // If this does not throw an exception, it works
method = pObject.getClass().getMethod(pName, pParams); method = pObject.getClass().getMethod(pName, pParams);
} }
catch (Throwable t) { catch (Throwable t) {
// Ignore // Ignore
} }
// 2: Try any supertypes of paramType, to see if we have a match // 2: Try any super-types of paramType, to see if we have a match
if (method == null) { if (method == null) {
while ((paramType = paramType.getSuperclass()) != null) { while ((paramType = paramType.getSuperclass()) != null) {
pParams[0] = paramType; pParams[0] = paramType;
try { try {
// If this does not throw an excption, it works // If this does not throw an exception, it works
method = pObject.getClass().getMethod(pName, pParams); method = pObject.getClass().getMethod(pName, pParams);
} }
catch (Throwable t) { catch (Throwable t) {
@@ -365,6 +365,9 @@ public final class BeanUtil {
} }
} }
// TODO: Convert value to single-value array if needed
// TODO: Convert CSV String to string array (or potentially any type of array)
// TODO: Convert other types // TODO: Convert other types
if (pValue instanceof String) { if (pValue instanceof String) {
Converter converter = Converter.getInstance(); Converter converter = Converter.getInstance();
@@ -596,8 +599,7 @@ public final class BeanUtil {
catch (NoSuchMethodException ignore) { catch (NoSuchMethodException ignore) {
// If invocation failed, convert lisp-style and try again // If invocation failed, convert lisp-style and try again
if (pLispToCamel && property.indexOf('-') > 0) { if (pLispToCamel && property.indexOf('-') > 0) {
setPropertyValue(pBean, StringUtil.lispToCamel(property, false), setPropertyValue(pBean, StringUtil.lispToCamel(property, false), entry.getValue());
entry.getValue());
} }
} }
} }
@@ -30,9 +30,6 @@ package com.twelvemonkeys.lang;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
/** /**
* A utility class with useful date manipulation methods and constants. * A utility class with useful date manipulation methods and constants.
@@ -191,20 +188,4 @@ public final class DateUtil {
int offset = pTimeZone.getOffset(pTime); int offset = pTimeZone.getOffset(pTime);
return (((pTime + offset) / DAY) * DAY) - offset; return (((pTime + offset) / DAY) * DAY) - offset;
} }
public static void main(String[] pArgs) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH.mm.ss S");
long time = pArgs.length > 0 ? format.parse(pArgs[0]).getTime() : System.currentTimeMillis();
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToSecond(time);
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToMinute(time);
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToHour(time);
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToDay(time);
System.out.println(time + ": " + format.format(new Date(time)));
}
} }
@@ -1,129 +0,0 @@
package com.twelvemonkeys.lang;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
/**
* ExceptionUtil
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ExceptionUtil.java#2 $
*/
public final class ExceptionUtil {
/*public*/ static void launder(final Throwable pThrowable, Class<? extends Throwable>... pExpectedTypes) {
if (pThrowable instanceof Error) {
throw (Error) pThrowable;
}
if (pThrowable instanceof RuntimeException) {
throw (RuntimeException) pThrowable;
}
for (Class<? extends Throwable> expectedType : pExpectedTypes) {
if (expectedType.isInstance(pThrowable)) {
throw new RuntimeException(pThrowable);
}
}
throw new UndeclaredThrowableException(pThrowable);
}
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
throw (T) pThrowable;
}
public static void throwUnchecked(final Throwable pThrowable) {
throwAs(RuntimeException.class, pThrowable);
}
/*public*/ static void handle(final Throwable pThrowable, final ThrowableHandler<? extends Throwable>... pHandler) {
handleImpl(pThrowable, pHandler);
}
@SuppressWarnings({"unchecked"})
private static <T extends Throwable> void handleImpl(final Throwable pThrowable, final ThrowableHandler<T>... pHandler) {
// TODO: Sort more specific throwable handlers before less specific?
for (ThrowableHandler<T> handler : pHandler) {
if (handler.handles(pThrowable)) {
handler.handle((T) pThrowable);
return;
}
}
throwUnchecked(pThrowable);
}
public static abstract class ThrowableHandler<T extends Throwable> {
private Class<? extends T>[] mThrowables;
protected ThrowableHandler(final Class<? extends T>... pThrowables) {
// TODO: Assert not null
mThrowables = pThrowables.clone();
}
final public boolean handles(final Throwable pThrowable) {
for (Class<? extends T> throwable : mThrowables) {
if (throwable.isAssignableFrom(pThrowable.getClass())) {
return true;
}
}
return false;
}
public abstract void handle(T pThrowable);
}
@SuppressWarnings({"InfiniteLoopStatement"})
public static void main(String[] pArgs) {
while (true) {
foo();
}
}
private static void foo() {
try {
bar();
}
catch (Throwable t) {
handle(t,
new ThrowableHandler<IOException>(IOException.class) {
public void handle(final IOException pThrowable) {
System.out.println("IOException: " + pThrowable + " handled");
}
},
new ThrowableHandler<Exception>(SQLException.class, NumberFormatException.class) {
public void handle(final Exception pThrowable) {
System.out.println("Exception: " + pThrowable + " handled");
}
},
new ThrowableHandler<Throwable>(Throwable.class) {
public void handle(final Throwable pThrowable) {
System.err.println("Generic throwable: " + pThrowable + " NOT handled");
throwUnchecked(pThrowable);
}
}
);
}
}
private static void bar() {
baz();
}
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
private static void baz() {
double random = Math.random();
if (random < (1.0 / 3.0)) {
throwUnchecked(new FileNotFoundException("FNF Boo"));
}
if (random < (2.0 / 3.0)) {
throwUnchecked(new SQLException("SQL Boo"));
}
else {
throwUnchecked(new Exception("Some Boo"));
}
}
}
@@ -43,36 +43,6 @@ public final class MathUtil {
/** */ /** */
private MathUtil() { private MathUtil() {
} }
/**
* Converts an angle measured in degrees to the equivalent angle measured
* in radians.
* This method is a replacement for the Math.toRadians() method in
* Java versions < 1.2 (typically for Applets).
*
* @param pAngDeg an angle, in degrees
* @return the measurement of the angle {@code angdeg} in radians.
*
* @see java.lang.Math#toRadians(double)
*/
public static double toRadians(final double pAngDeg) {
return pAngDeg * Math.PI / 180.0;
}
/**
* Converts an angle measured in radians to the equivalent angle measured
* in degrees.
* This method is a replacement for the Math.toDegrees() method in
* Java versions < 1.2 (typically for Applets).
*
* @param pAngRad an angle, in radians
* @return the measurement of the angle {@code angrad} in degrees.
*
* @see java.lang.Math#toDegrees(double)
*/
public static double toDegrees(final double pAngRad) {
return pAngRad * 180.0 / Math.PI;
}
/** /**
* Returns the natural logarithm (base <I>e</I>) of a double value. * Returns the natural logarithm (base <I>e</I>) of a double value.
@@ -135,7 +105,7 @@ public final class MathUtil {
* @see Math#abs(long) * @see Math#abs(long)
* @see Long#MIN_VALUE * @see Long#MIN_VALUE
* *
* @param pNumber * @param pNumber a number
* @return the absolute value of {@code pNumber} * @return the absolute value of {@code pNumber}
* *
* @throws ArithmeticException if {@code pNumber == Long.MIN_VALUE} * @throws ArithmeticException if {@code pNumber == Long.MIN_VALUE}
@@ -144,6 +114,7 @@ public final class MathUtil {
if (pNumber == Long.MIN_VALUE) { if (pNumber == Long.MIN_VALUE) {
throw new ArithmeticException("long overflow: 9223372036854775808"); throw new ArithmeticException("long overflow: 9223372036854775808");
} }
return (pNumber < 0) ? -pNumber : pNumber; return (pNumber < 0) ? -pNumber : pNumber;
} }
@@ -154,7 +125,7 @@ public final class MathUtil {
* @see Math#abs(int) * @see Math#abs(int)
* @see Integer#MIN_VALUE * @see Integer#MIN_VALUE
* *
* @param pNumber * @param pNumber a number
* @return the absolute value of {@code pNumber} * @return the absolute value of {@code pNumber}
* *
* @throws ArithmeticException if {@code pNumber == Integer.MIN_VALUE} * @throws ArithmeticException if {@code pNumber == Integer.MIN_VALUE}
@@ -163,6 +134,7 @@ public final class MathUtil {
if (pNumber == Integer.MIN_VALUE) { if (pNumber == Integer.MIN_VALUE) {
throw new ArithmeticException("int overflow: 2147483648"); throw new ArithmeticException("int overflow: 2147483648");
} }
return (pNumber < 0) ? -pNumber : pNumber; return (pNumber < 0) ? -pNumber : pNumber;
} }
} }
@@ -28,6 +28,8 @@
package com.twelvemonkeys.lang; package com.twelvemonkeys.lang;
import java.util.Properties;
/** /**
* Platform * Platform
* *
@@ -39,40 +41,46 @@ public final class Platform {
/** /**
* Normalized operating system constant * Normalized operating system constant
*/ */
final OperatingSystem mOS; final OperatingSystem os;
/** /**
* Unormalized operating system version constant (for completeness) * Unnormalized operating system version constant (for completeness)
*/ */
final String mVersion; final String version;
/** /**
* Normalized system architecture constant * Normalized system architecture constant
*/ */
final Architecture mArchitecture; final Architecture architecture;
static final private Platform INSTANCE = new Platform(); static final private Platform INSTANCE = new Platform();
private Platform() { private Platform() {
mOS = normalizeOperatingSystem(); this(System.getProperties());
mVersion = System.getProperty("os.version");
mArchitecture = normalizeArchitecture(mOS);
} }
private static OperatingSystem normalizeOperatingSystem() { Platform(final Properties properties) {
String os = System.getProperty("os.name"); os = normalizeOperatingSystem(properties.getProperty("os.name"));
version = properties.getProperty("os.version");
architecture = normalizeArchitecture(os, properties.getProperty("os.arch"));
}
static OperatingSystem normalizeOperatingSystem(final String osName) {
String os = osName;
if (os == null) { if (os == null) {
throw new IllegalStateException("System property \"os.name\" == null"); throw new IllegalStateException("System property \"os.name\" == null");
} }
os = os.toLowerCase(); os = os.toLowerCase();
if (os.startsWith("windows")) { if (os.startsWith("windows")) {
return OperatingSystem.Windows; return OperatingSystem.Windows;
} }
else if (os.startsWith("linux")) { else if (os.startsWith("linux")) {
return OperatingSystem.Linux; return OperatingSystem.Linux;
} }
else if (os.startsWith("mac os")) { else if (os.startsWith("mac os") || os.startsWith("darwin")) {
return OperatingSystem.MacOS; return OperatingSystem.MacOS;
} }
else if (os.startsWith("solaris") || os.startsWith("sunos")) { else if (os.startsWith("solaris") || os.startsWith("sunos")) {
@@ -82,15 +90,16 @@ public final class Platform {
return OperatingSystem.Unknown; return OperatingSystem.Unknown;
} }
private static Architecture normalizeArchitecture(final OperatingSystem pOsName) { static Architecture normalizeArchitecture(final OperatingSystem pOsName, final String osArch) {
String arch = System.getProperty("os.arch"); String arch = osArch;
if (arch == null) { if (arch == null) {
throw new IllegalStateException("System property \"os.arch\" == null"); throw new IllegalStateException("System property \"os.arch\" == null");
} }
arch = arch.toLowerCase(); arch = arch.toLowerCase();
if (pOsName == OperatingSystem.Windows
&& (arch.startsWith("x86") || arch.startsWith("i386"))) { if (pOsName == OperatingSystem.Windows && (arch.startsWith("x86") || arch.startsWith("i386"))) {
return Architecture.X86; return Architecture.X86;
// TODO: 64 bit // TODO: 64 bit
} }
@@ -101,6 +110,9 @@ public final class Platform {
else if (arch.startsWith("i686")) { else if (arch.startsWith("i686")) {
return Architecture.I686; return Architecture.I686;
} }
else if (arch.startsWith("power") || arch.startsWith("ppc")) {
return Architecture.PPC;
}
// TODO: More Linux options? // TODO: More Linux options?
// TODO: 64 bit // TODO: 64 bit
} }
@@ -108,9 +120,13 @@ public final class Platform {
if (arch.startsWith("power") || arch.startsWith("ppc")) { if (arch.startsWith("power") || arch.startsWith("ppc")) {
return Architecture.PPC; return Architecture.PPC;
} }
else if (arch.startsWith("i386")) { else if (arch.startsWith("x86")) {
return Architecture.I386; return Architecture.X86;
} }
else if (arch.startsWith("i386")) {
return Architecture.X86;
}
// TODO: 64 bit
} }
else if (pOsName == OperatingSystem.Solaris) { else if (pOsName == OperatingSystem.Solaris) {
if (arch.startsWith("sparc")) { if (arch.startsWith("sparc")) {
@@ -138,21 +154,21 @@ public final class Platform {
* @return this platform's OS. * @return this platform's OS.
*/ */
public OperatingSystem getOS() { public OperatingSystem getOS() {
return mOS; return os;
} }
/** /**
* @return this platform's OS version. * @return this platform's OS version.
*/ */
public String getVersion() { public String getVersion() {
return mVersion; return version;
} }
/** /**
* @return this platform's architecture. * @return this platform's architecture.
*/ */
public Architecture getArchitecture() { public Architecture getArchitecture() {
return mArchitecture; return architecture;
} }
/** /**
@@ -160,7 +176,7 @@ public final class Platform {
* @return the current {@code OperatingSystem}. * @return the current {@code OperatingSystem}.
*/ */
public static OperatingSystem os() { public static OperatingSystem os() {
return INSTANCE.mOS; return INSTANCE.os;
} }
/** /**
@@ -168,7 +184,7 @@ public final class Platform {
* @return the current OS version. * @return the current OS version.
*/ */
public static String version() { public static String version() {
return INSTANCE.mVersion; return INSTANCE.version;
} }
/** /**
@@ -176,7 +192,7 @@ public final class Platform {
* @return the current {@code Architecture}. * @return the current {@code Architecture}.
*/ */
public static Architecture arch() { public static Architecture arch() {
return INSTANCE.mArchitecture; return INSTANCE.architecture;
} }
/** /**
@@ -197,14 +213,14 @@ public final class Platform {
Unknown(System.getProperty("os.arch")); Unknown(System.getProperty("os.arch"));
final String mName;// for debug only final String name;// for debug only
private Architecture(String pName) { private Architecture(String pName) {
mName = pName; name = pName;
} }
public String toString() { public String toString() {
return mName; return name;
} }
} }
@@ -223,22 +239,26 @@ public final class Platform {
Solaris("Solaris", "sun"), Solaris("Solaris", "sun"),
MacOS("Mac OS", "osx"), MacOS("Mac OS", "osx"),
Unknown(System.getProperty("os.name"), ""); Unknown(System.getProperty("os.name"), null);
final String mId; final String id;
final String mName;// for debug only final String name;// for debug only
private OperatingSystem(String pName, String pId) { private OperatingSystem(String pName, String pId) {
mName = pName; name = pName;
mId = pId; id = pId != null ? pId : pName.toLowerCase();
} }
public String getName() { public String getName() {
return mName; return name;
}
public String id() {
return id;
} }
public String toString() { public String toString() {
return mId; return String.format("%s (%s)", id, name);
} }
} }
} }
@@ -77,7 +77,7 @@ public final class StringUtil {
} }
/** /**
* Constructs a new {@link String} by decoding the specified subarray of bytes using the specified charset. * Constructs a new {@link String} by decoding the specified sub array of bytes using the specified charset.
* Replacement for {@link String#String(byte[], int, int, String) new String(byte[], int, int, String)}, that does * Replacement for {@link String#String(byte[], int, int, String) new String(byte[], int, int, String)}, that does
* not throw the checked {@link UnsupportedEncodingException}, * not throw the checked {@link UnsupportedEncodingException},
* but instead the unchecked {@link UnsupportedCharsetException} if the character set is not supported. * but instead the unchecked {@link UnsupportedCharsetException} if the character set is not supported.
@@ -1580,7 +1580,7 @@ public final class StringUtil {
* Converts a string array to a string separated by the given delimiter. * Converts a string array to a string separated by the given delimiter.
* *
* @param pStringArray the string array * @param pStringArray the string array
* @param pDelimiterString the delimter string * @param pDelimiterString the delimiter string
* @return string of delimiter separated values * @return string of delimiter separated values
* @throws IllegalArgumentException if {@code pDelimiterString == null} * @throws IllegalArgumentException if {@code pDelimiterString == null}
*/ */
@@ -81,8 +81,7 @@ public final class SystemUtil {
* *
* @return an input stream reading from the resource * @return an input stream reading from the resource
*/ */
private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName, private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName, boolean pGuessSuffix) {
boolean pGuessSuffix) {
InputStream is; InputStream is;
if (!pGuessSuffix) { if (!pGuessSuffix) {
@@ -122,8 +121,7 @@ public final class SystemUtil {
* *
* @return an input stream reading from the resource * @return an input stream reading from the resource
*/ */
private static InputStream getFileAsStream(String pName, private static InputStream getFileAsStream(String pName, boolean pGuessSuffix) {
boolean pGuessSuffix) {
InputStream is = null; InputStream is = null;
File propertiesFile; File propertiesFile;
@@ -206,8 +204,7 @@ public final class SystemUtil {
* @todo Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html * @todo Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
* @todo Consider using Context Classloader instead? * @todo Consider using Context Classloader instead?
*/ */
public static Properties loadProperties(Class pClass, String pName) public static Properties loadProperties(Class pClass, String pName) throws IOException
throws IOException
{ {
// Convert to name the classloader understands // Convert to name the classloader understands
String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/'); String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/');
@@ -219,8 +216,7 @@ public final class SystemUtil {
// TODO: WHAT IF MULTIPLE RESOURCES EXISTS?! // TODO: WHAT IF MULTIPLE RESOURCES EXISTS?!
// Try loading resource through the current class' classloader // Try loading resource through the current class' classloader
if (pClass != null if (pClass != null && (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) {
&& (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) {
//&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) { //&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) {
// Nothing to do // Nothing to do
//System.out.println(((is instanceof XMLPropertiesInputStream) ? //System.out.println(((is instanceof XMLPropertiesInputStream) ?
@@ -228,9 +224,8 @@ public final class SystemUtil {
// + " from Class' ClassLoader"); // + " from Class' ClassLoader");
} }
// If that fails, try the system classloader // If that fails, try the system classloader
else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(), else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(), name, guessSuffix)) != null) {
name, guessSuffix)) != null) { //else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) {
//else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) {
// Nothing to do // Nothing to do
//System.out.println(((is instanceof XMLPropertiesInputStream) ? //System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties") // "XML-properties" : "Normal .properties")
@@ -682,8 +677,8 @@ public final class SystemUtil {
} }
private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException { private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException {
// NOTE: We need the context classloader, as SystemUtil's // NOTE: We need the context class loader, as SystemUtil's
// classloader may have a totally different classloader than // class loader may have a totally different class loader than
// the original caller class (as in Class.forName(cn, false, null)). // the original caller class (as in Class.forName(cn, false, null)).
ClassLoader loader = pLoader != null ? pLoader : ClassLoader loader = pLoader != null ? pLoader :
Thread.currentThread().getContextClassLoader(); Thread.currentThread().getContextClassLoader();
@@ -9,13 +9,15 @@ import java.util.Map;
* <p/> * <p/>
* Uses type parameterized return values, thus making it possible to check * Uses type parameterized return values, thus making it possible to check
* constructor arguments before * constructor arguments before
* they are passed on to {@code super} or {@code this} type constructors. * they are passed on to {@code super} or {@code this} type constructors.
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Validate.java#1 $ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Validate.java#1 $
*/ */
public final class Validate { public final class Validate {
// TODO: Make it possible to throw IllegalStateException instead of IllegalArgumentException?
private static final String UNSPECIFIED_PARAM_NAME = "method parameter"; private static final String UNSPECIFIED_PARAM_NAME = "method parameter";
private Validate() {} private Validate() {}
@@ -30,23 +32,34 @@ public final class Validate {
if (pParameter == null) { if (pParameter == null) {
throw new IllegalArgumentException(String.format("%s may not be null", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName)); throw new IllegalArgumentException(String.format("%s may not be null", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
} }
return pParameter; return pParameter;
} }
// Not empty... // Not empty
public static <T extends CharSequence> T notEmpty(final T pParameter) { public static <T extends CharSequence> T notEmpty(final T pParameter) {
return notEmpty(pParameter, null); return notEmpty(pParameter, null);
} }
public static <T extends CharSequence> T notEmpty(final T pParameter, final String pParamName) { public static <T extends CharSequence> T notEmpty(final T pParameter, final String pParamName) {
if (pParameter == null || pParameter.length() == 0) { if (pParameter == null || pParameter.length() == 0 || isOnlyWhiteSpace(pParameter)) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName)); throw new IllegalArgumentException(String.format("%s may not be blank", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
} }
return pParameter; return pParameter;
} }
private static <T extends CharSequence> boolean isOnlyWhiteSpace(T pParameter) {
for (int i = 0; i < pParameter.length(); i++) {
if (!Character.isWhitespace(pParameter.charAt(i))) {
return false;
}
}
return true;
}
public static <T> T[] notEmpty(final T[] pParameter) { public static <T> T[] notEmpty(final T[] pParameter) {
return notEmpty(pParameter, null); return notEmpty(pParameter, null);
} }
@@ -90,7 +103,7 @@ public final class Validate {
} }
public static <T> T[] noNullElements(final T[] pParameter, final String pParamName) { public static <T> T[] noNullElements(final T[] pParameter, final String pParamName) {
noNullElements(Arrays.asList(pParameter), pParamName); noNullElements(pParameter == null ? null : Arrays.asList(pParameter), pParamName);
return pParameter; return pParameter;
} }
@@ -99,6 +112,8 @@ public final class Validate {
} }
public static <T> Collection<T> noNullElements(final Collection<T> pParameter, final String pParamName) { public static <T> Collection<T> noNullElements(final Collection<T> pParameter, final String pParamName) {
notNull(pParameter, pParamName);
for (T element : pParameter) { for (T element : pParameter) {
if (element == null) { if (element == null) {
throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName)); throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
@@ -108,17 +123,49 @@ public final class Validate {
return pParameter; return pParameter;
} }
public static <K, V> Map<K, V> noNullElements(final Map<K, V> pParameter) { public static <K, V> Map<K, V> noNullValues(final Map<K, V> pParameter) {
return noNullElements(pParameter, null); return noNullValues(pParameter, null);
} }
public static <K, V> Map<K, V> noNullElements(final Map<K, V> pParameter, final String pParamName) { public static <K, V> Map<K, V> noNullValues(final Map<K, V> pParameter, final String pParamName) {
for (V element : pParameter.values()) { notNull(pParameter, pParamName);
if (element == null) {
throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName)); for (V value : pParameter.values()) {
if (value == null) {
throw new IllegalArgumentException(String.format("%s may not contain null values", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
} }
} }
return pParameter; return pParameter;
} }
public static <K, V> Map<K, V> noNullKeys(final Map<K, V> pParameter) {
return noNullKeys(pParameter, null);
}
public static <K, V> Map<K, V> noNullKeys(final Map<K, V> pParameter, final String pParamName) {
notNull(pParameter, pParamName);
for (K key : pParameter.keySet()) {
if (key == null) {
throw new IllegalArgumentException(String.format("%s may not contain null keys", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
}
return pParameter;
}
// Is true
public static boolean isTrue(final boolean pExpression, final String pMessage) {
return isTrue(pExpression, pExpression, pMessage);
}
public static <T> T isTrue(final boolean condition, final T value, final String message) {
if (!condition) {
throw new IllegalArgumentException(String.format(message == null ? "expression may not be %s" : message, value));
}
return value;
}
} }
@@ -40,12 +40,12 @@ import java.io.Serializable;
*/ */
// TODO: The generics in this class looks suspicious.. // TODO: The generics in this class looks suspicious..
abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Serializable, Cloneable { abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Serializable, Cloneable {
protected Map<K, Entry<K, V>> mEntries; protected Map<K, Entry<K, V>> entries;
protected transient volatile int mModCount; protected transient volatile int modCount;
private transient volatile Set<Entry<K, V>> mEntrySet = null; private transient volatile Set<Entry<K, V>> entrySet = null;
private transient volatile Set<K> mKeySet = null; private transient volatile Set<K> keySet = null;
private transient volatile Collection<V> mValues = null; private transient volatile Collection<V> values = null;
/** /**
* Creates a {@code Map} backed by a {@code HashMap}. * Creates a {@code Map} backed by a {@code HashMap}.
@@ -104,7 +104,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
throw new IllegalArgumentException("backing must be empty"); throw new IllegalArgumentException("backing must be empty");
} }
mEntries = pBacking; this.entries = pBacking;
init(); init();
if (pContents != null) { if (pContents != null) {
@@ -125,21 +125,21 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
} }
public int size() { public int size() {
return mEntries.size(); return entries.size();
} }
public void clear() { public void clear() {
mEntries.clear(); entries.clear();
mModCount++; modCount++;
init(); init();
} }
public boolean isEmpty() { public boolean isEmpty() {
return mEntries.isEmpty(); return entries.isEmpty();
} }
public boolean containsKey(Object pKey) { public boolean containsKey(Object pKey) {
return mEntries.containsKey(pKey); return entries.containsKey(pKey);
} }
/** /**
@@ -166,18 +166,18 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
} }
public Collection<V> values() { public Collection<V> values() {
Collection<V> values = mValues; Collection<V> values = this.values;
return values != null ? values : (mValues = new Values()); return values != null ? values : (this.values = new Values());
} }
public Set<Entry<K, V>> entrySet() { public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> es = mEntrySet; Set<Entry<K, V>> es = entrySet;
return es != null ? es : (mEntrySet = new EntrySet()); return es != null ? es : (entrySet = new EntrySet());
} }
public Set<K> keySet() { public Set<K> keySet() {
Set<K> ks = mKeySet; Set<K> ks = keySet;
return ks != null ? ks : (mKeySet = new KeySet()); return ks != null ? ks : (keySet = new KeySet());
} }
/** /**
@@ -189,9 +189,9 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
protected Object clone() throws CloneNotSupportedException { protected Object clone() throws CloneNotSupportedException {
AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone(); AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone();
map.mValues = null; map.values = null;
map.mEntrySet = null; map.entrySet = null;
map.mKeySet = null; map.keySet = null;
// TODO: Implement: Need to clone the backing map... // TODO: Implement: Need to clone the backing map...
@@ -217,7 +217,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
} }
/*protected*/ Entry<K, V> getEntry(K pKey) { /*protected*/ Entry<K, V> getEntry(K pKey) {
return mEntries.get(pKey); return entries.get(pKey);
} }
/** /**
@@ -271,7 +271,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
Entry e = (Entry) o; Entry e = (Entry) o;
//noinspection SuspiciousMethodCalls //noinspection SuspiciousMethodCalls
Entry<K, V> candidate = mEntries.get(e.getKey()); Entry<K, V> candidate = entries.get(e.getKey());
return candidate != null && candidate.equals(e); return candidate != null && candidate.equals(e);
} }
@@ -284,7 +284,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
// NOTE: Extra cautions is taken, to only remove the entry if it // NOTE: Extra cautions is taken, to only remove the entry if it
// equals the entry in the map // equals the entry in the map
Object key = ((Entry) o).getKey(); Object key = ((Entry) o).getKey();
Entry entry = (Entry) mEntries.get(key); Entry entry = (Entry) entries.get(key);
// Same entry? // Same entry?
if (entry != null && entry.equals(o)) { if (entry != null && entry.equals(o)) {
@@ -40,20 +40,20 @@ import java.io.Serializable;
/** /**
* A {@code Map} adapter for a Java Bean. * A {@code Map} adapter for a Java Bean.
* <p/> * <p/>
* Ruhtlessly stolen from * Ruthlessly stolen from
* <a href="http://binkley.blogspot.com/2006/08/mapping-java-bean.html>Binkley's Blog</a> * <a href="http://binkley.blogspot.com/2006/08/mapping-java-bean.html>Binkley's Blog</a>
*/ */
public final class BeanMap extends AbstractMap<String, Object> implements Serializable, Cloneable { public final class BeanMap extends AbstractMap<String, Object> implements Serializable, Cloneable {
private final Object mBean; private final Object bean;
private transient Set<PropertyDescriptor> mDescriptors; private transient Set<PropertyDescriptor> descriptors;
public BeanMap(Object pBean) throws IntrospectionException { public BeanMap(Object pBean) throws IntrospectionException {
if (pBean == null) { if (pBean == null) {
throw new IllegalArgumentException("bean == null"); throw new IllegalArgumentException("bean == null");
} }
mBean = pBean; bean = pBean;
mDescriptors = initDescriptors(pBean); descriptors = initDescriptors(pBean);
} }
private static Set<PropertyDescriptor> initDescriptors(Object pBean) throws IntrospectionException { private static Set<PropertyDescriptor> initDescriptors(Object pBean) throws IntrospectionException {
@@ -100,7 +100,7 @@ public final class BeanMap extends AbstractMap<String, Object> implements Serial
} }
public int size() { public int size() {
return mDescriptors.size(); return descriptors.size();
} }
private String checkKey(final Object pKey) { private String checkKey(final Object pKey) {
@@ -119,17 +119,17 @@ public final class BeanMap extends AbstractMap<String, Object> implements Serial
private Object readResolve() throws IntrospectionException { private Object readResolve() throws IntrospectionException {
// Initialize the property descriptors // Initialize the property descriptors
mDescriptors = initDescriptors(mBean); descriptors = initDescriptors(bean);
return this; return this;
} }
private class BeanSet extends AbstractSet<Entry<String, Object>> { private class BeanSet extends AbstractSet<Entry<String, Object>> {
public Iterator<Entry<String, Object>> iterator() { public Iterator<Entry<String, Object>> iterator() {
return new BeanIterator(mDescriptors.iterator()); return new BeanIterator(descriptors.iterator());
} }
public int size() { public int size() {
return mDescriptors.size(); return descriptors.size();
} }
} }
@@ -173,7 +173,7 @@ public final class BeanMap extends AbstractMap<String, Object> implements Serial
throw new UnsupportedOperationException("No getter: " + mDescriptor.getName()); throw new UnsupportedOperationException("No getter: " + mDescriptor.getName());
} }
return method.invoke(mBean); return method.invoke(bean);
} }
}); });
} }
@@ -188,7 +188,7 @@ public final class BeanMap extends AbstractMap<String, Object> implements Serial
} }
final Object old = getValue(); final Object old = getValue();
method.invoke(mBean, pValue); method.invoke(bean, pValue);
return old; return old;
} }
}); });
@@ -28,17 +28,21 @@
package com.twelvemonkeys.util; package com.twelvemonkeys.util;
import com.twelvemonkeys.lang.Validate;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.*; import java.util.*;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
/** /**
* A utility class with some useful collection-related functions. * A utility class with some useful collection-related functions.
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author <A href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</A> * @author <A href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</A>
* @author last modified by $Author: haku $ * @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/CollectionUtil.java#3 $ * @version $Id: com/twelvemonkeys/util/CollectionUtil.java#3 $
* @todo move makeList and makeSet to StringUtil?
* @see Collections * @see Collections
* @see Arrays * @see Arrays
*/ */
@@ -51,8 +55,6 @@ public final class CollectionUtil {
*/ */
@SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"}) @SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"})
public static void main(String[] pArgs) { public static void main(String[] pArgs) {
test();
int howMany = 1000; int howMany = 1000;
if (pArgs.length > 0) { if (pArgs.length > 0) {
@@ -257,7 +259,7 @@ public final class CollectionUtil {
* If the sub array is same length as the original * If the sub array is same length as the original
* ({@code pStart == 0}), the original array will be returned. * ({@code pStart == 0}), the original array will be returned.
* *
* @param pArray the origianl array * @param pArray the original array
* @param pStart the start index of the original array * @param pStart the start index of the original array
* @return a subset of the original array, or the original array itself, * @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0. * if {@code pStart} is 0.
@@ -270,16 +272,33 @@ public final class CollectionUtil {
return subArray(pArray, pStart, -1); return subArray(pArray, pStart, -1);
} }
/**
* Creates an array containing a subset of the original array.
* If the sub array is same length as the original
* ({@code pStart == 0}), the original array will be returned.
*
* @param pArray the original array
* @param pStart the start index of the original array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null}
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
public static <T> T[] subArray(T[] pArray, int pStart) {
return subArray(pArray, pStart, -1);
}
/** /**
* Creates an array containing a subset of the original array. * Creates an array containing a subset of the original array.
* If the {@code pLength} parameter is negative, it will be ignored. * If the {@code pLength} parameter is negative, it will be ignored.
* If there are not {@code pLength} elements in the original array * If there are not {@code pLength} elements in the original array
* after {@code pStart}, the {@code pLength} paramter will be * after {@code pStart}, the {@code pLength} parameter will be
* ignored. * ignored.
* If the sub array is same length as the original, the original array will * If the sub array is same length as the original, the original array will
* be returned. * be returned.
* *
* @param pArray the origianl array * @param pArray the original array
* @param pStart the start index of the original array * @param pStart the start index of the original array
* @param pLength the length of the new array * @param pLength the length of the new array
* @return a subset of the original array, or the original array itself, * @return a subset of the original array, or the original array itself,
@@ -292,9 +311,7 @@ public final class CollectionUtil {
*/ */
@SuppressWarnings({"SuspiciousSystemArraycopy"}) @SuppressWarnings({"SuspiciousSystemArraycopy"})
public static Object subArray(Object pArray, int pStart, int pLength) { public static Object subArray(Object pArray, int pStart, int pLength) {
if (pArray == null) { Validate.notNull(pArray, "array");
throw new IllegalArgumentException("array == null");
}
// Get component type // Get component type
Class type; Class type;
@@ -321,7 +338,7 @@ public final class CollectionUtil {
Object result; Object result;
if (newLength < originalLength) { if (newLength < originalLength) {
// Create subarray & copy into // Create sub array & copy into
result = Array.newInstance(type, newLength); result = Array.newInstance(type, newLength);
System.arraycopy(pArray, pStart, result, 0, newLength); System.arraycopy(pArray, pStart, result, 0, newLength);
} }
@@ -335,7 +352,33 @@ public final class CollectionUtil {
return result; return result;
} }
/**
* Creates an array containing a subset of the original array.
* If the {@code pLength} parameter is negative, it will be ignored.
* If there are not {@code pLength} elements in the original array
* after {@code pStart}, the {@code pLength} parameter will be
* ignored.
* If the sub array is same length as the original, the original array will
* be returned.
*
* @param pArray the original array
* @param pStart the start index of the original array
* @param pLength the length of the new array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0 and {@code pLength} is either
* negative, or greater or equal to {@code pArray.length}.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null}
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
@SuppressWarnings("unchecked")
public static <T> T[] subArray(T[] pArray, int pStart, int pLength) {
return (T[]) subArray((Object) pArray, pStart, pLength);
}
public static <T> Iterator<T> iterator(final Enumeration<T> pEnum) { public static <T> Iterator<T> iterator(final Enumeration<T> pEnum) {
notNull(pEnum, "enumeration");
return new Iterator<T>() { return new Iterator<T>() {
public boolean hasNext() { public boolean hasNext() {
return pEnum.hasMoreElements(); return pEnum.hasMoreElements();
@@ -361,8 +404,8 @@ public final class CollectionUtil {
* the given collection. * the given collection.
* @throws ClassCastException class of the specified element prevents it * @throws ClassCastException class of the specified element prevents it
* from being added to this collection. * from being added to this collection.
* @throws NullPointerException if the specified element is null and this * @throws NullPointerException if the specified element is {@code null} and this
* collection does not support null elements. * collection does not support {@code null} elements.
* @throws IllegalArgumentException some aspect of this element prevents * @throws IllegalArgumentException some aspect of this element prevents
* it from being added to this collection. * it from being added to this collection.
*/ */
@@ -372,7 +415,7 @@ public final class CollectionUtil {
} }
} }
// Is there a usecase where Arrays.asList(pArray).iterator() can't ne used? // Is there a use case where Arrays.asList(pArray).iterator() can't ne used?
/** /**
* Creates a thin {@link Iterator} wrapper around an array. * Creates a thin {@link Iterator} wrapper around an array.
* *
@@ -383,7 +426,7 @@ public final class CollectionUtil {
* {@code pLength > pArray.length - pStart} * {@code pLength > pArray.length - pStart}
*/ */
public static <E> ListIterator<E> iterator(final E[] pArray) { public static <E> ListIterator<E> iterator(final E[] pArray) {
return iterator(pArray, 0, pArray.length); return iterator(pArray, 0, notNull(pArray).length);
} }
/** /**
@@ -408,7 +451,7 @@ public final class CollectionUtil {
* @return a new {@code Map} of same type as {@code pSource} * @return a new {@code Map} of same type as {@code pSource}
* @throws IllegalArgumentException if {@code pSource == null}, * @throws IllegalArgumentException if {@code pSource == null},
* or if a new map can't be instantiated, * or if a new map can't be instantiated,
* or if source map contains duplaicates. * or if source map contains duplicates.
* *
* @see #invert(java.util.Map, java.util.Map, DuplicateHandler) * @see #invert(java.util.Map, java.util.Map, DuplicateHandler)
*/ */
@@ -424,7 +467,7 @@ public final class CollectionUtil {
* @param pResult the map used to contain the result, may be {@code null}, * @param pResult the map used to contain the result, may be {@code null},
* in that case a new {@code Map} of same type as {@code pSource} is created. * in that case a new {@code Map} of same type as {@code pSource} is created.
* The result map <em>should</em> be empty, otherwise duplicate values will need to be resolved. * The result map <em>should</em> be empty, otherwise duplicate values will need to be resolved.
* @param pHandler duplicate handler, may be {@code null} if source map don't contain dupliate values * @param pHandler duplicate handler, may be {@code null} if source map don't contain duplicate values
* @return {@code pResult}, or a new {@code Map} if {@code pResult == null} * @return {@code pResult}, or a new {@code Map} if {@code pResult == null}
* @throws IllegalArgumentException if {@code pSource == null}, * @throws IllegalArgumentException if {@code pSource == null},
* or if result map is {@code null} and a new map can't be instantiated, * or if result map is {@code null} and a new map can't be instantiated,
@@ -476,20 +519,20 @@ public final class CollectionUtil {
return result; return result;
} }
public static <T> Comparator<T> reverseOrder(Comparator<T> pOriginal) { public static <T> Comparator<T> reverseOrder(final Comparator<T> pOriginal) {
return new ReverseComparator<T>(pOriginal); return new ReverseComparator<T>(pOriginal);
} }
private static class ReverseComparator<T> implements Comparator<T> { private static class ReverseComparator<T> implements Comparator<T> {
private Comparator<T> mComparator; private final Comparator<T> comparator;
public ReverseComparator(Comparator<T> pComparator) { public ReverseComparator(final Comparator<T> pComparator) {
mComparator = pComparator; comparator = notNull(pComparator);
} }
public int compare(T pLeft, T pRight) { public int compare(T pLeft, T pRight) {
int result = mComparator.compare(pLeft, pRight); int result = comparator.compare(pLeft, pRight);
// We can't simply return -result, as -Integer.MIN_VALUE == Integer.MIN_VALUE. // We can't simply return -result, as -Integer.MIN_VALUE == Integer.MIN_VALUE.
return -(result | (result >>> 1)); return -(result | (result >>> 1));
@@ -516,73 +559,21 @@ public final class CollectionUtil {
return (T) pCollection; return (T) pCollection;
} }
@SuppressWarnings({"UnusedDeclaration"})
static void test() {
List list = Collections.singletonList("foo");
@SuppressWarnings({"unchecked"})
Set set = new HashSet(list);
List<String> strs0 = CollectionUtil.generify(list, String.class);
List<Object> objs0 = CollectionUtil.generify(list, String.class);
// List<String> strs01 = CollectionUtil.generify(list, Object.class); // Not okay
try {
List<String> strs1 = CollectionUtil.generify(set, String.class); // Not ok, runtime CCE unless set is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
try {
ArrayList<String> strs01 = CollectionUtil.generify(list, String.class); // Not ok, runtime CCE unless list is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
Set<String> setstr1 = CollectionUtil.generify(set, String.class);
Set<Object> setobj1 = CollectionUtil.generify(set, String.class);
try {
Set<Object> setobj44 = CollectionUtil.generify(list, String.class); // Not ok, runtime CCE unless list is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
List<String> strs2 = CollectionUtil.<List<String>, String>generify2(list);
List<Object> objs2 = CollectionUtil.<List<Object>, String>generify2(list);
// List<String> morestrs = CollectionUtil.<List<Object>, String>generify2(list); // Not ok
try {
List<String> strs3 = CollectionUtil.<List<String>, String>generify2(set); // Not ok, runtime CCE unless set is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
}
private static class ArrayIterator<E> implements ListIterator<E> { private static class ArrayIterator<E> implements ListIterator<E> {
private int mIndex; private int next;
private final int mStart; private final int start;
private final int mLength; private final int length;
private final E[] mArray; private final E[] array;
public ArrayIterator(E[] pArray, int pStart, int pLength) { public ArrayIterator(final E[] pArray, final int pStart, final int pLength) {
if (pArray == null) { array = notNull(pArray, "array");
throw new IllegalArgumentException("array == null"); start = isTrue(pStart >= 0, pStart, "start < 0: %d");
} length = isTrue(pLength <= pArray.length - pStart, pLength, "length > array.length - start: %d");
if (pStart < 0) { next = start;
throw new IllegalArgumentException("start < 0");
}
if (pLength > pArray.length - pStart) {
throw new IllegalArgumentException("length > array.length - start");
}
mArray = pArray;
mStart = pStart;
mLength = pLength;
mIndex = mStart;
} }
public boolean hasNext() { public boolean hasNext() {
return mIndex < mLength + mStart; return next < length + start;
} }
public E next() { public E next() {
@@ -591,7 +582,7 @@ public final class CollectionUtil {
} }
try { try {
return mArray[mIndex++]; return array[next++];
} }
catch (ArrayIndexOutOfBoundsException e) { catch (ArrayIndexOutOfBoundsException e) {
NoSuchElementException nse = new NoSuchElementException(e.getMessage()); NoSuchElementException nse = new NoSuchElementException(e.getMessage());
@@ -609,11 +600,11 @@ public final class CollectionUtil {
} }
public boolean hasPrevious() { public boolean hasPrevious() {
return mIndex > mStart; return next > start;
} }
public int nextIndex() { public int nextIndex() {
return mIndex + 1; return next - start;
} }
public E previous() { public E previous() {
@@ -622,7 +613,7 @@ public final class CollectionUtil {
} }
try { try {
return mArray[mIndex--]; return array[--next];
} }
catch (ArrayIndexOutOfBoundsException e) { catch (ArrayIndexOutOfBoundsException e) {
NoSuchElementException nse = new NoSuchElementException(e.getMessage()); NoSuchElementException nse = new NoSuchElementException(e.getMessage());
@@ -632,11 +623,11 @@ public final class CollectionUtil {
} }
public int previousIndex() { public int previousIndex() {
return mIndex - 1; return nextIndex() - 1;
} }
public void set(E pElement) { public void set(E pElement) {
mArray[mIndex] = pElement; array[next - 1] = pElement;
} }
} }
} }
@@ -49,11 +49,11 @@ import java.util.NoSuchElementException;
*/ */
public class FilterIterator<E> implements Iterator<E> { public class FilterIterator<E> implements Iterator<E> {
protected final Filter<E> mFilter; protected final Filter<E> filter;
protected final Iterator<E> mIterator; protected final Iterator<E> iterator;
private E mNext = null; private E next = null;
private E mCurrent = null; private E current = null;
/** /**
* Creates a {@code FilterIterator} that wraps the {@code Iterator}. Each * Creates a {@code FilterIterator} that wraps the {@code Iterator}. Each
@@ -72,8 +72,8 @@ public class FilterIterator<E> implements Iterator<E> {
throw new IllegalArgumentException("filter == null"); throw new IllegalArgumentException("filter == null");
} }
mIterator = pIterator; iterator = pIterator;
mFilter = pFilter; filter = pFilter;
} }
/** /**
@@ -85,16 +85,16 @@ public class FilterIterator<E> implements Iterator<E> {
* @see FilterIterator.Filter#accept * @see FilterIterator.Filter#accept
*/ */
public boolean hasNext() { public boolean hasNext() {
while (mNext == null && mIterator.hasNext()) { while (next == null && iterator.hasNext()) {
E element = mIterator.next(); E element = iterator.next();
if (mFilter.accept(element)) { if (filter.accept(element)) {
mNext = element; next = element;
break; break;
} }
} }
return mNext != null; return next != null;
} }
/** /**
@@ -105,11 +105,11 @@ public class FilterIterator<E> implements Iterator<E> {
*/ */
public E next() { public E next() {
if (hasNext()) { if (hasNext()) {
mCurrent = mNext; current = next;
// Make sure we advance next time // Make sure we advance next time
mNext = null; next = null;
return mCurrent; return current;
} }
else { else {
throw new NoSuchElementException("Iteration has no more elements."); throw new NoSuchElementException("Iteration has no more elements.");
@@ -124,8 +124,8 @@ public class FilterIterator<E> implements Iterator<E> {
* progress in any way other than by calling this method. * progress in any way other than by calling this method.
*/ */
public void remove() { public void remove() {
if (mCurrent != null) { if (current != null) {
mIterator.remove(); iterator.remove();
} }
else { else {
throw new IllegalStateException("Iteration has no current element."); throw new IllegalStateException("Iteration has no current element.");
@@ -107,7 +107,7 @@ public class IgnoreCaseMap<V> extends AbstractDecoratedMap<String, V> implements
*/ */
public V put(String pKey, V pValue) { public V put(String pKey, V pValue) {
String key = (String) toUpper(pKey); String key = (String) toUpper(pKey);
return unwrap(mEntries.put(key, new BasicEntry<String, V>(key, pValue))); return unwrap(entries.put(key, new BasicEntry<String, V>(key, pValue)));
} }
private V unwrap(Entry<String, V> pEntry) { private V unwrap(Entry<String, V> pEntry) {
@@ -124,7 +124,7 @@ public class IgnoreCaseMap<V> extends AbstractDecoratedMap<String, V> implements
* the key is not mapped to any value in this map. * the key is not mapped to any value in this map.
*/ */
public V get(Object pKey) { public V get(Object pKey) {
return unwrap(mEntries.get(toUpper(pKey))); return unwrap(entries.get(toUpper(pKey)));
} }
/** /**
@@ -137,7 +137,7 @@ public class IgnoreCaseMap<V> extends AbstractDecoratedMap<String, V> implements
* or null if the key did not have a mapping. * or null if the key did not have a mapping.
*/ */
public V remove(Object pKey) { public V remove(Object pKey) {
return unwrap(mEntries.remove(toUpper(pKey))); return unwrap(entries.remove(toUpper(pKey)));
} }
/** /**
@@ -149,7 +149,7 @@ public class IgnoreCaseMap<V> extends AbstractDecoratedMap<String, V> implements
* map, as determined by the equals method; false otherwise. * map, as determined by the equals method; false otherwise.
*/ */
public boolean containsKey(Object pKey) { public boolean containsKey(Object pKey) {
return mEntries.containsKey(toUpper(pKey)); return entries.containsKey(toUpper(pKey));
} }
/** /**
@@ -163,14 +163,14 @@ public class IgnoreCaseMap<V> extends AbstractDecoratedMap<String, V> implements
} }
protected Iterator<Entry<String, V>> newEntryIterator() { protected Iterator<Entry<String, V>> newEntryIterator() {
return (Iterator) mEntries.entrySet().iterator(); return (Iterator) entries.entrySet().iterator();
} }
protected Iterator<String> newKeyIterator() { protected Iterator<String> newKeyIterator() {
return mEntries.keySet().iterator(); return entries.keySet().iterator();
} }
protected Iterator<V> newValueIterator() { protected Iterator<V> newValueIterator() {
return (Iterator<V>) mEntries.values().iterator(); return (Iterator<V>) entries.values().iterator();
} }
} }

Some files were not shown because too many files have changed in this diff Show More