Adding twelvemonkeys-core

This commit is contained in:
Harald Kuhr
2009-09-03 20:28:39 +02:00
parent 224c40f3f0
commit 2523f31a8c
213 changed files with 52695 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>
<artifactId>twelvemonkeys-core</artifactId>
<groupId>com.twelvemonkeys</groupId>
<version>2.2-SNAPSHOT</version>
<name>TwelveMonkeys Core</name>
<description>
The TwelveMonkeys Core library. Contains common utility classes.
</description>
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys-parent</artifactId>
<version>2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>jmagick</groupId>
<artifactId>jmagick</artifactId>
<version>6.2.4</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jmock</groupId>
<artifactId>jmock-cglib</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,104 @@
/*
* 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.image;
import java.awt.image.ImageProducer;
import java.awt.image.ImageConsumer;
import java.util.List;
import java.util.ArrayList;
/**
* AbstractImageSource
* <p/>
*
* @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/AbstractImageSource.java#1 $
*/
public abstract class AbstractImageSource implements ImageProducer {
private List<ImageConsumer> mConsumers = new ArrayList<ImageConsumer>();
protected int mWidth;
protected int mHeight;
protected int mXOff;
protected int mYOff;
// ImageProducer interface
public void addConsumer(ImageConsumer pConsumer) {
if (mConsumers.contains(pConsumer)) {
return;
}
mConsumers.add(pConsumer);
try {
initConsumer(pConsumer);
sendPixels(pConsumer);
if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
// Get rid of "sticky" consumers...
if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
removeConsumer(pConsumer);
}
}
}
catch (Exception e) {
e.printStackTrace();
if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
}
}
}
public void removeConsumer(ImageConsumer pConsumer) {
mConsumers.remove(pConsumer);
}
/**
* This implementation silently ignores this instruction. If pixeldata is
* not in TDLR order by default, subclasses must override this method.
*
* @param pConsumer the consumer that requested the resend
*
* @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
*/
public void requestTopDownLeftRightResend(ImageConsumer pConsumer) {
// ignore
}
public void startProduction(ImageConsumer pConsumer) {
addConsumer(pConsumer);
}
public boolean isConsumer(ImageConsumer pConsumer) {
return mConsumers.contains(pConsumer);
}
protected abstract void initConsumer(ImageConsumer pConsumer);
protected abstract void sendPixels(ImageConsumer pConsumer);
}

View File

@@ -0,0 +1,453 @@
/*
* 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.image;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
/**
* AreaAverageOp
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/image/AreaAverageOp.java#2 $
*/
public class AreaAverageOp implements BufferedImageOp, RasterOp {
final private int mWidth;
final private int mHeight;
private Rectangle mSourceRegion;
public AreaAverageOp(final int pWidth, final int pHeight) {
mWidth = pWidth;
mHeight = pHeight;
}
public Rectangle getSourceRegion() {
if (mSourceRegion == null) {
return null;
}
return new Rectangle(mSourceRegion);
}
public void setSourceRegion(final Rectangle pSourceRegion) {
if (pSourceRegion == null) {
mSourceRegion = null;
}
else {
if (mSourceRegion == null) {
mSourceRegion = new Rectangle(pSourceRegion);
}
else {
mSourceRegion.setBounds(pSourceRegion);
}
}
}
public BufferedImage filter(BufferedImage src, BufferedImage dest) {
BufferedImage result = dest != null ? dest : createCompatibleDestImage(src, null);
// TODO: src and dest can't be the same
// TODO: Do some type checking here..
// Should work with
// * all BYTE types, unless sub-byte packed rasters/IndexColorModel
// * all INT types (even custom, as long as they use 8bit/componnet)
// * all USHORT types (even custom)
// TODO: Also check if the images are really compatible!?
long start = System.currentTimeMillis();
// Straight-forward version
//Image scaled = src.getScaledInstance(mWidth, mHeight, Image.SCALE_AREA_AVERAGING);
//ImageUtil.drawOnto(result, scaled);
//result = new BufferedImageFactory(scaled).getBufferedImage();
/*
// Try: Use bilinear/bicubic and half the image down until it's less than
// twice as big, then use bicubic for the last step?
BufferedImage temp = null;
AffineTransform xform = null;
int w = src.getWidth();
int h = src.getHeight();
while (w / 2 > mWidth && h / 2 > mHeight) {
w /= 2;
h /= 2;
if (temp == null) {
xform = AffineTransform.getScaleInstance(.5, .5);
ColorModel cm = src.getColorModel();
temp = new BufferedImage(cm,
ImageUtil.createCompatibleWritableRaster(src, cm, w, h),
cm.isAlphaPremultiplied(), null);
resample(src, temp, xform);
}
else {
resample(temp, temp, xform);
}
System.out.println("w: " + w);
System.out.println("h: " + h);
}
if (temp != null) {
src = temp.getSubimage(0, 0, w, h);
}
resample(src, result, AffineTransform.getScaleInstance(mWidth / (double) w, mHeight / (double) h));
*/
// The real version
filterImpl(src.getRaster(), result.getRaster());
long time = System.currentTimeMillis() - start;
System.out.println("time: " + time);
return result;
}
private void resample(final BufferedImage pSrc, final BufferedImage pDest, final AffineTransform pXform) {
Graphics2D d = pDest.createGraphics();
d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
try {
d.drawImage(pSrc, pXform, null);
}
finally {
d.dispose();
}
}
public WritableRaster filter(Raster src, WritableRaster dest) {
WritableRaster result = dest != null ? dest : createCompatibleDestRaster(src);
return filterImpl(src, result);
}
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
if (mSourceRegion != null) {
int cx = mSourceRegion.x;
int cy = mSourceRegion.y;
int cw = mSourceRegion.width;
int ch = mSourceRegion.height;
boolean same = src == dest;
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
src = same ? dest : src.createChild(cx, cy, cw, ch, 0, 0, null);
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
}
final int width = src.getWidth();
final int height = src.getHeight();
// TODO: This don't work too well..
// The thing is that the step length and the scan length will vary, for
// non-even (1/2, 1/4, 1/8 etc) resampling
int widthSteps = (width + mWidth - 1) / mWidth;
int heightSteps = (height + mHeight - 1) / mHeight;
final boolean oddX = width % mWidth != 0;
final boolean oddY = height % mHeight != 0;
final int dataElements = src.getNumDataElements();
final int bands = src.getNumBands();
final int dataType = src.getTransferType();
Object data = null;
int scanW;
int scanH;
// TYPE_USHORT setup
int[] bitMasks = null;
int[] bitOffsets = null;
if (src.getTransferType() == DataBuffer.TYPE_USHORT) {
if (src.getSampleModel() instanceof SinglePixelPackedSampleModel) {
// DIRECT
SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) src.getSampleModel();
bitMasks = sampleModel.getBitMasks();
bitOffsets = sampleModel.getBitOffsets();
}
else {
// GRAY
bitMasks = new int[]{0xffff};
bitOffsets = new int[]{0};
}
}
for (int y = 0; y < mHeight; y++) {
if (!oddY || y < mHeight) {
scanH = heightSteps;
}
else {
scanH = height - (y * heightSteps);
}
for (int x = 0; x < mWidth; x++) {
if (!oddX || x < mWidth) {
scanW = widthSteps;
}
else {
scanW = width - (x * widthSteps);
}
final int pixelCount = scanW * scanH;
final int pixelLength = pixelCount * dataElements;
try {
data = src.getDataElements(x * widthSteps, y * heightSteps, scanW, scanH, data);
}
catch (IndexOutOfBoundsException e) {
// TODO: FixMe!
// The bug is in the steps...
//System.err.println("x: " + x);
//System.err.println("y: " + y);
//System.err.println("widthSteps: " + widthSteps);
//System.err.println("heightSteps: " + heightSteps);
//System.err.println("scanW: " + scanW);
//System.err.println("scanH: " + scanH);
//
//System.err.println("width: " + width);
//System.err.println("height: " + height);
//System.err.println("mWidth: " + mWidth);
//System.err.println("mHeight: " + mHeight);
//
//e.printStackTrace();
continue;
}
// TODO: Might need more channels... Use an array?
// NOTE: These are not neccessarily ARGB..
double valueA = 0.0;
double valueR = 0.0;
double valueG = 0.0;
double valueB = 0.0;
switch (dataType) {
case DataBuffer.TYPE_BYTE:
// TODO: Doesn't hold for index color models...
// For index color, the best bet is probably convert to
// true color, then convert back to the same index color
// model
byte[] bytePixels = (byte[]) data;
for (int i = 0; i < pixelLength; i += dataElements) {
valueA += bytePixels[i] & 0xff;
if (bands > 1) {
valueR += bytePixels[i + 1] & 0xff;
valueG += bytePixels[i + 2] & 0xff;
if (bands > 3) {
valueB += bytePixels[i + 3] & 0xff;
}
}
}
// Average
valueA /= pixelCount;
if (bands > 1) {
valueR /= pixelCount;
valueG /= pixelCount;
if (bands > 3) {
valueB /= pixelCount;
}
}
//for (int i = 0; i < pixelLength; i += dataElements) {
bytePixels[0] = (byte) clamp((int) valueA);
if (bands > 1) {
bytePixels[1] = (byte) clamp((int) valueR);
bytePixels[2] = (byte) clamp((int) valueG);
if (bands > 3) {
bytePixels[3] = (byte) clamp((int) valueB);
}
}
//}
break;
case DataBuffer.TYPE_INT:
int[] intPixels = (int[]) data;
// TODO: Rewrite to use bit offsets and masks from
// color model (see TYPE_USHORT) in case of a non-
// 888 or 8888 colormodel?
for (int i = 0; i < pixelLength; i += dataElements) {
valueA += (intPixels[i] & 0xff000000) >> 24;
valueR += (intPixels[i] & 0xff0000) >> 16;
valueG += (intPixels[i] & 0xff00) >> 8;
valueB += (intPixels[i] & 0xff);
}
// Average
valueA /= pixelCount;
valueR /= pixelCount;
valueG /= pixelCount;
valueB /= pixelCount;
//for (int i = 0; i < pixelLength; i += dataElements) {
intPixels[0] = clamp((int) valueA) << 24;
intPixels[0] |= clamp((int) valueR) << 16;
intPixels[0] |= clamp((int) valueG) << 8;
intPixels[0] |= clamp((int) valueB);
//}
break;
case DataBuffer.TYPE_USHORT:
if (bitMasks != null) {
short[] shortPixels = (short[]) data;
for (int i = 0; i < pixelLength; i += dataElements)
{
valueA += (shortPixels[i] & bitMasks[0]) >> bitOffsets[0];
if (bitMasks.length > 1) {
valueR += (shortPixels[i] & bitMasks[1]) >> bitOffsets[1];
valueG += (shortPixels[i] & bitMasks[2]) >> bitOffsets[2];
if (bitMasks.length > 3) {
valueB += (shortPixels[i] & bitMasks[3]) >> bitOffsets[3];
}
}
}
// Average
valueA /= pixelCount;
valueR /= pixelCount;
valueG /= pixelCount;
valueB /= pixelCount;
//for (int i = 0; i < pixelLength; i += dataElements) {
shortPixels[0] = (short) (((int) valueA << bitOffsets[0]) & bitMasks[0]);
if (bitMasks.length > 1) {
shortPixels[0] |= (short) (((int) valueR << bitOffsets[1]) & bitMasks[1]);
shortPixels[0] |= (short) (((int) valueG << bitOffsets[2]) & bitMasks[2]);
if (bitMasks.length > 3) {
shortPixels[0] |= (short) (((int) valueB << bitOffsets[3]) & bitMasks[3]);
}
}
//}
break;
}
default:
throw new IllegalArgumentException("TransferType not supported: " + dataType);
}
dest.setDataElements(x, y, 1, 1, data);
}
}
return dest;
}
private static int clamp(final int pValue) {
return pValue > 255 ? 255 : pValue;
}
public RenderingHints getRenderingHints() {
return null;
}
// TODO: Refactor boilerplate to AbstractBufferedImageOp or use a delegate?
// Delegate is maybe better as we won't always implement both BIOp and RasterOP
// (but are there ever any time we want to implemnet RasterOp and not BIOp?)
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
ColorModel cm = destCM != null ? destCM : src.getColorModel();
return new BufferedImage(cm,
ImageUtil.createCompatibleWritableRaster(src, cm, mWidth, mHeight),
cm.isAlphaPremultiplied(), null);
}
public WritableRaster createCompatibleDestRaster(Raster src) {
return src.createCompatibleWritableRaster(mWidth, mHeight);
}
public Rectangle2D getBounds2D(Raster src) {
return new Rectangle(mWidth, mHeight);
}
public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(mWidth, mHeight);
}
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
// TODO: This is wrong!
if (dstPt == null) {
if (srcPt instanceof Point2D.Double) {
dstPt = new Point2D.Double();
}
else {
dstPt = new Point2D.Float();
}
}
dstPt.setLocation(srcPt);
return dstPt;
}
public static void main(String[] pArgs) throws IOException {
BufferedImage image = ImageIO.read(new File("2006-Lamborghini-Gallardo-Spyder-Y-T-1600x1200.png"));
//BufferedImage image = ImageIO.read(new File("focus-rs.jpg"));
//BufferedImage image = ImageIO.read(new File("blauesglas_16_bitmask444.bmp"));
//image = ImageUtil.toBuffered(image, BufferedImage.TYPE_USHORT_GRAY);
for (int i = 0; i < 100; i++) {
//new PixelizeOp(10).filter(image, null);
//new AffineTransformOp(AffineTransform.getScaleInstance(.1, .1), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
//ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_BOX).filter(image, null);
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_QUADRATIC).filter(image, null);
//new AreaAverageOp(image.getWidth() / 10, image.getHeight() / 10).filter(image, null);
}
long start = System.currentTimeMillis();
//PixelizeOp pixelizer = new PixelizeOp(image.getWidth() / 10, 1);
//pixelizer.setSourceRegion(new Rectangle(0, 2 * image.getHeight() / 3, image.getWidth(), image.getHeight() / 4));
//PixelizeOp pixelizer = new PixelizeOp(4);
//image = pixelizer.filter(image, image); // Filter in place, that's cool
//image = new AffineTransformOp(AffineTransform.getScaleInstance(.25, .25), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
//image = ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_BOX).filter(image, null);
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_QUADRATIC).filter(image, null);
//image = new AreaAverageOp(image.getWidth() / 7, image.getHeight() / 4).filter(image, null);
image = new AreaAverageOp(500, 600).filter(image, null);
//image = new ResampleOp(500, 600, ResampleOp.FILTER_BOX).filter(image, null);
long time = System.currentTimeMillis() - start;
System.out.println("time: " + time + " ms");
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new JScrollPane(new JLabel(new BufferedImageIcon(image))));
frame.pack();
frame.setVisible(true);
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.image;
import java.awt.image.RGBImageFilter;
/**
* Adjusts the contrast and brightness of an image.
* <p/>
* For brightness, the valid range is {@code -2.0,..,0.0,..,2.0}.
* A value of {@code 0.0} means no change.
* Negative values will make the pixels darker.
* Maximum negative value ({@code -2}) will make all filtered pixels black.
* Positive values will make the pixels brighter.
* Maximum positive value ({@code 2}) will make all filtered pixels white.
* <p/>
* For contrast, the valid range is {@code -1.0,..,0.0,..,1.0}.
* A value of {@code 0.0} means no change.
* Negative values will reduce contrast.
* Maximum negative value ({@code -1}) will make all filtered pixels grey
* (no contrast).
* Positive values will increase contrast.
* Maximum positive value ({@code 1}) will make all filtered pixels primary
* colors (either black, white, cyan, magenta, yellow, red, blue or green).
*
* @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/image/BrightnessContrastFilter.java#1 $
*
* @todo consider doing something similar to http://archives.java.sun.com/cgi-bin/wa?A2=ind0302&L=jai-interest&F=&S=&P=15947
*/
public class BrightnessContrastFilter extends RGBImageFilter {
// This filter can filter IndexColorModel, as it is does not depend on
// the pixels' location
{
canFilterIndexColorModel = true;
}
// Use a precalculated lookup table for performace
private int[] mLUT = null;
/**
* Creates a BrightnessContrastFilter with default values
* ({@code brightness=0.3, contrast=0.3}).
* <p/>
* This will slightly increase both brightness and contrast.
*/
public BrightnessContrastFilter() {
this(0.3f, 0.3f);
}
/**
* Creates a BrightnessContrastFilter with the given values for brightness
* and contrast.
* <p/>
* For brightness, the valid range is {@code -2.0,..,0.0,..,2.0}.
* A value of {@code 0.0} means no change.
* Negative values will make the pixels darker.
* Maximum negative value ({@code -2}) will make all filtered pixels black.
* Positive values will make the pixels brighter.
* Maximum positive value ({@code 2}) will make all filtered pixels white.
* <p/>
* For contrast, the valid range is {@code -1.0,..,0.0,..,1.0}.
* A value of {@code 0.0} means no change.
* Negative values will reduce contrast.
* Maximum negative value ({@code -1}) will make all filtered pixels grey
* (no contrast).
* Positive values will increase contrast.
* Maximum positive value ({@code 1}) will make all filtered pixels primary
* colors (either black, white, cyan, magenta, yellow, red, blue or green).
*
* @param pBrightness adjust the brightness of the image, in the range
* {@code -2.0,..,0.0,..,2.0}.
* @param pContrast adjust the contrast of the image, in the range
* {@code -1.0,..,0.0,..,1.0}.
*/
public BrightnessContrastFilter(float pBrightness, float pContrast) {
mLUT = createLUT(pBrightness, pContrast);
}
private static int[] createLUT(float pBrightness, float pContrast) {
int[] lut = new int[256];
// Hmmm.. This approximates Photoshop values.. Not good though..
double contrast = pContrast > 0 ? Math.pow(pContrast, 7.0) * 127.0 : pContrast;
// Convert range [-1,..,0,..,1] -> [0,..,1,..,2]
double brightness = pBrightness + 1.0;
for (int i = 0; i < 256; i++) {
lut[i] = clamp((int) (127.5 * brightness + (i - 127) * (contrast + 1.0)));
}
// Special case, to ensure only primary colors for max contrast
if (pContrast == 1f) {
lut[127] = lut[126];
}
return lut;
}
private static int clamp(int i) {
if (i < 0) {
return 0;
}
if (i > 255) {
return 255;
}
return i;
}
/**
* Filters one pixel, adjusting brightness and contrast according to this
* filter.
*
* @param pX x
* @param pY y
* @param pARGB pixel value in default color space
*
* @return the filtered pixel value in the default color space
*/
public int filterRGB(int pX, int pY, int pARGB) {
// Get color components
int r = pARGB >> 16 & 0xFF;
int g = pARGB >> 8 & 0xFF;
int b = pARGB & 0xFF;
// Scale to new contrast
r = mLUT[r];
g = mLUT[g];
b = mLUT[b];
// Return ARGB pixel, leave transparency as is
return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b;
}
}

View File

@@ -0,0 +1,543 @@
/*
* 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.image;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.util.List;
import java.lang.reflect.Array;
/**
* A faster, lighter and easier way to convert an {@code Image} to a
* {@code BufferedImage} than using a {@code PixelGrabber}.
* Clients may provide progress listeners to monitor conversion progress.
* <p/>
* Supports source image subsampling and source region extraction.
* Supports source images with 16 bit {@link ColorModel} and
* {@link DataBuffer#TYPE_USHORT} transfer type, without converting to
* 32 bit/TYPE_INT.
* <p/>
* NOTE: Does not support images with more than one {@code ColorModel} or
* different types of pixel data. This is not very common.
*
* @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/BufferedImageFactory.java#1 $
*/
public final class BufferedImageFactory {
private List<ProgressListener> mListeners;
private int mPercentageDone;
private ImageProducer mProducer;
private boolean mError;
private boolean mFetching;
private boolean mReadColorModelOnly;
private int mX = 0;
private int mY = 0;
private int mWidth = -1;
private int mHeight = -1;
private int mXSub = 1;
private int mYSub = 1;
private int mOffset;
private int mScanSize;
private ColorModel mSourceColorModel;
private Hashtable mSourceProperties; // ImageConsumer API dictates Hashtable
private Object mSourcePixels;
private BufferedImage mBuffered;
private ColorModel mColorModel;
// NOTE: Just to not expose the inheritance
private final Consumer mConsumer = new Consumer();
/**
* Creates a {@code BufferedImageFactory}.
* @param pSource the source image
*/
public BufferedImageFactory(Image pSource) {
this(pSource.getSource());
}
/**
* Creates a {@code BufferedImageFactory}.
* @param pSource the source image producer
*/
public BufferedImageFactory(ImageProducer pSource) {
mProducer = pSource;
}
/**
* Returns the {@code BufferedImage} extracted from the given
* {@code ImageSource}. Multiple requests will return the same image.
*
* @return the {@code BufferedImage}
*
* @throws ImageConversionException if the given {@code ImageSource} cannot
* be converted for some reason.
*/
public BufferedImage getBufferedImage() throws ImageConversionException {
doFetch(false);
return mBuffered;
}
/**
* Returns the {@code ColorModel} extracted from the
* given {@code ImageSource}. Multiple requests will return the same model.
*
* @return the {@code ColorModel}
*
* @throws ImageConversionException if the given {@code ImageSource} cannot
* be converted for some reason.
*/
public ColorModel getColorModel() throws ImageConversionException {
doFetch(true);
return mBuffered != null ? mBuffered.getColorModel() : mColorModel;
}
/**
* Frees resources used by this {@code BufferedImageFactory}.
*/
public void dispose() {
freeResources();
mBuffered = null;
mColorModel = null;
}
/**
* Aborts the image prodcution.
*/
public void abort() {
mConsumer.imageComplete(ImageConsumer.IMAGEABORTED);
}
/**
* Sets the source region (AOI) for the new image.
*
* @param pRect the source region
*/
public void setSourceRegion(Rectangle pRect) {
// Refetch everything, if region changed
if (mX != pRect.x || mY != pRect.y || mWidth != pRect.width || mHeight != pRect.height) {
dispose();
}
mX = pRect.x;
mY = pRect.y;
mWidth = pRect.width;
mHeight = pRect.height;
}
/**
* Sets the source subsampling for the new image.
*
* @param pXSub horisontal subsampling factor
* @param pYSub vertical subsampling factor
*/
public void setSourceSubsampling(int pXSub, int pYSub) {
// Refetch everything, if subsampling changed
if (mXSub != pXSub || mYSub != pYSub) {
dispose();
}
if (pXSub > 1) {
mXSub = pXSub;
}
if (pYSub > 1) {
mYSub = pYSub;
}
}
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
if (!mFetching && (!pColorModelOnly && mBuffered == null || mBuffered == null && mSourceColorModel == null)) {
// NOTE: Subsampling is only applied if extracting full image
if (!pColorModelOnly && (mXSub > 1 || mYSub > 1)) {
// If only sampling a region, the region must be scaled too
if (mWidth > 0 && mHeight > 0) {
mWidth = (mWidth + mXSub - 1) / mXSub;
mHeight = (mHeight + mYSub - 1) / mYSub;
mX = (mX + mXSub - 1) / mXSub;
mY = (mY + mYSub - 1) / mYSub;
}
mProducer = new FilteredImageSource(mProducer, new SubsamplingFilter(mXSub, mYSub));
}
// Start fetching
mFetching = true;
mReadColorModelOnly = pColorModelOnly;
mProducer.startProduction(mConsumer); // Note: If single-thread (synchronous), this call will block
// Wait until the producer wakes us up, by calling imageComplete
while (mFetching) {
try {
wait();
}
catch (InterruptedException e) {
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
}
}
if (mError) {
throw new ImageConversionException("Image conversion failed: ImageConsumer.IMAGEERROR.");
}
if (pColorModelOnly) {
createColorModel();
}
else {
createBuffered();
}
}
}
private void createColorModel() {
mColorModel = mSourceColorModel;
// Clean up, in case any objects are copied/cloned, so we can free resources
freeResources();
}
private void createBuffered() {
if (mWidth > 0 && mHeight > 0) {
if (mSourceColorModel != null && mSourcePixels != null) {
// TODO: Fix pixel size / color model problem
WritableRaster raster = ImageUtil.createRaster(mWidth, mHeight, mSourcePixels, mSourceColorModel);
mBuffered = new BufferedImage(mSourceColorModel, raster, mSourceColorModel.isAlphaPremultiplied(), mSourceProperties);
}
else {
mBuffered = ImageUtil.createClear(mWidth, mHeight, null);
}
}
// Clean up, in case any objects are copied/cloned, so we can free resources
freeResources();
}
private void freeResources() {
mSourceColorModel = null;
mSourcePixels = null;
mSourceProperties = null;
}
private void processProgress(int mScanline) {
if (mListeners != null) {
int percent = 100 * mScanline / mHeight;
//System.out.println("Progress: " + percent + "%");
if (percent > mPercentageDone) {
mPercentageDone = percent;
// TODO: Fix concurrent modification if a listener removes itself...
for (ProgressListener listener : mListeners) {
listener.progress(this, percent);
}
}
}
}
/**
* Adds a progress listener to this factory.
*
* @param pListener the progress listener
*/
public void addProgressListener(ProgressListener pListener) {
if (mListeners == null) {
mListeners = new ArrayList<ProgressListener>();
}
mListeners.add(pListener);
}
/**
* Removes a progress listener from this factory.
*
* @param pListener the progress listener
*/
public void removeProgressListener(ProgressListener pListener) {
if (mListeners == null) {
return;
}
mListeners.remove(pListener);
}
/**
* Removes all progress listeners from this factory.
*/
public void removeAllProgressListeners() {
if (mListeners != null) {
mListeners.clear();
}
}
/**
* Converts an array of {@code int} pixles to an array of {@code short}
* pixels. The conversion is done, by masking out the
* <em>higher 16 bits</em> of the {@code int}.
*
* For eny given {@code int}, the {@code short} value is computed as
* follows:
* <blockquote>{@code
* short value = (short) (intValue & 0x0000ffff);
* }</blockquote>
*
* @param pPixels the pixel data to convert
* @return an array of {@code short}s, same lenght as {@code pPixels}
*/
private static short[] toShortPixels(int[] pPixels) {
short[] pixels = new short[pPixels.length];
for (int i = 0; i < pixels.length; i++) {
pixels[i] = (short) (pPixels[i] & 0xffff);
}
return pixels;
}
/**
* This interface allows clients of a {@code BufferedImageFactory} to
* receive notifications of decoding progress.
*
* @see BufferedImageFactory#addProgressListener
* @see BufferedImageFactory#removeProgressListener
*/
public static interface ProgressListener extends EventListener {
/**
* Reports progress to this listener.
* Invoked by the {@code BufferedImageFactory} to report progress in
* the image decoding.
*
* @param pFactory the factory reporting the progress
* @param pPercentage the perccentage of progress
*/
void progress(BufferedImageFactory pFactory, float pPercentage);
}
private class Consumer implements ImageConsumer {
/**
* Implementation of all setPixels methods.
* Note that this implementation assumes that all invocations for one
* image uses the same color model, and that the pixel data has the
* same type.
*
* @param pX x coordinate of pixel data region
* @param pY y coordinate of pixel data region
* @param pWidth width of pixel data region
* @param pHeight height of pixel data region
* @param pModel the color model of the pixel data
* @param pPixels the pixel data array
* @param pOffset the offset into the pixel data array
* @param pScanSize the scan size of the pixel data array
*/
private void setPixelsImpl(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, Object pPixels, int pOffset, int pScanSize) {
setColorModelOnce(pModel);
if (pPixels == null) {
return;
}
//System.out.println("Setting " + pPixels.getClass().getComponentType() + " pixels: " + Array.getLength(pPixels));
// Allocate array if neccessary
if (mSourcePixels == null) {
/*
System.out.println("ColorModel: " + pModel);
System.out.println("Scansize: " + pScanSize + " TrasferType: " + ImageUtil.getTransferType(pModel));
System.out.println("Creating " + pPixels.getClass().getComponentType() + " array of length " + (mWidth * mHeight));
*/
// Allocate a suitable source pixel array
// TODO: Should take pixel "width" into consideration, for byte packed rasters?!
// OR... Is anything but single-pixel models really supported by the API?
mSourcePixels = Array.newInstance(pPixels.getClass().getComponentType(), mWidth * mHeight);
mScanSize = mWidth;
mOffset = 0;
}
else if (mSourcePixels.getClass() != pPixels.getClass()) {
throw new IllegalStateException("Only one pixel type allowed");
}
// AOI stuff
if (pY < mY) {
int diff = mY - pY;
if (diff >= pHeight) {
return;
}
pOffset += pScanSize * diff;
pY += diff;
pHeight -= diff;
}
if (pY + pHeight > mY + mHeight) {
pHeight = (mY + mHeight) - pY;
if (pHeight <= 0) {
return;
}
}
if (pX < mX) {
int diff = mX - pX;
if (diff >= pWidth) {
return;
}
pOffset += diff;
pX += diff;
pWidth -= diff;
}
if (pX + pWidth > mX + mWidth) {
pWidth = (mX + mWidth) - pX;
if (pWidth <= 0) {
return;
}
}
int dstOffset = mOffset + (pY - mY) * mScanSize + (pX - mX);
// Do the pixel copying
for (int i = pHeight; i > 0; i--) {
System.arraycopy(pPixels, pOffset, mSourcePixels, dstOffset, pWidth);
pOffset += pScanSize;
dstOffset += mScanSize;
}
processProgress(pY + pHeight);
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) {
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
}
private void setColorModelOnce(ColorModel pModel) {
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
// first passes the original colormodel through in setColorModel, then
// later replaces it with the default RGB in the first setPixels call
// (this is probably allowed according to the spec, but it's a waste of
// time and space).
if (mSourceColorModel != pModel) {
if (/*mSourceColorModel == null ||*/ mSourcePixels == null) {
mSourceColorModel = pModel;
}
else {
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
}
}
// If color model is all we ask for, stop now
if (mReadColorModelOnly) {
mConsumer.imageComplete(ImageConsumer.IMAGEABORTED);
}
}
/** {@code ImageConsumer} implementation, do not invoke */
public void imageComplete(int pStatus) {
mFetching = false;
if (mProducer != null) {
mProducer.removeConsumer(this);
}
switch (pStatus) {
case IMAGEERROR:
new Error().printStackTrace();
mError = true;
break;
}
synchronized (BufferedImageFactory.this) {
BufferedImageFactory.this.notifyAll();
}
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setColorModel(ColorModel pModel) {
//System.out.println("SetColorModel: " + pModel);
setColorModelOnce(pModel);
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setDimensions(int pWidth, int pHeight) {
//System.out.println("Setting dimensions: " + pWidth + ", " + pHeight);
if (mWidth < 0) {
mWidth = pWidth - mX;
}
if (mHeight < 0) {
mHeight = pHeight - mY;
}
// Hmm.. Special case, but is it a good idea?
if (mWidth <= 0 || mHeight <= 0) {
imageComplete(STATICIMAGEDONE);
}
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setHints(int pHintflags) {
// ignore
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
/*if (pModel.getPixelSize() < 8) {
// Byte packed
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toBytePackedPixels(pPixels, pModel.getPixelSize()), pOffset, pScanSize);
}
/*
else if (pModel.getPixelSize() > 8) {
// Byte interleaved
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toByteInterleavedPixels(pPixels), pOffset, pScanSize);
}
*/
//else {
// Default, pixelSize == 8, one byte pr pixel
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
//}
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
if (ImageUtil.getTransferType(pModel) == DataBuffer.TYPE_USHORT) {
// NOTE: Workaround for limitation in ImageConsumer API
// Convert int[] to short[], to be compatible with the ColorModel
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
}
else {
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
}
}
/** {@code ImageConsumer} implementation, do not invoke directly */
public void setProperties(Hashtable pProperties) {
mSourceProperties = pProperties;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.image;
import javax.swing.Icon;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.geom.AffineTransform;
/**
* An {@code Icon} implementation backed by a {@code BufferedImage}.
* <p/>
*
* @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/BufferedImageIcon.java#2 $
*/
public class BufferedImageIcon implements Icon {
private final BufferedImage mImage;
private int mWidth;
private int mHeight;
private final boolean mFast;
public BufferedImageIcon(BufferedImage pImage) {
this(pImage, pImage.getWidth(), pImage.getHeight());
}
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
if (pWidth <= 0 || pHeight <= 0) {
throw new IllegalArgumentException("Icon size must be positive");
}
mImage = pImage;
mWidth = pWidth;
mHeight = pHeight;
mFast = pImage.getWidth() == mWidth && pImage.getHeight() == mHeight;
}
public int getIconHeight() {
return mHeight;
}
public int getIconWidth() {
return mWidth;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
if (mFast || !(g instanceof Graphics2D)) {
//System.out.println("Scaling fast");
g.drawImage(mImage, x, y, mWidth, mHeight, null);
}
else {
//System.out.println("Scaling using interpolation");
Graphics2D g2 = (Graphics2D) g;
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
xform.scale(mWidth / (double) mImage.getWidth(), mHeight / (double) mImage.getHeight());
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(mImage, xform, null);
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.image;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* ConvolveTester
*
* @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/image/ConvolveTester.java#1 $
*/
public class ConvolveTester {
// Initial sample timings (avg, 1000 iterations)
// PNG, type 0: JPEG, type 3:
// ZERO_FILL: 5.4 ms 4.6 ms
// NO_OP: 5.4 ms 4.6 ms
// REFLECT: 42.4 ms 24.9 ms
// WRAP: 86.9 ms 29.5 ms
final static int ITERATIONS = 1000;
public static void main(String[] pArgs) throws IOException {
File input = new File(pArgs[0]);
BufferedImage image = ImageIO.read(input);
BufferedImage result = null;
System.out.println("image: " + image);
if (pArgs.length > 1) {
float ammount = Float.parseFloat(pArgs[1]);
int edgeOp = pArgs.length > 2 ? Integer.parseInt(pArgs[2]) : ImageUtil.EDGE_REFLECT;
long start = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
result = sharpen(image, ammount, edgeOp);
}
long end = System.currentTimeMillis();
System.out.println("Time: " + ((end - start) / (double) ITERATIONS) + "ms");
showIt(result, "Sharpened " + ammount + " " + input.getName());
}
else {
showIt(image, "Original " + input.getName());
}
}
public static void showIt(final BufferedImage pImage, final String pTitle) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
JFrame frame = new JFrame(pTitle);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
JPanel pane = new JPanel(new BorderLayout());
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImageIcon icon = new BufferedImageIcon(ImageUtil.accelerate(pImage, gc));
JScrollPane scroll = new JScrollPane(new JLabel(icon));
scroll.setBorder(null);
pane.add(scroll);
frame.setContentPane(pane);
frame.pack();
frame.setVisible(true);
}
});
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
static BufferedImage sharpen(BufferedImage pOriginal, final float pAmmount, int pEdgeOp) {
if (pAmmount == 0f) {
return pOriginal;
}
// Create the convolution matrix
float[] data = new float[]{
0.0f, -pAmmount, 0.0f,
-pAmmount, 4f * pAmmount + 1f, -pAmmount,
0.0f, -pAmmount, 0.0f
};
// Do the filtering
return ImageUtil.convolve(pOriginal, new Kernel(3, 3, data), pEdgeOp);
}
}

View File

@@ -0,0 +1,244 @@
/*
* 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.image;
import java.awt.image.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Point2D;
/**
* This class implements a convolution from the source
* to the destination.
*
* @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/image/ConvolveWithEdgeOp.java#1 $
*
* @see java.awt.image.ConvolveOp
*/
public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
/**
* Alias for {@link ConvolveOp#EDGE_ZERO_FILL}.
* @see #EDGE_REFLECT
*/
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
/**
* Alias for {@link ConvolveOp#EDGE_NO_OP}.
* @see #EDGE_REFLECT
*/
public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP;
/**
* Adds a border to the image while convolving. The border will reflect the
* edges of the original image. This is usually a good default.
* Note that while this mode typically provides better quality than the
* standard modes {@code EDGE_ZERO_FILL} and {@code EDGE_NO_OP}, it does so
* at the expense of higher memory consumption and considerable more computation.
*/
public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT
/**
* 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.
* Note that while this mode typically provides better quality than the
* standard modes {@code EDGE_ZERO_FILL} and {@code EDGE_NO_OP}, it does so
* at the expense of higher memory consumption and considerable more computation.
* @see #EDGE_REFLECT
*/
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
private final Kernel mKernel;
private final int mEdgeCondition;
private final ConvolveOp mConvolve;
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
// Create convolution operation
int edge;
switch (pEdgeCondition) {
case EDGE_REFLECT:
case EDGE_WRAP:
edge = ConvolveOp.EDGE_NO_OP;
break;
default:
edge = pEdgeCondition;
break;
}
mKernel = pKernel;
mEdgeCondition = pEdgeCondition;
mConvolve = new ConvolveOp(pKernel, edge, pHints);
}
public ConvolveWithEdgeOp(final Kernel pKernel) {
this(pKernel, EDGE_ZERO_FILL, null);
}
public BufferedImage filter(BufferedImage pSource, BufferedImage pDestination) {
if (pSource == null) {
throw new NullPointerException("source image is null");
}
if (pSource == pDestination) {
throw new IllegalArgumentException("source image cannot be the same as the destination image");
}
int borderX = mKernel.getWidth() / 2;
int borderY = mKernel.getHeight() / 2;
BufferedImage original = addBorder(pSource, borderX, borderY);
// Workaround for what seems to be a Java2D bug:
// ConvolveOp needs explicit destination image type for some "uncommon"
// image types. However, TYPE_3BYTE_BGR is what javax.imageio.ImageIO
// normally returns for color JPEGs... :-/
BufferedImage destination = pDestination;
if (original.getType() == BufferedImage.TYPE_3BYTE_BGR) {
destination = ImageUtil.createBuffered(
pSource.getWidth(), pSource.getHeight(),
pSource.getType(), pSource.getColorModel().getTransparency(),
null
);
}
// Do the filtering (if destination is null, a new image will be created)
destination = mConvolve.filter(original, destination);
if (pSource != original) {
// Remove the border
destination = destination.getSubimage(borderX, borderY, pSource.getWidth(), pSource.getHeight());
}
return destination;
}
private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) {
if ((mEdgeCondition & 2) == 0) {
return pOriginal;
}
// TODO: Might be faster if we could clone raster and strech it...
int w = pOriginal.getWidth();
int h = pOriginal.getHeight();
ColorModel cm = pOriginal.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(w + 2 * pBorderX, h + 2 * pBorderY);
BufferedImage bordered = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
Graphics2D g = bordered.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
// Draw original in center
g.drawImage(pOriginal, pBorderX, pBorderY, null);
// TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel
switch (mEdgeCondition) {
case EDGE_REFLECT:
// Top/left (empty)
g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center
// Top/right (empty)
g.drawImage(pOriginal, -w + pBorderX, pBorderY, pBorderX, h + pBorderY, 0, 0, 1, h, null); // Center/left
// Center/center (already drawn)
g.drawImage(pOriginal, w + pBorderX, pBorderY, 2 * pBorderX + w, h + pBorderY, w - 1, 0, w, h, null); // Center/right
// Bottom/left (empty)
g.drawImage(pOriginal, pBorderX, pBorderY + h, pBorderX + w, 2 * pBorderY + h, 0, h - 1, w, h, null); // Bottom/center
// Bottom/right (empty)
break;
case EDGE_WRAP:
g.drawImage(pOriginal, -w + pBorderX, -h + pBorderY, null); // Top/left
g.drawImage(pOriginal, pBorderX, -h + pBorderY, null); // Top/center
g.drawImage(pOriginal, w + pBorderX, -h + pBorderY, null); // Top/right
g.drawImage(pOriginal, -w + pBorderX, pBorderY, null); // Center/left
// Center/center (already drawn)
g.drawImage(pOriginal, w + pBorderX, pBorderY, null); // Center/right
g.drawImage(pOriginal, -w + pBorderX, h + pBorderY, null); // Bottom/left
g.drawImage(pOriginal, pBorderX, h + pBorderY, null); // Bottom/center
g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right
break;
default:
throw new IllegalArgumentException("Illegal edge operation " + mEdgeCondition);
}
}
finally {
g.dispose();
}
return bordered;
}
/**
* Returns the edge condition.
* @return the edge condition of this {@code ConvolveOp}.
* @see #EDGE_NO_OP
* @see #EDGE_ZERO_FILL
* @see #EDGE_REFLECT
* @see #EDGE_WRAP
*/
public int getEdgeCondition() {
return mEdgeCondition;
}
public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) {
return mConvolve.filter(pSource, pDestination);
}
public BufferedImage createCompatibleDestImage(final BufferedImage pSource, final ColorModel pDesinationColorModel) {
return mConvolve.createCompatibleDestImage(pSource, pDesinationColorModel);
}
public WritableRaster createCompatibleDestRaster(final Raster pSource) {
return mConvolve.createCompatibleDestRaster(pSource);
}
public Rectangle2D getBounds2D(final BufferedImage pSource) {
return mConvolve.getBounds2D(pSource);
}
public Rectangle2D getBounds2D(final Raster pSource) {
return mConvolve.getBounds2D(pSource);
}
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
return mConvolve.getPoint2D(pSourcePoint, pDestinationPoint);
}
public RenderingHints getRenderingHints() {
return mConvolve.getRenderingHints();
}
public Kernel getKernel() {
return mConvolve.getKernel();
}
}

View File

@@ -0,0 +1,298 @@
/*
* 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.image;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RasterOp;
import java.awt.image.WritableRaster;
/**
* This BufferedImageOp simply copies pixels, converting to a
* {@code IndexColorModel}.
* @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/image/CopyDither.java#1 $
*
*/
public class CopyDither implements BufferedImageOp, RasterOp {
protected IndexColorModel mIndexColorModel = null;
/**
* Creates a {@code CopyDither}, using the given
* {@code IndexColorModel} for dithering into.
*
* @param pICM an IndexColorModel.
*/
public CopyDither(IndexColorModel pICM) {
// Store colormodel
mIndexColorModel = pICM;
}
/**
* Creates a {@code CopyDither}, with no fixed
* {@code IndexColorModel}. The colormodel will be generated for each
* filtering, unless the dest image allready has an
* {@code IndexColorModel}.
*/
public CopyDither() {
}
/**
* Creates a compatible {@code BufferedImage} to dither into.
* Only {@code IndexColorModel} allowed.
*
* @return a compatible {@code BufferedImage}
*
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
* an instance of {@code IndexColorModel}.
*/
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
ColorModel pDestCM) {
if (pDestCM == null) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
mIndexColorModel);
}
else if (pDestCM instanceof IndexColorModel) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
(IndexColorModel) pDestCM);
}
else {
throw new ImageFilterException("Only IndexColorModel allowed.");
}
}
/**
* Creates a compatible {@code Raster} to dither into.
* Only {@code IndexColorModel} allowed.
*
* @param pSrc
*
* @return a {@code WritableRaster}
*/
public final WritableRaster createCompatibleDestRaster(Raster pSrc) {
return createCompatibleDestRaster(pSrc, getICM(pSrc));
}
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
IndexColorModel pIndexColorModel) {
/*
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
pIndexColorModel).getRaster();
*/
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
}
/**
* Returns the bounding box of the filtered destination image. Since
* this is not a geometric operation, the bounding box does not
* change.
* @param pSrc the {@code BufferedImage} to be filtered
* @return the bounds of the filtered definition image.
*/
public final Rectangle2D getBounds2D(BufferedImage pSrc) {
return getBounds2D(pSrc.getRaster());
}
/**
* Returns the bounding box of the filtered destination Raster. Since
* this is not a geometric operation, the bounding box does not
* change.
* @param pSrc the {@code Raster} to be filtered
* @return the bounds of the filtered definition {@code Raster}.
*/
public final Rectangle2D getBounds2D(Raster pSrc) {
return pSrc.getBounds();
}
/**
* Returns the location of the destination point given a
* point in the source. If {@code dstPt} is not
* {@code null}, it will be used to hold the return value.
* Since this is not a geometric operation, the {@code srcPt}
* will equal the {@code dstPt}.
* @param pSrcPt a {@code Point2D} that represents a point
* in the source image
* @param pDstPt a {@code Point2D}that represents the location
* in the destination
* @return the {@code Point2D} in the destination that
* corresponds to the specified point in the source.
*/
public final Point2D getPoint2D(Point2D pSrcPt, Point2D pDstPt) {
// Create new Point, if needed
if (pDstPt == null) {
pDstPt = new Point2D.Float();
}
// Copy location
pDstPt.setLocation(pSrcPt.getX(), pSrcPt.getY());
// Return dest
return pDstPt;
}
/**
* Returns the rendering mHints for this op.
* @return the {@code RenderingHints} object associated
* with this op.
*/
public final RenderingHints getRenderingHints() {
return null;
}
/**
* Converts a int triplet to int ARGB.
*/
private static int toIntARGB(int[] pRGB) {
return 0xff000000 // All opaque
| (pRGB[0] << 16)
| (pRGB[1] << 8)
| (pRGB[2]);
/*
| ((int) (pRGB[0] << 16) & 0x00ff0000)
| ((int) (pRGB[1] << 8) & 0x0000ff00)
| ((int) (pRGB[2] ) & 0x000000ff);
*/
}
/**
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource the source image
* @param pDest the destiantion image
*
* @return the destination image, or a new image, if {@code pDest} was
* {@code null}.
*/
public final BufferedImage filter(BufferedImage pSource,
BufferedImage pDest) {
// Create destination image, if none provided
if (pDest == null) {
pDest = createCompatibleDestImage(pSource, getICM(pSource));
}
else if (!(pDest.getColorModel() instanceof IndexColorModel)) {
throw new ImageFilterException("Only IndexColorModel allowed.");
}
// Filter rasters
filter(pSource.getRaster(), pDest.getRaster(), (IndexColorModel) pDest.getColorModel());
return pDest;
}
/**
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource
* @param pDest
*
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
*/
public final WritableRaster filter(final Raster pSource, WritableRaster pDest) {
return filter(pSource, pDest, getICM(pSource));
}
private IndexColorModel getICM(BufferedImage pSource) {
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY));
}
private IndexColorModel getICM(Raster pSource) {
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
}
private IndexColorModel createIndexColorModel(Raster pSource) {
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_INT_ARGB);
image.setData(pSource);
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY);
}
/**
* Performs a single-input/single-output pixel copy operation.
*
* @param pSource
* @param pDest
* @param pColorModel
*
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
*/
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
IndexColorModel pColorModel) {
int width = pSource.getWidth();
int height = pSource.getHeight();
if (pDest == null) {
pDest = createCompatibleDestRaster(pSource, pColorModel);
}
// temp buffers
final int[] inRGB = new int[4];
Object pixel = null;
// TODO: Use getPixels instead of getPixel for better performance?
// Loop through image data
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Get rgb from original raster
// DON'T KNOW IF THIS WILL WORK FOR ALL TYPES..?
pSource.getPixel(x, y, inRGB);
// Get pixel value...
// It is VERY important that we are using an IndexColorModel that
// support reverse color lookup for speed.
pixel = pColorModel.getDataElements(toIntARGB(inRGB), pixel);
// And set it
pDest.setDataElements(x, y, pixel);
}
}
return pDest;
}
}

View File

@@ -0,0 +1,465 @@
package com.twelvemonkeys.image;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RasterOp;
import java.awt.image.WritableRaster;
import java.util.Random;
/**
* This {@code BufferedImageOp/RasterOp} implements basic
* Floyd-Steinberg error-diffusion algorithm for dithering.
* <P/>
* The weights used are 7/16 3/16 5/16 1/16, distributed like this:
* <!-- - -
* | |x|7|
* - - - -
* |3|5|1|
* - - -->
* <P/>
* <TABLE border="1" cellpadding="4" cellspacing="0">
* <TR><TD bgcolor="#000000">&nbsp;</TD><TD class="TableHeadingColor"
* align="center">X</TD><TD>7/16</TD></TR>
* <TR><TD>3/16</TD><TD>5/16</TD><TD>1/16</TD></TR>
* </TABLE>
* <P/>
* See <A href="http://www.awprofessional.com/bookstore/product.asp?isbn=0201848406&rl=1">Computer Graphics (Foley et al.)</a>
* for more information.
*
* @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/image/DiffusionDither.java#1 $
*
*/
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 Random RANDOM = new Random();
/**
* Creates a {@code DiffusionDither}, using the given
* {@code IndexColorModel} for dithering into.
*
* @param pICM an IndexColorModel.
*/
public DiffusionDither(IndexColorModel pICM) {
// Store colormodel
mIndexColorModel = pICM;
}
/**
* Creates a {@code DiffusionDither}, with no fixed
* {@code IndexColorModel}. The colormodel will be generated for each
* filtering, unless the dest image allready has an
* {@code IndexColorModel}.
*/
public DiffusionDither() {
}
/**
* Sets the scan mode. If the parameter is true, error distribution for
* every even line will be left-to-right, while odd lines will be
* right-to-left.
*
* @param pUse {@code true} if scan mode should be alternating left/right
*/
public void setAlternateScans(boolean pUse) {
mAlternateScans = pUse;
}
/**
* Creates a compatible {@code BufferedImage} to dither into.
* Only {@code IndexColorModel} allowed.
*
* @return a compatible {@code BufferedImage}
*
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
* an instance of {@code IndexColorModel}.
*/
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
ColorModel pDestCM) {
if (pDestCM == null) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
getICM(pSource));
}
else if (pDestCM instanceof IndexColorModel) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
(IndexColorModel) pDestCM);
}
else {
throw new ImageFilterException("Only IndexColorModel allowed.");
}
}
/**
* Creates a compatible {@code Raster} to dither into.
* Only {@code IndexColorModel} allowed.
*
* @param pSrc
*
* @return a {@code WritableRaster}
*/
public final WritableRaster createCompatibleDestRaster(Raster pSrc) {
return createCompatibleDestRaster(pSrc, getICM(pSrc));
}
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
IndexColorModel pIndexColorModel) {
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
/*
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
pIndexColorModel).getRaster();
*/
}
/**
* Returns the bounding box of the filtered destination image. Since
* this is not a geometric operation, the bounding box does not
* change.
* @param pSrc the {@code BufferedImage} to be filtered
* @return the bounds of the filtered definition image.
*/
public final Rectangle2D getBounds2D(BufferedImage pSrc) {
return getBounds2D(pSrc.getRaster());
}
/**
* Returns the bounding box of the filtered destination Raster. Since
* this is not a geometric operation, the bounding box does not
* change.
* @param pSrc the {@code Raster} to be filtered
* @return the bounds of the filtered definition {@code Raster}.
*/
public final Rectangle2D getBounds2D(Raster pSrc) {
return pSrc.getBounds();
}
/**
* Returns the location of the destination point given a
* point in the source. If {@code dstPt} is not
* {@code null}, it will be used to hold the return value.
* Since this is not a geometric operation, the {@code srcPt}
* will equal the {@code dstPt}.
* @param pSrcPt a {@code Point2D} that represents a point
* in the source image
* @param pDstPt a {@code Point2D}that represents the location
* in the destination
* @return the {@code Point2D} in the destination that
* corresponds to the specified point in the source.
*/
public final Point2D getPoint2D(Point2D pSrcPt, Point2D pDstPt) {
// Create new Point, if needed
if (pDstPt == null) {
pDstPt = new Point2D.Float();
}
// Copy location
pDstPt.setLocation(pSrcPt.getX(), pSrcPt.getY());
// Return dest
return pDstPt;
}
/**
* Returns the rendering mHints for this op.
* @return the {@code RenderingHints} object associated
* with this op.
*/
public final RenderingHints getRenderingHints() {
return null;
}
/**
* Converts an int ARGB to int triplet.
*/
private static int[] toRGBArray(int pARGB, int[] pBuffer) {
pBuffer[0] = ((pARGB & 0x00ff0000) >> 16);
pBuffer[1] = ((pARGB & 0x0000ff00) >> 8);
pBuffer[2] = ((pARGB & 0x000000ff));
//pBuffer[3] = ((pARGB & 0xff000000) >> 24); // alpha
return pBuffer;
}
/**
* Converts a int triplet to int ARGB.
*/
private static int toIntARGB(int[] pRGB) {
return 0xff000000 // All opaque
| (pRGB[0] << 16)
| (pRGB[1] << 8)
| (pRGB[2]);
/*
| ((int) (pRGB[0] << 16) & 0x00ff0000)
| ((int) (pRGB[1] << 8) & 0x0000ff00)
| ((int) (pRGB[2] ) & 0x000000ff);
*/
}
/**
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource the source image
* @param pDest the destiantion image
*
* @return the destination image, or a new image, if {@code pDest} was
* {@code null}.
*/
public final BufferedImage filter(BufferedImage pSource,
BufferedImage pDest) {
// Create destination image, if none provided
if (pDest == null) {
pDest = createCompatibleDestImage(pSource, getICM(pSource));
}
else if (!(pDest.getColorModel() instanceof IndexColorModel)) {
throw new ImageFilterException("Only IndexColorModel allowed.");
}
// Filter rasters
filter(pSource.getRaster(), pDest.getRaster(), (IndexColorModel) pDest.getColorModel());
return pDest;
}
/**
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource
* @param pDest
*
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
*/
public final WritableRaster filter(final Raster pSource, WritableRaster pDest) {
return filter(pSource, pDest, getICM(pSource));
}
private IndexColorModel getICM(BufferedImage pSource) {
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK));
}
private IndexColorModel getICM(Raster pSource) {
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
}
private IndexColorModel createIndexColorModel(Raster pSource) {
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_INT_ARGB);
image.setData(pSource);
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK);
}
/**
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource
* @param pDest
* @param pColorModel
*
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
*/
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
IndexColorModel pColorModel) {
int width = pSource.getWidth();
int height = pSource.getHeight();
// Create destination raster if needed
if (pDest == null) {
pDest = createCompatibleDestRaster(pSource, pColorModel);
}
// Initialize Floyd-Steinberg error vectors.
// +2 to handle the previous pixel and next pixel case minimally
// When reference for column, add 1 to reference as this buffer is
// offset from actual column position by one to allow FS to not check
// left/right edge conditions
int[][] mCurrErr = new int[width + 2][3];
int[][] mNextErr = new int[width + 2][3];
// Random errors in [-1 .. 1] - for first row
for (int i = 0; i < width + 2; i++) {
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
/*
mCurrErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
mCurrErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
mCurrErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
*/
mCurrErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
mCurrErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
mCurrErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
}
// Temp buffers
final int[] diff = new int[3]; // No alpha
final int[] inRGB = new int[4];
final int[] outRGB = new int[4];
Object pixel = null;
boolean forward = true;
// Loop through image data
for (int y = 0; y < height; y++) {
// Clear out next error rows for colour errors
for (int i = mNextErr.length; --i >= 0;) {
mNextErr[i][0] = 0;
mNextErr[i][1] = 0;
mNextErr[i][2] = 0;
}
// Set up start column and limit
int x;
int limit;
if (forward) {
x = 0;
limit = width;
}
else {
x = width - 1;
limit = -1;
}
// TODO: Use getPixels instead of getPixel for better performance?
// Loop over row
while (true) {
// Get RGB from original raster
// DON'T KNOW IF THIS WILL WORK FOR ALL TYPES.
pSource.getPixel(x, y, inRGB);
// Get error for this pixel & add error to rgb
for (int i = 0; i < 3; i++) {
// Make a 28.4 FP number, add Error (with fraction),
// rounding and truncate to int
inRGB[i] = ((inRGB[i] << 4) + mCurrErr[x + 1][i] + 0x08) >> 4;
// Clamp
if (inRGB[i] > 255) {
inRGB[i] = 255;
}
else if (inRGB[i] < 0) {
inRGB[i] = 0;
}
}
// Get pixel value...
// It is VERY important that we are using a IndexColorModel that
// support reverse color lookup for speed.
pixel = pColorModel.getDataElements(toIntARGB(inRGB), pixel);
// ...set it...
pDest.setDataElements(x, y, pixel);
// ..and get back the closet match
pDest.getPixel(x, y, outRGB);
// Convert the value to default sRGB
// Should work for all transfertypes supported by IndexColorModel
toRGBArray(pColorModel.getRGB(outRGB[0]), outRGB);
// Find diff
diff[0] = inRGB[0] - outRGB[0];
diff[1] = inRGB[1] - outRGB[1];
diff[2] = inRGB[2] - outRGB[2];
// Apply F-S error diffusion
// Serpentine scan: left-right
if (forward) {
// Row 1 (y)
// Update error in this pixel (x + 1)
mCurrErr[x + 2][0] += diff[0] * 7;
mCurrErr[x + 2][1] += diff[1] * 7;
mCurrErr[x + 2][2] += diff[2] * 7;
// Row 2 (y + 1)
// Update error in this pixel (x - 1)
mNextErr[x][0] += diff[0] * 3;
mNextErr[x][1] += diff[1] * 3;
mNextErr[x][2] += diff[2] * 3;
// Update error in this pixel (x)
mNextErr[x + 1][0] += diff[0] * 5;
mNextErr[x + 1][1] += diff[1] * 5;
mNextErr[x + 1][2] += diff[2] * 5;
// Update error in this pixel (x + 1)
// TODO: Consider calculating this using
// error term = error - sum(error terms 1, 2 and 3)
// See Computer Graphics (Foley et al.), p. 573
mNextErr[x + 2][0] += diff[0]; // * 1;
mNextErr[x + 2][1] += diff[1]; // * 1;
mNextErr[x + 2][2] += diff[2]; // * 1;
// Next
x++;
// Done?
if (x >= limit) {
break;
}
}
else {
// Row 1 (y)
// Update error in this pixel (x - 1)
mCurrErr[x][0] += diff[0] * 7;
mCurrErr[x][1] += diff[1] * 7;
mCurrErr[x][2] += diff[2] * 7;
// Row 2 (y + 1)
// Update error in this pixel (x + 1)
mNextErr[x + 2][0] += diff[0] * 3;
mNextErr[x + 2][1] += diff[1] * 3;
mNextErr[x + 2][2] += diff[2] * 3;
// Update error in this pixel (x)
mNextErr[x + 1][0] += diff[0] * 5;
mNextErr[x + 1][1] += diff[1] * 5;
mNextErr[x + 1][2] += diff[2] * 5;
// Update error in this pixel (x - 1)
// TODO: Consider calculating this using
// error term = error - sum(error terms 1, 2 and 3)
// See Computer Graphics (Foley et al.), p. 573
mNextErr[x][0] += diff[0]; // * 1;
mNextErr[x][1] += diff[1]; // * 1;
mNextErr[x][2] += diff[2]; // * 1;
// Previous
x--;
// Done?
if (x <= limit) {
break;
}
}
}
// Make next error info current for next iteration
int[][] temperr;
temperr = mCurrErr;
mCurrErr = mNextErr;
mNextErr = temperr;
// Toggle direction
if (mAlternateScans) {
forward = !forward;
}
}
return pDest;
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.image;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.*;
import java.awt.image.renderable.RenderableImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* EasyImage
* <p/>
*
* @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/EasyImage.java#1 $
*/
public class EasyImage extends BufferedImage {
public EasyImage(InputStream pInput) throws IOException {
this(ImageIO.read(pInput));
}
public EasyImage(BufferedImage pImage) {
this(pImage.getColorModel(), pImage.getRaster());
}
public EasyImage(RenderableImage pImage) {
this(pImage.createDefaultRendering());
}
public EasyImage(RenderedImage pImage) {
this(pImage.getColorModel(), pImage.copyData(pImage.getColorModel().createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight())));
}
public EasyImage(ImageProducer pImage) {
this(new BufferedImageFactory(pImage).getBufferedImage());
}
public EasyImage(Image pImage) {
this(new BufferedImageFactory(pImage).getBufferedImage());
}
private EasyImage(ColorModel cm, WritableRaster raster) {
super(cm, raster, cm.isAlphaPremultiplied(), null);
}
public boolean write(String pFormat, OutputStream pOutput) throws IOException {
return ImageIO.write(this, pFormat, pOutput);
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.image;
import java.awt.image.ImageConsumer;
import java.awt.image.ColorModel;
/**
* ExtendedImageConsumer
* <p/>
*
* @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/ExtendedImageConsumer.java#1 $
*/
public interface ExtendedImageConsumer extends ImageConsumer {
/**
*
* @param pX
* @param pY
* @param pWidth
* @param pHeight
* @param pModel
* @param pPixels
* @param pOffset
* @param pScanSize
*/
public void setPixels(int pX, int pY, int pWidth, int pHeight,
ColorModel pModel,
short[] pPixels, int pOffset, int pScanSize);
// Allow for packed and interleaved models
public void setPixels(int pX, int pY, int pWidth, int pHeight,
ColorModel pModel,
byte[] pPixels, int pOffset, int pScanSize);
}

View File

@@ -0,0 +1,82 @@
/*
* 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.image;
import java.awt.*;
/**
* GraphicsUtil
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/image/GraphicsUtil.java#1 $
*/
public final class GraphicsUtil {
/**
* Enables anti-aliasing in the {@code Graphics} object.
* <p/>
* Anti-aliasing is enabled by casting to {@code Graphics2D} and setting
* the rendering hint {@code RenderingHints.KEY_ANTIALIASING} to
* {@code RenderingHints.VALUE_ANTIALIAS_ON}.
*
* @param pGraphics the graphics object
* @throws ClassCastException if {@code pGraphics} is not an instance of
* {@code Graphics2D}.
*
* @see java.awt.RenderingHints#KEY_ANTIALIASING
*/
public static void enableAA(final Graphics pGraphics) {
((Graphics2D) pGraphics).setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
);
}
/**
* Sets the alpha in the {@code Graphics} object.
* <p/>
* Alpha is set by casting to {@code Graphics2D} and setting the composite
* to the rule {@code AlphaComposite.SRC_OVER} multiplied by the given
* alpha.
*
* @param pGraphics the graphics object
* @param pAlpha the alpha level, {@code alpha} must be a floating point
* number in the inclusive range [0.0,&nbsp;1.0].
* @throws ClassCastException if {@code pGraphics} is not an instance of
* {@code Graphics2D}.
*
* @see java.awt.AlphaComposite#SRC_OVER
* @see java.awt.AlphaComposite#getInstance(int, float)
*/
public static void setAlpha(final Graphics pGraphics, final float pAlpha) {
((Graphics2D) pGraphics).setComposite(
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, pAlpha)
);
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.image;
import java.awt.image.*;
/**
* This class represents a 256 color fixed grayscale IndexColorModel.
*
* @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/image/GrayColorModel.java#1 $
*
*/
public class GrayColorModel extends IndexColorModel {
private final static byte[] sGrays = createGrayScale();
public GrayColorModel() {
super(8, sGrays.length, sGrays, sGrays, sGrays);
}
private static byte[] createGrayScale() {
byte[] grays = new byte[256];
for (int i = 0; i < 256; i++) {
grays[i] = (byte) i;
}
return grays;
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.image;
import java.awt.image.*;
/**
* This class can convert a color image to grayscale.
* <P/>
* Uses ITU standard conversion: (222 * Red + 707 * Green + 71 * Blue) / 1000.
*
* @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/image/GrayFilter.java#1 $
*
*/
public class GrayFilter extends RGBImageFilter {
// This filter can filter IndexColorModel
{
canFilterIndexColorModel = true;
}
private int mLow = 0;
private float mRange = 1.0f;
/**
* Constructs a GrayFilter using ITU color-conversion.
*/
public GrayFilter() {
}
/**
* Constructs a GrayFilter using ITU color-conversion, and a dynamic range between
* pLow and pHigh.
*
* @param pLow float in the range 0..1
* @param pHigh float in the range 0..1 and >= pLow
*/
public GrayFilter(float pLow, float pHigh) {
if (pLow > pHigh) {
pLow = 0f;
}
// Make sure high and low are inside range
if (pLow < 0f) {
pLow = 0f;
}
else if (pLow > 1f) {
pLow = 1f;
}
if (pHigh < 0f) {
pHigh = 0f;
}
else if (pHigh > 1f) {
pHigh = 1f;
}
mLow = (int) (pLow * 255f);
mRange = pHigh - pLow;
}
/**
* Constructs a GrayFilter using ITU color-conversion, and a dynamic
* range between pLow and pHigh.
*
* @param pLow integer in the range 0..255
* @param pHigh inteeger in the range 0..255 and >= pLow
*/
public GrayFilter(int pLow, int pHigh) {
this(pLow / 255f, pHigh / 255f);
}
/**
* Filters one pixel using ITU color-conversion.
*
* @param pX x
* @param pY y
* @param pARGB pixel value in default color space
*
* @return the filtered pixel value in the default color space
*/
public int filterRGB(int pX, int pY, int pARGB) {
// Get color components
int r = pARGB >> 16 & 0xFF;
int g = pARGB >> 8 & 0xFF;
int b = pARGB & 0xFF;
// ITU standard: Gray scale=(222*Red+707*Green+71*Blue)/1000
int gray = (222 * r + 707 * g + 71 * b) / 1000;
//int gray = (int) ((float) (r + g + b) / 3.0f);
if (mRange != 1.0f) {
// Apply range
gray = mLow + (int) (gray * mRange);
}
// Return ARGB pixel
return (pARGB & 0xFF000000) | (gray << 16) | (gray << 8) | gray;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.image;
/**
* This class wraps IllegalArgumentException, and is thrown by the ImageUtil
* class, when trying to convert images read from {@code null}-sources etc.
*
* @author Harald Kuhr
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageConversionException.java#1 $
*/
public class ImageConversionException extends ImageFilterException {
public ImageConversionException(String pMessage) {
super(pMessage);
}
public ImageConversionException(String pMessage, Throwable pCause) {
super(pMessage, pCause);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.image;
/**
* This class wraps IllegalArgumentException as thrown by the
* BufferedImageOp interface for more fine-grained control.
*
* @author Harald Kuhr
*
* @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 {
private Throwable mCause = null;
public ImageFilterException(String pStr) {
super(pStr);
}
public ImageFilterException(Throwable pT) {
initCause(pT);
}
public ImageFilterException(String pStr, Throwable pT) {
super(pStr);
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;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
/*
* 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.image;
/**
* Inverse Colormap to provide efficient lookup of any given input color
* to the closest match to the given color map.
* <p/>
* Based on "Efficient Inverse Color Map Computation" by Spencer W. Thomas
* in "Graphics Gems Volume II"
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author Robin Luiten (Java port)
* @author Spencer W. Thomas (original c version).
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/InverseColorMap.java#1 $
*/
class InverseColorMap {
/**
* Number of high bits of each color channel to use to lookup near match
*/
final static int QUANTBITS = 5;
/**
* Truncated bits of each color channel
*/
final static int TRUNCBITS = 8 - QUANTBITS;
/**
* BITMASK representing the bits for blue in the color lookup
*/
final static int QUANTMASK_BLUE = (1 << 5) - 1;
/**
* BITMASK representing the bits for green in the color lookup
*/
final static int QUANTMASK_GREEN = (QUANTMASK_BLUE << QUANTBITS);
/**
* BITMASK representing the bits for red in the color lookup
*/
final static int QUANTMASK_RED = (QUANTMASK_GREEN << QUANTBITS);
/**
* Maximum value a quantised color channel can have
*/
final static int MAXQUANTVAL = 1 << 5;
byte[] mRGBMapByte;
int[] mRGBMapInt;
int mNumColors;
int mMaxColor;
byte[] mInverseRGB; // inverse rgb color map
int mTransparentIndex = -1;
/**
* @param pRGBColorMap the rgb color map to create inverse color map for.
*/
InverseColorMap(byte[] pRGBColorMap) {
this(pRGBColorMap, -1);
}
/**
* @param pRGBColorMap the rgb color map to create inverse color map for.
*/
// HaraldK 20040801: Added support for int[]
InverseColorMap(int[] pRGBColorMap) {
this(pRGBColorMap, -1);
}
/**
* @param pRGBColorMap the rgb color map to create inverse color map for.
* @param pTransparent the index of the transparent pixel in the map
*/
InverseColorMap(byte[] pRGBColorMap, int pTransparent) {
mRGBMapByte = pRGBColorMap;
mNumColors = mRGBMapByte.length / 4;
mTransparentIndex = pTransparent;
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
}
/**
* @param pRGBColorMap the rgb color map to create inverse color map for.
* @param pTransparent the index of the transparent pixel in the map
*/
InverseColorMap(int[] pRGBColorMap, int pTransparent) {
mRGBMapInt = pRGBColorMap;
mNumColors = mRGBMapInt.length;
mTransparentIndex = pTransparent;
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
}
/**
* Simple inverse color table creation method.
* @param pTemp temp array
*/
void initIRGB(int[] pTemp) {
final int x = (1 << TRUNCBITS); // 8 the size of 1 Dimension of each quantized cell
final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors
final int xsqr2 = xsqr + xsqr;
for (int i = 0; i < mNumColors; ++i) {
if (i == mTransparentIndex) {
// Skip the transparent pixel
continue;
}
int red, r, rdist, rinc, rxx;
int green, g, gdist, ginc, gxx;
int blue, b, bdist, binc, bxx;
// HaraldK 20040801: Added support for int[]
if (mRGBMapByte != null) {
red = mRGBMapByte[i * 4] & 0xFF;
green = mRGBMapByte[i * 4 + 1] & 0xFF;
blue = mRGBMapByte[i * 4 + 2] & 0xFF;
}
else if (mRGBMapInt != null) {
red = (mRGBMapInt[i] >> 16) & 0xFF;
green = (mRGBMapInt[i] >> 8) & 0xFF;
blue = mRGBMapInt[i] & 0xFF;
}
else {
throw new IllegalStateException("colormap == null");
}
rdist = red - x / 2; // distance of red to center of current cell
gdist = green - x / 2; // green
bdist = blue - x / 2; // blue
rdist = rdist * rdist + gdist * gdist + bdist * bdist;
rinc = 2 * (xsqr - (red << TRUNCBITS));
ginc = 2 * (xsqr - (green << TRUNCBITS));
binc = 2 * (xsqr - (blue << TRUNCBITS));
int rgbI = 0;
for (r = 0, rxx = rinc; r < MAXQUANTVAL; rdist += rxx, ++r, rxx += xsqr2) {
for (g = 0, gdist = rdist, gxx = ginc; g < MAXQUANTVAL; gdist += gxx, ++g, gxx += xsqr2) {
for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) {
if (i == 0 || pTemp[rgbI] > bdist) {
pTemp[rgbI] = bdist;
mInverseRGB[rgbI] = (byte) i;
}
}
}
}
}
}
/**
* Gets the index of the nearest color to from the color map.
*
* @param pColor the color to get the nearest color to from color map
* color must be of format {@code 0x00RRGGBB} - standard default RGB
* @return index of color which closest matches input color by using the
* created inverse color map.
*/
public final int getIndexNearest(int pColor) {
return mInverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) +
((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) +
((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
}
/**
* Gets the index of the nearest color to from the color map.
*
* @param pRed red component of the color to get the nearest color to from color map
* @param pGreen green component of the color to get the nearest color to from color map
* @param pBlue blue component of the color to get the nearest color to from color map
* @return index of color which closest matches input color by using the
* created inverse color map.
*/
public final int getIndexNearest(int pRed, int pGreen, int pBlue) {
// NOTE: the third line in expression for blue is shifting DOWN not UP.
return mInverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) +
((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) +
((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
}
}

View File

@@ -0,0 +1,310 @@
/*
* 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.image;
import com.twelvemonkeys.lang.StringUtil;
import java.awt.*;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
/**
* A faster implementation of {@code IndexColorModel}, that is backed by an
* inverse color-map, for fast lookups.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java#1 $
*
*/
public class InverseColorMapIndexColorModel extends IndexColorModel {
protected int mRGBs[];
protected int mMapSize;
protected InverseColorMap mInverseMap = null;
private final static int ALPHA_THRESHOLD = 0x80;
private int mWhiteIndex = -1;
private final static int WHITE = 0x00FFFFFF;
private final static int RGB_MASK = 0x00FFFFFF;
/**
* Creates an {@code InverseColorMapIndexColorModel} from an existing
* {@code IndexColorModel}.
*
* @param pColorModel the colormodel to create from
*/
public InverseColorMapIndexColorModel(IndexColorModel pColorModel) {
this(pColorModel, getRGBs(pColorModel));
}
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
// times. What is wrong with protected?!
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(),
pRGBs, 0,
ImageUtil.getTransferType(pColorModel),
pColorModel.getValidPixels());
mRGBs = pRGBs;
mMapSize = mRGBs.length;
mInverseMap = new InverseColorMap(mRGBs);
mWhiteIndex = getWhiteIndex();
}
/**
* Creates a defensive copy of the RGB colormap in the given
* {@code IndexColorModel}.
*
* @param pColorModel the indec colormodel to get RGB values from
* @return the RGB colormap
*/
private static int[] getRGBs(IndexColorModel pColorModel) {
int[] rgb = new int[pColorModel.getMapSize()];
pColorModel.getRGBs(rgb);
return rgb;
}
/**
* Creates an {@code InverseColorMapIndexColorModel} from the given array
* of RGB components, plus one transparent index.
*
* @param pNumBits the number of bits each pixel occupies
* @param pSize the size of the color component arrays
* @param pRGBs the array of packed RGB color components
* @param pStart the starting offset of the first color component
* @param pAlpha indicates whether alpha values are contained in {@code pRGBs}
* @param pTransparentIndex the index of the transparent pixel
* @param pTransferType the data type of the array used to represent pixels
*
* @throws IllegalArgumentException if bits is less than 1 or greater than 16,
* or if size is less than 1
*
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
*/
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs,
int pStart, boolean pAlpha, int pTransparentIndex,
int pTransferType) {
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
mRGBs = getRGBs(this);
mMapSize = mRGBs.length;
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
mWhiteIndex = getWhiteIndex();
}
/**
* Creates an {@code InverseColorMapIndexColorModel} from the given arrays
* of red, green, and blue components, plus one transparent index.
*
* @param pNumBits the number of bits each pixel occupies
* @param pSize the size of the color component arrays
* @param pReds the array of red color components
* @param pGreens the array of green color components
* @param pBlues the array of blue color components
* @param pTransparentIndex the index of the transparent pixel
*
* @throws IllegalArgumentException if bits is less than 1 or greater than 16,
* or if size is less than 1
*
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
*/
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
byte[] pReds, byte[] pGreens, byte[] pBlues,
int pTransparentIndex) {
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
mRGBs = getRGBs(this);
mMapSize = mRGBs.length;
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
mWhiteIndex = getWhiteIndex();
}
/**
* Creates an {@code InverseColorMapIndexColorModel} from the given arrays
* of red, green, and blue components.
*
* @param pNumBits the number of bits each pixel occupies
* @param pSize the size of the color component arrays
* @param pReds the array of red color components
* @param pGreens the array of green color components
* @param pBlues the array of blue color components
*
* @throws IllegalArgumentException if bits is less than 1 or greater than 16,
* or if size is less than 1
*
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
*/
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
byte[] pReds, byte[] pGreens, byte[] pBlues) {
super(pNumBits, pSize, pReds, pGreens, pBlues);
mRGBs = getRGBs(this);
mMapSize = mRGBs.length;
mInverseMap = new InverseColorMap(mRGBs);
mWhiteIndex = getWhiteIndex();
}
private int getWhiteIndex() {
for (int i = 0; i < mRGBs.length; i++) {
int color = mRGBs[i];
if ((color & RGB_MASK) == WHITE) {
return i;
}
}
return -1;
}
/**
* Creates an {@code IndexColorModel} optimized for the given {@code Image}.
*
* @param pImage the {@code Image} containing the RGB samples
* @param pNumCols the maximum number of colors in the {@code IndexColorModel}
* @param pFlags flags
*
* @return a new optimized {@code IndexColorModel}
*/
public static IndexColorModel create(Image pImage, int pNumCols, int pFlags) {
// TODO: Inline and deprecate IndexImage.getIndexColorModel!?
IndexColorModel icm = IndexImage.getIndexColorModel(pImage, pNumCols, pFlags);
InverseColorMapIndexColorModel cm;
if (icm instanceof InverseColorMapIndexColorModel) {
cm = (InverseColorMapIndexColorModel) icm;
}
else {
cm = new InverseColorMapIndexColorModel(icm);
}
return cm;
}
/**
* Returns a data element array representation of a pixel in this
* ColorModel, given an integer pixel representation in the
* default RGB color model. This array can then be passed to the
* {@link java.awt.image.WritableRaster#setDataElements(int, int, Object) setDataElements}
* method of a {@link java.awt.image.WritableRaster} object. If the pixel variable is
* {@code null}, a new array is allocated. If {@code pixel}
* is not {@code null}, it must be
* a primitive array of type {@code transferType}; otherwise, a
* {@code ClassCastException} is thrown. An
* {@code ArrayIndexOutOfBoundsException} is
* thrown if {@code pixel} is not large enough to hold a pixel
* value for this {@code ColorModel}. The pixel array is returned.
* <p>
* Since {@code OpaqueIndexColorModel} can be subclassed, subclasses
* inherit the implementation of this method and if they don't
* override it then they throw an exception if they use an
* unsupported {@code transferType}.
*
* #param rgb the integer pixel representation in the default RGB
* color model
* #param pixel the specified pixel
* #return an array representation of the specified pixel in this
* {@code OpaqueIndexColorModel}.
* #throws ClassCastException if {@code pixel}
* is not a primitive array of type {@code transferType}
* #throws ArrayIndexOutOfBoundsException if
* {@code pixel} is not large enough to hold a pixel value
* for this {@code ColorModel}
* #throws UnsupportedOperationException if {@code transferType}
* is invalid
* @see java.awt.image.WritableRaster#setDataElements
* @see java.awt.image.SampleModel#setDataElements
*
*/
public Object getDataElements(int rgb, Object pixel) {
int alpha = (rgb>>>24);
int pix;
if (alpha < ALPHA_THRESHOLD && getTransparentPixel() != -1) {
pix = getTransparentPixel();
}
else {
int color = rgb & RGB_MASK;
if (color == WHITE && mWhiteIndex != -1) {
pix = mWhiteIndex;
}
else {
pix = mInverseMap.getIndexNearest(color);
}
}
return installpixel(pixel, pix);
}
private Object installpixel(Object pixel, int pix) {
switch (transferType) {
case DataBuffer.TYPE_INT:
int[] intObj;
if (pixel == null) {
pixel = intObj = new int[1];
}
else {
intObj = (int[]) pixel;
}
intObj[0] = pix;
break;
case DataBuffer.TYPE_BYTE:
byte[] byteObj;
if (pixel == null) {
pixel = byteObj = new byte[1];
}
else {
byteObj = (byte[]) pixel;
}
byteObj[0] = (byte) pix;
break;
case DataBuffer.TYPE_USHORT:
short[] shortObj;
if (pixel == null) {
pixel = shortObj = new short[1];
}
else {
shortObj = (short[]) pixel;
}
shortObj[0] = (short) pix;
break;
default:
throw new UnsupportedOperationException("This method has not been " +
"implemented for transferType " + transferType);
}
return pixel;
}
public String toString() {
// Just a workaround to ease debugging
return StringUtil.replace(super.toString(), "IndexColorModel: ", getClass().getName() + ": ");
}
}

View File

@@ -0,0 +1,24 @@
package com.twelvemonkeys.image;
/**
* Magick
*
* @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/image/Magick.java#1 $
*/
final class Magick {
static final boolean DEBUG = useDebug();
private static boolean useDebug() {
try {
return "TRUE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.magick.debug"));
}
catch (Throwable t) {
// Most probably in case of a SecurityManager
return false;
}
}
private Magick() {}
}

View File

@@ -0,0 +1,184 @@
package com.twelvemonkeys.image;
/*
* 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.
*/
import com.twelvemonkeys.lang.SystemUtil;
import magick.MagickImage;
import java.awt.image.*;
/**
* This class accelerates certain graphics operations, using
* JMagick and ImageMagick, if available.
* If those libraries are not installed, this class silently does nothing.
* <p/>
* Set the system property {@code "com.twelvemonkeys.image.accel"} to
* {@code false}, to disable, even if JMagick is installed.
* Set the system property {@code "com.twelvemonkeys.image.magick.debug"} to
* <p/>
*
* @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/MagickAccelerator.java#3 $
*/
final class MagickAccelerator {
private static final boolean DEBUG = Magick.DEBUG;
private static final boolean USE_MAGICK = useMagick();
private static final int RESAMPLE_OP = 0;
private static Class[] sNativeOp = new Class[1];
static {
try {
sNativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
}
catch (ClassNotFoundException e) {
System.err.println("Could not find class: " + e);
}
}
private static boolean useMagick() {
try {
boolean available = SystemUtil.isClassAvailable("magick.MagickImage");
if (DEBUG && !available) {
System.err.print("ImageMagick bindings not available.");
}
boolean useMagick =
available && !"FALSE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.accel"));
if (DEBUG) {
System.err.println(
useMagick
? "Will use ImageMagick bindings to accelerate image resampling operations."
: "Will not use ImageMagick to accelerate image resampling operations."
);
}
return useMagick;
}
catch (Throwable t) {
// Most probably in case of a SecurityManager
System.err.println("Could not enable ImageMagick bindings: " + t);
return false;
}
}
private static int getNativeOpIndex(Class pOpClass) {
for (int i = 0; i < sNativeOp.length; i++) {
if (pOpClass == sNativeOp[i]) {
return i;
}
}
return -1;
}
public static BufferedImage filter(BufferedImageOp pOperation, BufferedImage pInput, BufferedImage pOutput) {
if (!USE_MAGICK) {
return null;
}
BufferedImage result = null;
switch (getNativeOpIndex(pOperation.getClass())) {
case RESAMPLE_OP:
ResampleOp resample = (ResampleOp) pOperation;
result = resampleMagick(pInput, resample.mWidth, resample.mHeight, resample.mFilterType);
// NOTE: If output parameter is non-null, we have to return that
// image, instead of result
if (pOutput != null) {
//pOutput.setData(result.getRaster()); // Fast, but less compatible
// NOTE: For some reason, this is sometimes super-slow...?
ImageUtil.drawOnto(pOutput, result);
result = pOutput;
}
break;
default:
// Simply fall through, allowing acceleration to be added later
break;
}
return result;
}
private static BufferedImage resampleMagick(BufferedImage pSrc, int pWidth, int pHeight, int pFilterType) {
// Convert to Magick, scale and convert back
MagickImage image = null;
MagickImage scaled = null;
try {
image = MagickUtil.toMagick(pSrc);
long start = 0;
if (DEBUG) {
start = System.currentTimeMillis();
}
// NOTE: setFilter affects zoomImage, NOT scaleImage
image.setFilter(pFilterType);
scaled = image.zoomImage(pWidth, pHeight);
//scaled = image.scaleImage(pWidth, pHeight); // AREA_AVERAGING
if (DEBUG) {
long time = System.currentTimeMillis() - start;
System.out.println("Filtered: " + time + " ms");
}
return MagickUtil.toBuffered(scaled);
}
//catch (MagickException e) {
catch (Exception e) {
// NOTE: Stupid workaround: If MagickException is caught, a
// NoClassDefFoundError is thrown, when MagickException class is
// unavailable...
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new ImageConversionException(e.getMessage(), e);
}
finally {
// NOTE: ImageMagick might be unstable after a while, if image data
// is not deallocated. The GC/finalize method handles this, but in
// special circumstances, it's not triggered often enough.
if (image != null) {
image.destroyImages();
}
if (scaled != null) {
scaled.destroyImages();
}
}
}
}

View File

@@ -0,0 +1,549 @@
/*
* 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.image;
import magick.*;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
/**
* Utility for converting JMagick {@code MagickImage}s to standard Java
* {@code BufferedImage}s and back.
* <p/>
* <em>NOTE: This class is considered an implementation detail and not part of
* the public API. This class is subject to change without further notice.
* You have been warned. :-)</em>
*
* @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/MagickUtil.java#4 $
*/
public final class MagickUtil {
// IMPORTANT NOTE: Disaster happens if any of these constants are used outside this class
// because you then have a dependency on MagickException (this is due to Java class loading
// and initialization magic).
// Do not use outside this class. If the constants need to be shared, move to Magick or ImageUtil.
/** Color Model usesd for bilevel (B/W) */
private static final IndexColorModel CM_MONOCHROME = MonochromeColorModel.getInstance();
/** Color Model usesd for raw ABGR */
private static final ColorModel CM_COLOR_ALPHA =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8},
true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
/** Color Model usesd for raw BGR */
private static final ColorModel CM_COLOR_OPAQUE =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8},
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
/** Color Model usesd for raw RGB */
//private static final ColorModel CM_COLOR_RGB = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0);
/** Color Model usesd for raw GRAY + ALPHA */
private static final ColorModel CM_GRAY_ALPHA =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
/** Color Model usesd for raw GRAY */
private static final ColorModel CM_GRAY_OPAQUE =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
/** Band offsets for raw ABGR */
private static final int[] BAND_OFF_TRANS = new int[] {3, 2, 1, 0};
/** Band offsets for raw BGR */
private static final int[] BAND_OFF_OPAQUE = new int[] {2, 1, 0};
/** The point at {@code 0, 0} */
private static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
private static final boolean DEBUG = Magick.DEBUG;
// Only static members and methods
private MagickUtil() {}
/**
* Converts a {@code MagickImage} to a {@code BufferedImage}.
* <p/>
* The conversion depends on {@code pImage}'s {@code ImageType}:
* <dl>
* <dt>{@code ImageType.BilevelType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}</dd>
*
* <dt>{@code ImageType.GrayscaleType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}</dd>
* <dt>{@code ImageType.GrayscaleMatteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}</dd>
*
* <dt>{@code ImageType.PaletteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
* <dt>{@code ImageType.PaletteMatteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
*
* <dt>{@code ImageType.TrueColorType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}</dd>
* <dt>{@code ImageType.TrueColorPaletteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}</dd>
*
* @param pImage the original {@code MagickImage}
* @return a new {@code BufferedImage}
*
* @throws IllegalArgumentException if {@code pImage} is {@code null}
* or if the {@code ImageType} is not one mentioned above.
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
public static BufferedImage toBuffered(MagickImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
long start = 0L;
if (DEBUG) {
start = System.currentTimeMillis();
}
BufferedImage image = null;
try {
switch (pImage.getImageType()) {
case ImageType.BilevelType:
image = bilevelToBuffered(pImage);
break;
case ImageType.GrayscaleType:
image = grayToBuffered(pImage, false);
break;
case ImageType.GrayscaleMatteType:
image = grayToBuffered(pImage, true);
break;
case ImageType.PaletteType:
image = paletteToBuffered(pImage, false);
break;
case ImageType.PaletteMatteType:
image = paletteToBuffered(pImage, true);
break;
case ImageType.TrueColorType:
image = rgbToBuffered(pImage, false);
break;
case ImageType.TrueColorMatteType:
image = rgbToBuffered(pImage, true);
break;
case ImageType.ColorSeparationType:
case ImageType.ColorSeparationMatteType:
case ImageType.OptimizeType:
default:
throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType());
}
}
finally {
if (DEBUG) {
long time = System.currentTimeMillis() - start;
System.out.println("Converted JMagick image type: " + pImage.getImageType() + " to BufferedImage: " + image);
System.out.println("Conversion to BufferedImage: " + time + " ms");
}
}
return image;
}
/**
* Converts a {@code BufferedImage} to a {@code MagickImage}.
* <p/>
* The conversion depends on {@code pImage}'s {@code ColorModel}:
* <dl>
* <dt>{@code IndexColorModel} with 1 bit b/w</dt>
* <dd>{@code MagickImage} of type {@code ImageType.BilevelType}</dd>
* <dt>{@code IndexColorModel} &gt; 1 bit,</dt>
* <dd>{@code MagickImage} of type {@code ImageType.PaletteType}
* or {@code MagickImage} of type {@code ImageType.PaletteMatteType}
* depending on <tt>ColorModel.getAlpha()</dd>
*
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}</dt>
* <dd>{@code MagickImage} of type {@code ImageType.GrayscaleType}
* or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType}
* depending on <tt>ColorModel.getAlpha()</dd>
*
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}</dt>
* <dd>{@code MagickImage} of type {@code ImageType.TrueColorType}
* or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}</dd>
*
* @param pImage the original {@code BufferedImage}
* @return a new {@code MagickImage}
*
* @throws IllegalArgumentException if {@code pImage} is {@code null}
* or if the {@code ColorModel} is not one mentioned above.
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
long start = 0L;
if (DEBUG) {
start = System.currentTimeMillis();
}
try {
ColorModel cm = pImage.getColorModel();
if (cm instanceof IndexColorModel) {
// Handles both BilevelType, PaletteType and PaletteMatteType
return indexedToMagick(pImage, (IndexColorModel) cm, cm.hasAlpha());
}
switch (cm.getColorSpace().getType()) {
case ColorSpace.TYPE_GRAY:
// Handles GrayType and GrayMatteType
return grayToMagick(pImage, cm.hasAlpha());
case ColorSpace.TYPE_RGB:
// Handles TrueColorType and TrueColorMatteType
return rgbToMagic(pImage, cm.hasAlpha());
case ColorSpace.TYPE_CMY:
case ColorSpace.TYPE_CMYK:
case ColorSpace.TYPE_HLS:
case ColorSpace.TYPE_HSV:
// Other types not supported yet
default:
throw new IllegalArgumentException("Unknown buffered image type: " + pImage);
}
}
finally {
if (DEBUG) {
long time = System.currentTimeMillis() - start;
System.out.println("Conversion to MagickImage: " + time + " ms");
}
}
}
private static MagickImage rgbToMagic(BufferedImage pImage, boolean pAlpha) throws MagickException {
MagickImage image = new MagickImage();
BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
// Need to get data of sub raster, not the full data array, this is
// just a convenient way
Raster raster;
if (buffered.getRaster().getParent() != null) {
raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight()));
}
else {
raster = buffered.getRaster();
}
image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "BGR",
((DataBufferByte) raster.getDataBuffer()).getData());
return image;
}
private static MagickImage grayToMagick(BufferedImage pImage, boolean pAlpha) throws MagickException {
MagickImage image = new MagickImage();
// TODO: Make a fix for TYPE_USHORT_GRAY
// The code below does not seem to work (JMagick issues?)...
/*
if (pImage.getType() == BufferedImage.TYPE_USHORT_GRAY) {
short[] data = ((DataBufferUShort) pImage.getRaster().getDataBuffer()).getData();
int[] intData = new int[data.length];
for (int i = 0; i < data.length; i++) {
intData[i] = (data[i] & 0xffff) * 0xffff;
}
image.constituteImage(pImage.getWidth(), pImage.getHeight(), "I", intData);
System.out.println("storageClass: " + image.getStorageClass());
System.out.println("depth: " + image.getDepth());
System.out.println("imageType: " + image.getImageType());
}
else {
*/
BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_BYTE_GRAY);
// Need to get data of sub raster, not the full data array, this is
// just a convenient way
Raster raster;
if (buffered.getRaster().getParent() != null) {
raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight()));
}
else {
raster = buffered.getRaster();
}
image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "I", ((DataBufferByte) raster.getDataBuffer()).getData());
//}
return image;
}
private static MagickImage indexedToMagick(BufferedImage pImage, IndexColorModel pColorModel, boolean pAlpha) throws MagickException {
MagickImage image = rgbToMagic(pImage, pAlpha);
int mapSize = pColorModel.getMapSize();
image.setNumberColors(mapSize);
return image;
}
/*
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
final int width = pImage.getWidth();
final int height = pImage.getHeight();
// int ARGB -> byte RGBA conversion
// NOTE: This is ImageMagick Q16 compatible raw RGBA format with 16 bits/sample...
// For a Q8 build, we could probably go with half the space...
// NOTE: This is close to insanity, as it wastes extreme ammounts of memory
final int[] argb = new int[width];
final byte[] raw16 = new byte[width * height * 8];
for (int y = 0; y < height; y++) {
// Fetch one line of ARGB data
pImage.getRGB(0, y, width, 1, argb, 0, width);
for (int x = 0; x < width; x++) {
int pixel = (x + (y * width)) * 8;
raw16[pixel ] = (byte) ((argb[x] >> 16) & 0xff); // R
raw16[pixel + 2] = (byte) ((argb[x] >> 8) & 0xff); // G
raw16[pixel + 4] = (byte) ((argb[x] ) & 0xff); // B
raw16[pixel + 6] = (byte) ((argb[x] >> 24) & 0xff); // A
}
}
// Create magick image
ImageInfo info = new ImageInfo();
info.setMagick("RGBA"); // Raw RGBA samples
info.setSize(width + "x" + height); // String?!?
MagickImage image = new MagickImage(info);
image.setImageAttribute("depth", "8");
// Set pixel data in 16 bit raw RGBA format
image.blobToImage(info, raw16);
return image;
}
*/
/**
* Converts a bi-level {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_BYTE_BINARY}.
*
* @param pImage the original {@code MagickImage}
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage bilevelToBuffered(MagickImage pImage) throws MagickException {
// As there is no way to get the binary representation of the image,
// convert to gray, and the create a binary image from it
BufferedImage temp = grayToBuffered(pImage, false);
BufferedImage image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CM_MONOCHROME);
ImageUtil.drawOnto(image, temp);
return image;
}
/**
* Converts a gray {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_USHORT_GRAY} or {@code TYPE_BYTE_GRAY}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage grayToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
Dimension size = pImage.getDimension();
int length = size.width * size.height;
int bands = pAlpha ? 2 : 1;
byte[] pixels = new byte[length * bands];
// TODO: Make a fix for 16 bit TYPE_USHORT_GRAY?!
// Note: The ordering AI or I corresponds to BufferedImage
// TYPE_CUSTOM and TYPE_BYTE_GRAY respectively
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "AI" : "I", pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
int[] bandOffsets = pAlpha ? new int[] {1, 0} : new int[] {0};
WritableRaster raster =
Raster.createInterleavedRaster(buffer, size.width, size.height,
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
return new BufferedImage(pAlpha ? CM_GRAY_ALPHA : CM_GRAY_OPAQUE, raster, pAlpha, null);
}
/**
* Converts a palette-based {@code MagickImage} to a
* {@code BufferedImage}, of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage paletteToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
// Create indexcolormodel for the image
IndexColorModel cm;
try {
cm = createIndexColorModel(pImage.getColormap(), pAlpha);
}
catch (MagickException e) {
// NOTE: Some MagickImages incorrecly (?) reports to be paletteType,
// but does not have a colormap, this is a workaround.
return rgbToBuffered(pImage, pAlpha);
}
// As there is no way to get the indexes of an indexed image, convert to
// RGB, and the create an indexed image from it
BufferedImage temp = rgbToBuffered(pImage, pAlpha);
BufferedImage image;
if (cm.getMapSize() <= 16) {
image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, cm);
}
else {
image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm);
}
// Create transparent background for images containing alpha
if (pAlpha) {
Graphics2D g = image.createGraphics();
try {
g.setComposite(AlphaComposite.Clear);
g.fillRect(0, 0, temp.getWidth(), temp.getHeight());
}
finally {
g.dispose();
}
}
// NOTE: This is (surprisingly) much faster than using g2d.drawImage()..
// (Tests shows 20-30ms, vs. 600-700ms on the same image)
BufferedImageOp op = new CopyDither(cm);
op.filter(temp, image);
return image;
}
/**
* Creates an {@code IndexColorModel} from an array of
* {@code PixelPacket}s.
*
* @param pColormap the original colormap as a {@code PixelPacket} array
* @param pAlpha keep alpha channel
*
* @return a new {@code IndexColorModel}
*/
public static IndexColorModel createIndexColorModel(PixelPacket[] pColormap, boolean pAlpha) {
int[] colors = new int[pColormap.length];
// TODO: Verify if this is correct for alpha...?
int trans = pAlpha ? colors.length - 1 : -1;
//for (int i = 0; i < pColormap.length; i++) {
for (int i = pColormap.length - 1; i != 0; i--) {
PixelPacket color = pColormap[i];
if (pAlpha) {
colors[i] = (0xff - (color.getOpacity() & 0xff)) << 24 |
(color.getRed() & 0xff) << 16 |
(color.getGreen() & 0xff) << 8 |
(color.getBlue() & 0xff);
}
else {
colors[i] = (color.getRed() & 0xff) << 16 |
(color.getGreen() & 0xff) << 8 |
(color.getBlue() & 0xff);
}
}
return new InverseColorMapIndexColorModel(8, colors.length, colors, 0, pAlpha, trans, DataBuffer.TYPE_BYTE);
}
/**
* Converts an (A)RGB {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_4BYTE_ABGR} or {@code TYPE_3BYTE_BGR}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage rgbToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
Dimension size = pImage.getDimension();
int length = size.width * size.height;
int bands = pAlpha ? 4 : 3;
byte[] pixels = new byte[length * bands];
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
// Note: The ordering ABGR or BGR corresponds to BufferedImage
// TYPE_4BYTE_ABGR and TYPE_3BYTE_BGR respectively
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ABGR" : "BGR", pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
int[] bandOffsets = pAlpha ? BAND_OFF_TRANS : BAND_OFF_OPAQUE;
WritableRaster raster =
Raster.createInterleavedRaster(buffer, size.width, size.height,
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.image;
import java.awt.image.*;
/**
* Monochrome B/W color model.
*
* @author Harald Kuhr
*/
public class MonochromeColorModel extends IndexColorModel {
private final static int[] MONO_PALETTE = {0x00000000, 0x00FFFFFF};
private static MonochromeColorModel sInstance = new MonochromeColorModel();
private MonochromeColorModel() {
super(1, 2, MONO_PALETTE, 0, false, -1, DataBuffer.TYPE_BYTE);
}
public static IndexColorModel getInstance() {
return sInstance;
}
public synchronized Object getDataElements(int pRGB, Object pPixel) {
// Get color components
int r = pRGB >> 16 & 0xFF;
int g = pRGB >> 8 & 0xFF;
int b = pRGB & 0xFF;
// ITU standard: Gray scale=(222*Red+707*Green+71*Blue)/1000
int gray = (222 * r + 707 * g + 71 * b) / 1000;
byte[] pixel;
if (pPixel != null) {
pixel = (byte[]) pPixel;
}
else {
pixel = new byte[1];
}
if (gray <= 0x80) {
pixel[0] = 0;
}
else {
pixel[0] = 1;
}
return pixel;
}
}

View File

@@ -0,0 +1,378 @@
/*
* 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.image;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
/**
* PixelizeOp
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/image/PixelizeOp.java#2 $
*/
public class PixelizeOp implements BufferedImageOp, RasterOp {
// TODO: support more raster types/color models
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
final private int mPixelSizeX;
final private int mPixelSizeY;
private Rectangle mSourceRegion;
public PixelizeOp(final int pPixelSize) {
this(pPixelSize, pPixelSize);
}
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
mPixelSizeX = pPixelSizeX;
mPixelSizeY = pPixelSizeY;
}
public Rectangle getSourceRegion() {
if (mSourceRegion == null) {
return null;
}
return new Rectangle(mSourceRegion);
}
public void setSourceRegion(final Rectangle pSourceRegion) {
if (pSourceRegion == null) {
mSourceRegion = null;
}
else {
if (mSourceRegion == null) {
mSourceRegion = new Rectangle(pSourceRegion);
}
else {
mSourceRegion.setBounds(pSourceRegion);
}
}
}
public BufferedImage filter(BufferedImage src, BufferedImage dest) {
BufferedImage result = dest != null ? dest : createCompatibleDestImage(src, null);
// TODO: Do some type checking here..
// Should work with
// * all BYTE types, unless sub-byte packed rasters/IndexColorModel
// * all INT types (even custom, as long as they use 8bit/componnet)
// * all USHORT types (even custom)
// TODO: Also check if the images are really compatible!?
filterImpl(src.getRaster(), result.getRaster());
return result;
}
public WritableRaster filter(Raster src, WritableRaster dest) {
WritableRaster result = dest != null ? dest : createCompatibleDestRaster(src);
return filterImpl(src, result);
}
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
if (mSourceRegion != null) {
int cx = mSourceRegion.x;
int cy = mSourceRegion.y;
int cw = mSourceRegion.width;
int ch = mSourceRegion.height;
boolean same = src == dest;
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
src = same ? dest : src.createChild(cx, cy, cw, ch, 0, 0, null);
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
}
final int width = src.getWidth();
final int height = src.getHeight();
int w = (width + mPixelSizeX - 1) / mPixelSizeX;
int h = (height + mPixelSizeY - 1) / mPixelSizeY;
final boolean oddX = width % w != 0;
final boolean oddY = height % h != 0;
final int dataElements = src.getNumDataElements();
final int bands = src.getNumBands();
final int dataType = src.getTransferType();
Object data = null;
int scanW;
int scanH;
// TYPE_USHORT setup
int[] bitMasks = null;
int[] bitOffsets = null;
if (src.getTransferType() == DataBuffer.TYPE_USHORT) {
if (src.getSampleModel() instanceof SinglePixelPackedSampleModel) {
// DIRECT
SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) src.getSampleModel();
bitMasks = sampleModel.getBitMasks();
bitOffsets = sampleModel.getBitOffsets();
}
else {
// GRAY
bitMasks = new int[] {0xffff};
bitOffsets = new int[] {0};
}
}
for (int y = 0; y < h; y++) {
if (!oddY || y + 1 < h) {
scanH = mPixelSizeY;
}
else {
scanH = height - (y * mPixelSizeY);
}
for (int x = 0; x < w; x++) {
if (!oddX || x + 1 < w) {
scanW = mPixelSizeX;
}
else {
scanW = width - (x * mPixelSizeX);
}
final int pixelCount = scanW * scanH;
final int pixelLength = pixelCount * dataElements;
data = src.getDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data);
// NOTE: These are not neccessarily ARGB..
double valueA = 0.0;
double valueR = 0.0;
double valueG = 0.0;
double valueB = 0.0;
switch (dataType) {
case DataBuffer.TYPE_BYTE:
// TODO: Doesn't hold for index color models...
byte[] bytePixels = (byte[]) data;
for (int i = 0; i < pixelLength; i += dataElements) {
valueA += bytePixels[i] & 0xff;
if (bands > 1) {
valueR += bytePixels[i + 1] & 0xff;
valueG += bytePixels[i + 2] & 0xff;
if (bands > 3) {
valueB += bytePixels[i + 3] & 0xff;
}
}
}
// Average
valueA /= pixelCount;
if (bands > 1) {
valueR /= pixelCount;
valueG /= pixelCount;
if (bands > 3) {
valueB /= pixelCount;
}
}
for (int i = 0; i < pixelLength; i += dataElements) {
bytePixels[i] = (byte) clamp((int) valueA);
if (bands > 1) {
bytePixels[i + 1] = (byte) clamp((int) valueR);
bytePixels[i + 2] = (byte) clamp((int) valueG);
if (bands > 3) {
bytePixels[i + 3] = (byte) clamp((int) valueB);
}
}
}
break;
case DataBuffer.TYPE_INT:
int[] intPixels = (int[]) data;
for (int i = 0; i < pixelLength; i += dataElements) {
valueA += (intPixels[i] & 0xff000000) >> 24;
valueR += (intPixels[i] & 0xff0000) >> 16;
valueG += (intPixels[i] & 0xff00) >> 8;
valueB += (intPixels[i] & 0xff);
}
// Average
valueA /= pixelCount;
valueR /= pixelCount;
valueG /= pixelCount;
valueB /= pixelCount;
for (int i = 0; i < pixelLength; i += dataElements) {
intPixels[i] = clamp((int) valueA) << 24;
intPixels[i] |= clamp((int) valueR) << 16;
intPixels[i] |= clamp((int) valueG) << 8;
intPixels[i] |= clamp((int) valueB);
}
break;
case DataBuffer.TYPE_USHORT:
if (bitMasks != null) {
short[] shortPixels = (short[]) data;
for (int i = 0; i < pixelLength; i += dataElements) {
valueA += (shortPixels[i] & bitMasks[0]) >> bitOffsets[0];
if (bitMasks.length > 1) {
valueR += (shortPixels[i] & bitMasks[1]) >> bitOffsets[1];
valueG += (shortPixels[i] & bitMasks[2]) >> bitOffsets[2];
if (bitMasks.length > 3) {
valueB += (shortPixels[i] & bitMasks[3]) >> bitOffsets[3];
}
}
}
// Average
valueA /= pixelCount;
valueR /= pixelCount;
valueG /= pixelCount;
valueB /= pixelCount;
for (int i = 0; i < pixelLength; i += dataElements) {
shortPixels[i] = (short) (((int) valueA << bitOffsets[0]) & bitMasks[0]);
if (bitMasks.length > 1) {
shortPixels[i] |= (short) (((int) valueR << bitOffsets[1]) & bitMasks[1]);
shortPixels[i] |= (short) (((int) valueG << bitOffsets[2]) & bitMasks[2]);
if (bitMasks.length > 3) {
shortPixels[i] |= (short) (((int) valueB << bitOffsets[3]) & bitMasks[3]);
}
}
}
break;
}
default:
throw new IllegalArgumentException("TransferType not supported: " + dataType);
}
dest.setDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data);
}
}
/*/
// This is a very naive way of pixelizing (but it works)...
// Thanks to the awsome speed of AffineTransformOp, it's also fast
double sx = w / (double) src.getWidth();
double sy = h / (double) src.getHeight();
WritableRaster temp = src.createCompatibleWritableRaster(w, h);
new AffineTransformOp(AffineTransform.getScaleInstance(sx, sy), 3)
.filter(src, temp);
new AffineTransformOp(AffineTransform.getScaleInstance(1 / sx, 1 / sy),
AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
.filter(temp, dest);
//*/
return dest;
}
private static int clamp(final int pValue) {
return pValue > 255 ? 255 : pValue;
}
public RenderingHints getRenderingHints() {
return null;
}
// TODO: Refactor boilerplate to AbstractBufferedImageOp or use a delegate?
// Delegate is maybe better as we won't always implement both BIOp and RasterOP
// (but are there ever any time we want to implemnet RasterOp and not BIOp?)
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
ColorModel cm = destCM != null ? destCM : src.getColorModel();
return new BufferedImage(cm,
ImageUtil.createCompatibleWritableRaster(src, cm, src.getWidth(), src.getHeight()),
cm.isAlphaPremultiplied(), null);
}
public WritableRaster createCompatibleDestRaster(Raster src) {
return src.createCompatibleWritableRaster();
}
public Rectangle2D getBounds2D(Raster src) {
return new Rectangle(src.getWidth(), src.getHeight());
}
public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(src.getWidth(), src.getHeight());
}
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
if (dstPt == null) {
if (srcPt instanceof Point2D.Double) {
dstPt = new Point2D.Double();
}
else {
dstPt = new Point2D.Float();
}
}
dstPt.setLocation(srcPt);
return dstPt;
}
public static void main(String[] pArgs) throws IOException {
BufferedImage image = ImageIO.read(new File("2006-Lamborghini-Gallardo-Spyder-Y-T-1600x1200.png"));
//BufferedImage image = ImageIO.read(new File("focus-rs.jpg"));
//BufferedImage image = ImageIO.read(new File("blauesglas_16_bitmask444.bmp"));
//image = ImageUtil.toBuffered(image, BufferedImage.TYPE_USHORT_GRAY);
for (int i = 0; i < 10; i++) {
//new PixelizeOp(10).filter(image, null);
//new AffineTransformOp(AffineTransform.getScaleInstance(.1, .1), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
//ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
//new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_BOX).filter(image, null);
new ResampleOp(image.getWidth() / 10, image.getHeight() / 10, ResampleOp.FILTER_QUADRATIC).filter(image, null);
}
long start = System.currentTimeMillis();
//PixelizeOp pixelizer = new PixelizeOp(image.getWidth() / 10, 1);
//pixelizer.setSourceRegion(new Rectangle(0, 2 * image.getHeight() / 3, image.getWidth(), image.getHeight() / 4));
//PixelizeOp pixelizer = new PixelizeOp(4);
//image = pixelizer.filter(image, image); // Filter in place, that's cool
//image = new AffineTransformOp(AffineTransform.getScaleInstance(.25, .25), AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(image, null);
//image = ImageUtil.toBuffered(image.getScaledInstance(image.getWidth() / 4, image.getHeight() / 4, Image.SCALE_AREA_AVERAGING));
//image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_BOX).filter(image, null);
image = new ResampleOp(image.getWidth() / 4, image.getHeight() / 4, ResampleOp.FILTER_QUADRATIC).filter(image, null);
long time = System.currentTimeMillis() - start;
System.out.println("time: " + time + " ms");
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new JScrollPane(new JLabel(new BufferedImageIcon(image))));
frame.pack();
frame.setVisible(true);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
/*
* 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.image;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
/**
* SubsampleTester
*
* @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/image/SubsampleTester.java#1 $
*/
public class SubsampleTester {
// Initial testing shows we need at least 9 pixels (sampleFactor == 3) to make a good looking image..
// Also, using Lanczos is much better than (and allmost as fast as) halving using AffineTransform
// - But I guess those numbers depend on the data type of the input image...
public static void main(String[] pArgs) throws IOException {
// To/from larger than or equal to 4x4
//ImageUtil.createResampled(new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB), 4, 4, BufferedImage.SCALE_SMOOTH);
//ImageUtil.createResampled(new BufferedImage(4, 4, BufferedImage.TYPE_INT_ARGB), 5, 5, BufferedImage.SCALE_SMOOTH);
// To/from smaller than or equal to 4x4 with fast scale
//ImageUtil.createResampled(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), 10, 10, BufferedImage.SCALE_FAST);
//ImageUtil.createResampled(new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB), 3, 3, BufferedImage.SCALE_FAST);
// To/from smaller than or equal to 4x4 with default scale
//ImageUtil.createResampled(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), 10, 10, BufferedImage.SCALE_DEFAULT);
//ImageUtil.createResampled(new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB), 3, 3, BufferedImage.SCALE_DEFAULT);
// To/from smaller than or equal to 4x4 with smooth scale
try {
ImageUtil.createResampled(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), 10, 10, BufferedImage.SCALE_SMOOTH);
}
catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
//try {
// ImageUtil.createResampled(new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB), 3, 3, BufferedImage.SCALE_SMOOTH);
//}
//catch (IndexOutOfBoundsException e) {
// e.printStackTrace();
// return;
//}
File input = new File(pArgs[0]);
ImageInputStream stream = ImageIO.createImageInputStream(input);
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (readers.hasNext()) {
if (stream == null) {
return;
}
ImageReader reader = readers.next();
reader.setInput(stream);
ImageReadParam param = reader.getDefaultReadParam();
for (int i = 0; i < 25; i++) {
//readImage(pArgs, reader, param);
}
long start = System.currentTimeMillis();
BufferedImage image = readImage(pArgs, reader, param);
long end = System.currentTimeMillis();
System.out.println("elapsed time: " + (end - start) + " ms");
int subX = param.getSourceXSubsampling();
int subY = param.getSourceYSubsampling();
System.out.println("image: " + image);
//ImageIO.write(image, "png", new File(input.getParentFile(), input.getName().replace('.', '_') + "_new.png"));
ConvolveTester.showIt(image, input.getName() + (subX > 1 || subY > 1 ? " (subsampled " + subX + " by " + subY + ")" : ""));
}
else {
System.err.println("No reader found for input: " + input.getAbsolutePath());
}
}
private static BufferedImage readImage(final String[] pArgs, final ImageReader pReader, final ImageReadParam pParam) throws IOException {
double sampleFactor; // Minimum number of samples (in each dimension) pr pixel in output
int width = pArgs.length > 1 ? Integer.parseInt(pArgs[1]) : 300;
int height = pArgs.length > 2 ? Integer.parseInt(pArgs[2]) : 200;
if (pArgs.length > 3 && (sampleFactor = Double.parseDouble(pArgs[3])) > 0) {
int originalWidth = pReader.getWidth(0);
int originalHeight = pReader.getHeight(0);
System.out.println("originalWidth: " + originalWidth);
System.out.println("originalHeight: " + originalHeight);
int subX = (int) Math.max(originalWidth / (double) (width * sampleFactor), 1.0);
int subY = (int) Math.max(originalHeight / (double) (height * sampleFactor), 1.0);
if (subX > 1 || subY > 1) {
System.out.println("subX: " + subX);
System.out.println("subY: " + subY);
pParam.setSourceSubsampling(subX, subY, subX > 1 ? subX / 2 : 0, subY > 1 ? subY / 2 : 0);
}
}
BufferedImage image = pReader.read(0, pParam);
System.out.println("image: " + image);
int algorithm = BufferedImage.SCALE_DEFAULT;
if (pArgs.length > 4) {
if ("smooth".equals(pArgs[4].toLowerCase())) {
algorithm = BufferedImage.SCALE_SMOOTH;
}
else if ("fast".equals(pArgs[4].toLowerCase())) {
algorithm = BufferedImage.SCALE_FAST;
}
}
if (image.getWidth() != width || image.getHeight() != height) {
image = ImageUtil.createScaled(image, width, height, algorithm);
}
return image;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.image;
import java.awt.image.ReplicateScaleFilter;
/**
* An {@code ImageFilter} class for subsampling images.
* <p/>
* It is meant to be used in conjunction with a {@code FilteredImageSource}
* object to produce subsampled versions of existing images.
*
* @see java.awt.image.FilteredImageSource
*
* @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/SubsamplingFilter.java#1 $
*/
public class SubsamplingFilter extends ReplicateScaleFilter {
private int mXSub;
private int mYSub;
/**
* Creates a {@code SubsamplingFilter}.
*
* @param pXSub
* @param pYSub
*
* @throws IllegalArgumentException if {@code pXSub} or {@code pYSub} is
* less than 1.
*/
public SubsamplingFilter(int pXSub, int pYSub) {
super(1, 1); // These are NOT REAL values, but we have to defer setting
// until w/h is available, in setDimensions below
if (pXSub < 1 || pYSub < 1) {
throw new IllegalArgumentException("Subsampling factors must be positive.");
}
mXSub = pXSub;
mYSub = pYSub;
}
/** {@code ImageFilter} implementation, do not invoke. */
public void setDimensions(int pWidth, int pHeight) {
destWidth = (pWidth + mXSub - 1) / mXSub;
destHeight = (pHeight + mYSub - 1) / mYSub;
//System.out.println("Subsampling: " + mXSub + "," + mYSub + "-> " + destWidth + ", " + destHeight);
super.setDimensions(pWidth, pHeight);
}
}

View File

@@ -0,0 +1,524 @@
/*
* This software is copyrighted as noted below. It may be freely copied,
* modified, and redistributed, provided that the copyright notice is
* preserved on all copies.
*
* There is no warranty or other guarantee of fitness for this software,
* it is provided solely "as is". Bug reports or fixes may be sent
* to the author, who may or may not act on them as he desires.
*
* You may not include this software in a program or other software product
* without supplying the source, or without informing the end-user that the
* source is available for no extra charge.
*
* If you modify this software, you should include a notice giving the
* name of the person performing the modification, the date of modification,
* and the reason for such modification.
*/
/*
* inv_cmap.c - Compute an inverse colormap.
*
* Author: Spencer W. Thomas
* EECS Dept.
* University of Michigan
* Date: Thu Sep 20 1990
* Copyright (c) 1990, University of Michigan
*
* $Id: inv_cmap.c,v 3.0.1.3 1992/04/30 14:07:28 spencer Exp $
*/
#include <math.h>
#include <stdio.h>
static int bcenter, gcenter, rcenter;
static long gdist, rdist, cdist;
static long cbinc, cginc, crinc;
static unsigned long *gdp, *rdp, *cdp;
static unsigned char *grgbp, *rrgbp, *crgbp;
static gstride, rstride;
static long x, xsqr, colormax;
static int cindex;
#ifdef USE_PROTOTYPES
static void maxfill( unsigned long *, long );
static int redloop( void );
static int greenloop( int );
static int blueloop( int );
#else
static void maxfill();
static int redloop();
static int greenloop();
static int blueloop();
#endif
/*****************************************************************
* TAG( inv_cmap )
*
* Compute an inverse colormap efficiently.
* Inputs:
* colors: Number of colors in the forward colormap.
* colormap: The forward colormap.
* bits: Number of quantization bits. The inverse
* colormap will have (2^bits)^3 entries.
* dist_buf: An array of (2^bits)^3 long integers to be
* used as scratch space.
* Outputs:
* rgbmap: The output inverse colormap. The entry
* rgbmap[(r<<(2*bits)) + (g<<bits) + b]
* is the colormap entry that is closest to the
* (quantized) color (r,g,b).
* Assumptions:
* Quantization is performed by right shift (low order bits are
* truncated). Thus, the distance to a quantized color is
* actually measured to the color at the center of the cell
* (i.e., to r+.5, g+.5, b+.5, if (r,g,b) is a quantized color).
* Algorithm:
* Uses a "distance buffer" algorithm:
* The distance from each representative in the forward color map
* to each point in the rgb space is computed. If it is less
* than the distance currently stored in dist_buf, then the
* corresponding entry in rgbmap is replaced with the current
* representative (and the dist_buf entry is replaced with the
* new distance).
*
* The distance computation uses an efficient incremental formulation.
*
* Distances are computed "outward" from each color. If the
* colors are evenly distributed in color space, the expected
* number of cells visited for color I is N^3/I.
* Thus, the complexity of the algorithm is O(log(K) N^3),
* where K = colors, and N = 2^bits.
*/
/*
* Here's the idea: scan from the "center" of each cell "out"
* until we hit the "edge" of the cell -- that is, the point
* at which some other color is closer -- and stop. In 1-D,
* this is simple:
* for i := here to max do
* if closer then buffer[i] = this color
* else break
* repeat above loop with i := here-1 to min by -1
*
* In 2-D, it's trickier, because along a "scan-line", the
* region might start "after" the "center" point. A picture
* might clarify:
* | ...
* | ... .
* ... .
* ... | .
* . + .
* . .
* . .
* .........
*
* The + marks the "center" of the above region. On the top 2
* lines, the region "begins" to the right of the "center".
*
* Thus, we need a loop like this:
* detect := false
* for i := here to max do
* if closer then
* buffer[..., i] := this color
* if !detect then
* here = i
* detect = true
* else
* if detect then
* break
*
* Repeat the above loop with i := here-1 to min by -1. Note that
* the "detect" value should not be reinitialized. If it was
* "true", and center is not inside the cell, then none of the
* cell lies to the left and this loop should exit
* immediately.
*
* The outer loops are similar, except that the "closer" test
* is replaced by a call to the "next in" loop; its "detect"
* value serves as the test. (No assignment to the buffer is
* done, either.)
*
* Each time an outer loop starts, the "here", "min", and
* "max" values of the next inner loop should be
* re-initialized to the center of the cell, 0, and cube size,
* respectively. Otherwise, these values will carry over from
* one "call" to the inner loop to the next. This tracks the
* edges of the cell and minimizes the number of
* "unproductive" comparisons that must be made.
*
* Finally, the inner-most loop can have the "if !detect"
* optimized out of it by splitting it into two loops: one
* that finds the first color value on the scan line that is
* in this cell, and a second that fills the cell until
* another one is closer:
* if !detect then {needed for "down" loop}
* for i := here to max do
* if closer then
* buffer[..., i] := this color
* detect := true
* break
* for i := i+1 to max do
* if closer then
* buffer[..., i] := this color
* else
* break
*
* In this implementation, each level will require the
* following variables. Variables labelled (l) are local to each
* procedure. The ? should be replaced with r, g, or b:
* cdist: The distance at the starting point.
* ?center: The value of this component of the color
* c?inc: The initial increment at the ?center position.
* ?stride: The amount to add to the buffer
* pointers (dp and rgbp) to get to the
* "next row".
* min(l): The "low edge" of the cell, init to 0
* max(l): The "high edge" of the cell, init to
* colormax-1
* detect(l): True if this row has changed some
* buffer entries.
* i(l): The index for this row.
* ?xx: The accumulated increment value.
*
* here(l): The starting index for this color. The
* following variables are associated with here,
* in the sense that they must be updated if here
* is changed.
* ?dist: The current distance for this level. The
* value of dist from the previous level (g or r,
* for level b or g) initializes dist on this
* level. Thus gdist is associated with here(b)).
* ?inc: The initial increment for the row.
* ?dp: Pointer into the distance buffer. The value
* from the previous level initializes this level.
* ?rgbp: Pointer into the rgb buffer. The value
* from the previous level initializes this level.
*
* The blue and green levels modify 'here-associated' variables (dp,
* rgbp, dist) on the green and red levels, respectively, when here is
* changed.
*/
void
inv_cmap( colors, colormap, bits, dist_buf, rgbmap )
int colors, bits;
unsigned char *colormap[3], *rgbmap;
unsigned long *dist_buf;
{
int nbits = 8 - bits;
colormax = 1 << bits;
x = 1 << nbits;
xsqr = 1 << (2 * nbits);
/* Compute "strides" for accessing the arrays. */
gstride = colormax;
rstride = colormax * colormax;
maxfill( dist_buf, colormax );
for ( cindex = 0; cindex < colors; cindex++ )
{
/*
* Distance formula is
* (red - map[0])^2 + (green - map[1])^2 + (blue - map[2])^2
*
* Because of quantization, we will measure from the center of
* each quantized "cube", so blue distance is
* (blue + x/2 - map[2])^2,
* where x = 2^(8 - bits).
* The step size is x, so the blue increment is
* 2*x*blue - 2*x*map[2] + 2*x^2
*
* Now, b in the code below is actually blue/x, so our
* increment will be 2*(b*x^2 + x^2 - x*map[2]). For
* efficiency, we will maintain this quantity in a separate variable
* that will be updated incrementally by adding 2*x^2 each time.
*/
/* The initial position is the cell containing the colormap
* entry. We get this by quantizing the colormap values.
*/
rcenter = colormap[0][cindex] >> nbits;
gcenter = colormap[1][cindex] >> nbits;
bcenter = colormap[2][cindex] >> nbits;
rdist = colormap[0][cindex] - (rcenter * x + x/2);
gdist = colormap[1][cindex] - (gcenter * x + x/2);
cdist = colormap[2][cindex] - (bcenter * x + x/2);
cdist = rdist*rdist + gdist*gdist + cdist*cdist;
crinc = 2 * ((rcenter + 1) * xsqr - (colormap[0][cindex] * x));
cginc = 2 * ((gcenter + 1) * xsqr - (colormap[1][cindex] * x));
cbinc = 2 * ((bcenter + 1) * xsqr - (colormap[2][cindex] * x));
/* Array starting points. */
cdp = dist_buf + rcenter * rstride + gcenter * gstride + bcenter;
crgbp = rgbmap + rcenter * rstride + gcenter * gstride + bcenter;
(void)redloop();
}
}
/* redloop -- loop up and down from red center. */
static int
redloop()
{
int detect;
int r;
int first;
long txsqr = xsqr + xsqr;
static long rxx;
detect = 0;
/* Basic loop up. */
for ( r = rcenter, rdist = cdist, rxx = crinc,
rdp = cdp, rrgbp = crgbp, first = 1;
r < colormax;
r++, rdp += rstride, rrgbp += rstride,
rdist += rxx, rxx += txsqr, first = 0 )
{
if ( greenloop( first ) )
detect = 1;
else if ( detect )
break;
}
/* Basic loop down. */
for ( r = rcenter - 1, rxx = crinc - txsqr, rdist = cdist - rxx,
rdp = cdp - rstride, rrgbp = crgbp - rstride, first = 1;
r >= 0;
r--, rdp -= rstride, rrgbp -= rstride,
rxx -= txsqr, rdist -= rxx, first = 0 )
{
if ( greenloop( first ) )
detect = 1;
else if ( detect )
break;
}
return detect;
}
/* greenloop -- loop up and down from green center. */
static int
greenloop( restart )
int restart;
{
int detect;
int g;
int first;
long txsqr = xsqr + xsqr;
static int here, min, max;
static long ginc, gxx, gcdist; /* "gc" variables maintain correct */
static unsigned long *gcdp; /* values for bcenter position, */
static unsigned char *gcrgbp; /* despite modifications by blueloop */
/* to gdist, gdp, grgbp. */
if ( restart )
{
here = gcenter;
min = 0;
max = colormax - 1;
ginc = cginc;
}
detect = 0;
/* Basic loop up. */
for ( g = here, gcdist = gdist = rdist, gxx = ginc,
gcdp = gdp = rdp, gcrgbp = grgbp = rrgbp, first = 1;
g <= max;
g++, gdp += gstride, gcdp += gstride, grgbp += gstride, gcrgbp += gstride,
gdist += gxx, gcdist += gxx, gxx += txsqr, first = 0 )
{
if ( blueloop( first ) )
{
if ( !detect )
{
/* Remember here and associated data! */
if ( g > here )
{
here = g;
rdp = gcdp;
rrgbp = gcrgbp;
rdist = gcdist;
ginc = gxx;
}
detect = 1;
}
}
else if ( detect )
{
break;
}
}
/* Basic loop down. */
for ( g = here - 1, gxx = ginc - txsqr, gcdist = gdist = rdist - gxx,
gcdp = gdp = rdp - gstride, gcrgbp = grgbp = rrgbp - gstride,
first = 1;
g >= min;
g--, gdp -= gstride, gcdp -= gstride, grgbp -= gstride, gcrgbp -= gstride,
gxx -= txsqr, gdist -= gxx, gcdist -= gxx, first = 0 )
{
if ( blueloop( first ) )
{
if ( !detect )
{
/* Remember here! */
here = g;
rdp = gcdp;
rrgbp = gcrgbp;
rdist = gcdist;
ginc = gxx;
detect = 1;
}
}
else if ( detect )
{
break;
}
}
return detect;
}
/* blueloop -- loop up and down from blue center. */
static int
blueloop( restart )
int restart;
{
int detect;
register unsigned long *dp;
register unsigned char *rgbp;
register long bdist, bxx;
register int b, i = cindex;
register long txsqr = xsqr + xsqr;
register int lim;
static int here, min, max;
static long binc;
if ( restart )
{
here = bcenter;
min = 0;
max = colormax - 1;
binc = cbinc;
}
detect = 0;
/* Basic loop up. */
/* First loop just finds first applicable cell. */
for ( b = here, bdist = gdist, bxx = binc, dp = gdp, rgbp = grgbp, lim = max;
b <= lim;
b++, dp++, rgbp++,
bdist += bxx, bxx += txsqr )
{
if ( *dp > bdist )
{
/* Remember new 'here' and associated data! */
if ( b > here )
{
here = b;
gdp = dp;
grgbp = rgbp;
gdist = bdist;
binc = bxx;
}
detect = 1;
break;
}
}
/* Second loop fills in a run of closer cells. */
for ( ;
b <= lim;
b++, dp++, rgbp++,
bdist += bxx, bxx += txsqr )
{
if ( *dp > bdist )
{
*dp = bdist;
*rgbp = i;
}
else
{
break;
}
}
/* Basic loop down. */
/* Do initializations here, since the 'find' loop might not get
* executed.
*/
lim = min;
b = here - 1;
bxx = binc - txsqr;
bdist = gdist - bxx;
dp = gdp - 1;
rgbp = grgbp - 1;
/* The 'find' loop is executed only if we didn't already find
* something.
*/
if ( !detect )
for ( ;
b >= lim;
b--, dp--, rgbp--,
bxx -= txsqr, bdist -= bxx )
{
if ( *dp > bdist )
{
/* Remember here! */
/* No test for b against here necessary because b <
* here by definition.
*/
here = b;
gdp = dp;
grgbp = rgbp;
gdist = bdist;
binc = bxx;
detect = 1;
break;
}
}
/* The 'update' loop. */
for ( ;
b >= lim;
b--, dp--, rgbp--,
bxx -= txsqr, bdist -= bxx )
{
if ( *dp > bdist )
{
*dp = bdist;
*rgbp = i;
}
else
{
break;
}
}
/* If we saw something, update the edge trackers. */
return detect;
}
static void
maxfill( buffer, side )
unsigned long *buffer;
long side;
{
register unsigned long maxv = ~0L;
register long i;
register unsigned long *bp;
for ( i = side * side * side, bp = buffer;
i > 0;
i--, bp++ )
*bp = maxv;
}

View File

@@ -0,0 +1,9 @@
/**
* Classes for image manipulation.
* <p/>
* See the class {@link com.twelvemonkeys.image.ImageUtil}.
*
* @version 1.0
* @author <a href="mailto:harald@escenic.com">Harald Kuhr</a>
*/
package com.twelvemonkeys.image;

View File

@@ -0,0 +1,277 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.IOException;
import java.io.InputStream;
/**
* Represents a cached seekable stream, that reads through a cache.
*
* @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/io/AbstractCachedSeekableStream.java#2 $
*/
abstract class AbstractCachedSeekableStream extends SeekableInputStream {
/** The backing stream */
protected final InputStream mStream;
/** The stream positon in the backing stream (mStream) */
protected long mStreamPosition;
private StreamCache mCache;
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
Validate.notNull(pStream, "stream");
Validate.notNull(pCache, "cache");
mStream = pStream;
mCache = pCache;
}
protected final StreamCache getCache() {
return mCache;
}
@Override
public int available() throws IOException {
long avail = mStreamPosition - mPosition + mStream.available();
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
}
public int read() throws IOException {
checkOpen();
int read;
if (mPosition == mStreamPosition) {
// TODO: Read more bytes here!
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
// Read a byte from the stream
read = mStream.read();
if (read >= 0) {
mStreamPosition++;
mCache.write(read);
}
}
else {
// ..or read byte from the cache
syncPosition();
read = mCache.read();
}
// TODO: This field is not REALLY considered accessible.. :-P
if (read != -1) {
mPosition++;
}
return read;
}
@Override
public int read(byte[] pBytes, int pOffset, int pLength) throws IOException {
checkOpen();
int length;
if (mPosition == mStreamPosition) {
// Read bytes from the stream
length = mStream.read(pBytes, pOffset, pLength);
if (length > 0) {
mStreamPosition += length;
mCache.write(pBytes, pOffset, length);
}
}
else {
// ...or read bytes from the cache
syncPosition();
length = mCache.read(pBytes, pOffset, pLength);
}
// TODO: This field is not REALLY considered accessible.. :-P
if (length > 0) {
mPosition += length;
}
return length;
}
protected final void syncPosition() throws IOException {
if (mCache.getPosition() != mPosition) {
mCache.seek(mPosition); // Assure EOF is correctly thrown
}
}
public final boolean isCached() {
return true;
}
public abstract boolean isCachedMemory();
public abstract boolean isCachedFile();
protected void seekImpl(long pPosition) throws IOException {
if (mStreamPosition < pPosition) {
// Make sure we append at end of cache
if (mCache.getPosition() != mStreamPosition) {
mCache.seek(mStreamPosition);
}
// Read diff from stream into cache
long left = pPosition - mStreamPosition;
// TODO: Use fixed buffer, instead of allocating here...
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("mPosition: " + mPosition);
// System.out.println("mStreamPosition: " + mStreamPosition);
// System.out.println("mCache.mPosition: " + mCache.getPosition());
// NOTE: If mPosition == pPosition then we're good to go
}
protected void flushBeforeImpl(long pPosition) {
mCache.flush(pPosition);
}
protected void closeImpl() throws IOException {
mCache.flush(mPosition);
mCache = null;
mStream.close();
}
/**
* An abstract stream cache.
*
* @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/io/AbstractCachedSeekableStream.java#2 $
*/
public static abstract class StreamCache {
/**
* Creates a {@code StreamCache}.
*/
protected StreamCache() {
}
/**
* Writes a single byte at the current read/write position. The read/write position will be increased by one.
*
* @param pByte the byte value to write.
*
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
*/
abstract void write(int pByte) throws IOException;
/**
* Writes a series of bytes at the current read/write position. The read/write position will be increased by
* {@code pLength}.
* <p/>
* This implementation invokes {@link #write(int)} {@code pLength} times.
* Subclasses may override this method for performance.
*
* @param pBuffer the bytes to write.
* @param pOffset the starting offset into the buffer.
* @param pLength the number of bytes to write from the buffer.
*
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
*/
void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
for (int i = 0; i < pLength; i++) {
write(pBuffer[pOffset + i]);
}
}
/**
* Reads a single byte a the current read/write position. The read/write position will be increased by one.
*
* @return the value read, or {@code -1} to indicate EOF.
*
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
*/
abstract int read() throws IOException;
/**
* Writes a series of bytes at the current read/write position. The read/write position will be increased by
* {@code pLength}.
* <p/>
* This implementation invokes {@link #read()} {@code pLength} times.
* Subclasses may override this method for performance.
*
* @param pBuffer the bytes to write
* @param pOffset the starting offset into the buffer.
* @param pLength the number of bytes to write from the buffer.
* @return the number of bytes read, or {@code -1} to indicate EOF.
*
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
*/
int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
int count = 0;
for (int i = 0; i < pLength; i++) {
int read = read();
if (read >= 0) {
pBuffer[pOffset + i] = (byte) read;
count++;
}
else {
break;
}
}
return count;
}
/**
* Repositions the current cache read/write position to the given position.
*
* @param pPosition the new read/write position
*
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
*/
abstract void seek(long pPosition) throws IOException;
/**
* Optionally flushes any data prior to the given position.
* <p/>
* Attempting to perform a seek operation, and/or a read or write operation to a position equal to or before
* the flushed position may result in exceptions or undefined behaviour.
* <p/>
* Subclasses should override this method for performance reasons, to avoid holding on to unnecessary resources.
* This implementation does nothing.
*
* @param pPosition the last position to flush.
*/
void flush(final long pPosition) {
}
/**
* Returns the current cache read/write position.
*
* @return the current cache read/write postion.
*
* @throws IOException if the position can't be determined because of a problem in the cache backing mechanism.
*/
abstract long getPosition() throws IOException;
}
}

View File

@@ -0,0 +1,218 @@
/*
* 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.Validate;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
/**
* A Reader implementation that can read from multiple sources.
* <p/>
*
* @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/io/CompoundReader.java#2 $
*/
public class CompoundReader extends Reader {
private Reader mCurrent;
private List<Reader> mReaders;
protected final Object mLock;
protected final boolean mMarkSupported;
private int mCurrentReader;
private int mMarkedReader;
private int mMark;
private int mNext;
/**
* Create a new compound reader.
*
* @param pReaders {@code Iterator} containting {@code Reader}s,
* providing the character stream.
*
* @throws NullPointerException if {@code pReaders} is {@code null}, or
* any of the elements in the iterator is {@code null}.
* @throws ClassCastException if any element of the iterator is not a
* {@code java.io.Reader}
*/
public CompoundReader(final Iterator<Reader> pReaders) {
super(Validate.notNull(pReaders, "readers"));
mLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
// reference can't change, only it's elements
mReaders = new ArrayList<Reader>();
boolean markSupported = true;
while (pReaders.hasNext()) {
Reader reader = pReaders.next();
if (reader == null) {
throw new NullPointerException("readers cannot contain null-elements");
}
mReaders.add(reader);
markSupported = markSupported && reader.markSupported();
}
mMarkSupported = markSupported;
mCurrent = nextReader();
}
protected final Reader nextReader() {
if (mCurrentReader >= mReaders.size()) {
mCurrent = new EmptyReader();
}
else {
mCurrent = mReaders.get(mCurrentReader++);
}
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
mNext = 0;
return mCurrent;
}
/**
* Check to make sure that the stream has not been closed
*
* @throws IOException if the stream is closed
*/
protected final void ensureOpen() throws IOException {
if (mReaders == null) {
throw new IOException("Stream closed");
}
}
public void close() throws IOException {
// Close all readers
for (Reader reader : mReaders) {
reader.close();
}
mReaders = null;
}
@Override
public void mark(int pReadLimit) throws IOException {
if (pReadLimit < 0) {
throw new IllegalArgumentException("Read limit < 0");
}
// TODO: It would be nice if we could actually close some readers now
synchronized (mLock) {
ensureOpen();
mMark = mNext;
mMarkedReader = mCurrentReader;
mCurrent.mark(pReadLimit);
}
}
@Override
public void reset() throws IOException {
synchronized (mLock) {
ensureOpen();
if (mCurrentReader != mMarkedReader) {
// Reset any reader before this
for (int i = mCurrentReader; i >= mMarkedReader; i--) {
mReaders.get(i).reset();
}
mCurrentReader = mMarkedReader - 1;
nextReader();
}
mCurrent.reset();
mNext = mMark;
}
}
@Override
public boolean markSupported() {
return mMarkSupported;
}
@Override
public int read() throws IOException {
synchronized (mLock) {
int read = mCurrent.read();
if (read < 0 && mCurrentReader < mReaders.size()) {
nextReader();
return read(); // In case of 0-length readers
}
mNext++;
return read;
}
}
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (mLock) {
int read = mCurrent.read(pBuffer, pOffset, pLength);
if (read < 0 && mCurrentReader < mReaders.size()) {
nextReader();
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
}
mNext += read;
return read;
}
}
@Override
public boolean ready() throws IOException {
return mCurrent.ready();
}
@Override
public long skip(long pChars) throws IOException {
synchronized (mLock) {
long skipped = mCurrent.skip(pChars);
if (skipped == 0 && mCurrentReader < mReaders.size()) {
nextReader();
return skip(pChars); // In case of 0-length readers
}
mNext += skipped;
return skipped;
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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 java.io.StringReader;
/**
* EmptyReader
* <p/>
*
* @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/io/EmptyReader.java#1 $
*/
final class EmptyReader extends StringReader {
public EmptyReader() {
super("");
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
/**
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
* also has a constructor that lets you create a stream with initial content.
* <p/>
*
* @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 $
*/
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
/** Max grow size (unless if writing more than this ammount of bytes) */
protected int mMaxGrowSize = 1024 * 1024; // 1 MB
/**
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
* size.
*
* @param pSize initial buffer size
*/
public FastByteArrayOutputStream(int pSize) {
super(pSize);
}
/**
* Creates a {@code ByteArrayOutputStream} with the given initial content.
* <p/>
* Note that the buffer is not cloned, for maximum performance.
*
* @param pBuffer initial buffer
*/
public FastByteArrayOutputStream(byte[] pBuffer) {
super(0); // Don't allocate array
buf = pBuffer;
count = pBuffer.length;
}
@Override
public synchronized void write(byte pBytes[], int pOffset, int pLength) {
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException();
}
else if (pLength == 0) {
return;
}
int newcount = count + pLength;
growIfNeeded(newcount);
System.arraycopy(pBytes, pOffset, buf, count, pLength);
count = newcount;
}
@Override
public synchronized void write(int pByte) {
int newcount = count + 1;
growIfNeeded(newcount);
buf[count] = (byte) pByte;
count = newcount;
}
private void growIfNeeded(int pNewcount) {
if (pNewcount > buf.length) {
int newSize = Math.max(Math.min(buf.length << 1, buf.length + mMaxGrowSize), pNewcount);
byte newBuf[] = new byte[newSize];
System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf;
}
}
// Non-synchronized version of writeTo
@Override
public void writeTo(OutputStream pOut) throws IOException {
pOut.write(buf, 0, count);
}
// Non-synchronized version of toByteArray
@Override
public byte[] toByteArray() {
byte newbuf[] = new byte[count];
System.arraycopy(buf, 0, newbuf, 0, count);
return newbuf;
}
/**
* Creates a {@code ByteArrayInputStream} that reads directly from this
* {@code FastByteArrayOutputStream}'s byte buffer.
* The buffer is not cloned, for maximum performance.
* <p/>
* Note that care needs to be taken to avoid writes to
* this output stream after the input stream is created.
* Failing to do so, may result in unpredictable behviour.
*
* @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
*/
public ByteArrayInputStream createInputStream() {
return new ByteArrayInputStream(buf, 0, count);
}
}

View File

@@ -0,0 +1,313 @@
/*
* 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.Validate;
import java.io.*;
/**
* A {@code SeekableInputStream} implementation that caches data in a temporary {@code File}.
* <p/>
* Temporary files are created as specified in {@link File#createTempFile(String, String, java.io.File)}.
*
* @see MemoryCacheSeekableStream
* @see FileSeekableStream
*
* @see File#createTempFile(String, String)
* @see RandomAccessFile
*
* @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/FileCacheSeekableStream.java#5 $
*/
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
// private final InputStream mStream;
// 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
* {@code InputStream}. Data will be cached in a temporary file.
*
* @param pStream the {@code InputStream} to read from
*
* @throws IOException if the temporary file cannot be created,
* or cannot be opened for random access.
*/
public FileCacheSeekableStream(final InputStream pStream) throws IOException {
this(pStream, "iocache", null);
}
/**
* Creates a {@code FileCacheSeekableStream} reading from the given
* {@code InputStream}. Data will be cached in a temporary file, with
* the given base name.
*
* @param pStream the {@code InputStream} to read from
* @param pTempBaseName optional base name for the temporary file
*
* @throws IOException if the temporary file cannot be created,
* or cannot be opened for random access.
*/
public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName) throws IOException {
this(pStream, pTempBaseName, null);
}
/**
* Creates a {@code FileCacheSeekableStream} reading from the given
* {@code InputStream}. Data will be cached in a temporary file, with
* the given base name, in the given directory
*
* @param pStream the {@code InputStream} to read from
* @param pTempBaseName optional base name for the temporary file
* @param pTempDir optional temp directory
*
* @throws IOException if the temporary file cannot be created,
* or cannot be opened for random access.
*/
public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName, final File pTempDir) throws IOException {
// NOTE: We do validation BEFORE we create temp file, to avoid orphan files
this(Validate.notNull(pStream, "stream"), createTempFile(pTempBaseName, pTempDir));
}
/*protected*/ static File createTempFile(String pTempBaseName, File pTempDir) throws IOException {
Validate.notNull(pTempBaseName, "tempBaseName");
File file = File.createTempFile(pTempBaseName, null, pTempDir);
file.deleteOnExit();
return file;
}
// TODO: Consider exposing this for external use
/*protected*/ FileCacheSeekableStream(final InputStream pStream, final File pFile) throws FileNotFoundException {
super(pStream, new FileCache(pFile));
// TODO: Allow for custom buffer sizes?
mBuffer = new byte[1024];
}
public final boolean isCachedMemory() {
return false;
}
public final boolean isCachedFile() {
return true;
}
@Override
protected void closeImpl() throws IOException {
super.closeImpl();
mBuffer = 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
public int read() throws IOException {
checkOpen();
int read;
if (mPosition == mStreamPosition) {
// Read ahead into buffer, for performance
read = readAhead(mBuffer, 0, mBuffer.length);
if (read >= 0) {
read = mBuffer[0] & 0xff;
}
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff));
}
else {
// ..or read byte from the cache
syncPosition();
read = getCache().read();
//System.out.println("Read 1 byte from cache: " + Integer.toHexString(read & 0xff));
}
// TODO: This field is not REALLY considered accessible.. :-P
if (read != -1) {
mPosition++;
}
return read;
}
@Override
public int read(byte[] pBytes, int pOffset, int pLength) throws IOException {
checkOpen();
int length;
if (mPosition == mStreamPosition) {
// Read bytes from the stream
length = readAhead(pBytes, pOffset, pLength);
//System.out.println("Read " + length + " byte from stream");
}
else {
// ...or read bytes from the cache
syncPosition();
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, mStreamPosition - mPosition));
//System.out.println("Read " + length + " byte from cache");
}
// TODO: This field is not REALLY considered accessible.. :-P
if (length > 0) {
mPosition += length;
}
return length;
}
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
int length;
length = mStream.read(pBytes, pOffset, pLength);
if (length > 0) {
mStreamPosition += length;
getCache().write(pBytes, pOffset, 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 {
private RandomAccessFile mCacheFile;
public FileCache(final File pFile) throws FileNotFoundException {
Validate.notNull(pFile, "file");
mCacheFile = new RandomAccessFile(pFile, "rw");
}
public void write(final int pByte) throws IOException {
mCacheFile.write(pByte);
}
@Override
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
mCacheFile.write(pBuffer, pOffset, pLength);
}
public int read() throws IOException {
return mCacheFile.read();
}
@Override
public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
return mCacheFile.read(pBuffer, pOffset, pLength);
}
public void seek(final long pPosition) throws IOException {
mCacheFile.seek(pPosition);
}
public long getPosition() throws IOException {
return mCacheFile.getFilePointer();
}
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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 java.io.*;
/**
* A {@code SeekableInputStream} implementation that uses random access directly to a {@code File}.
* <p/>
* @see FileCacheSeekableStream
* @see MemoryCacheSeekableStream
* @see RandomAccessFile
*
* @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/FileSeekableStream.java#4 $
*/
public final class FileSeekableStream extends SeekableInputStream {
// TODO: Figure out why this class is SLOWER than FileCacheSeekableStream in
// my tests..?
final RandomAccessFile mRandomAccess;
/**
* Creates a {@code FileSeekableStream} that reads from the given
* {@code File}.
*
* @param pInput file to read from
* @throws FileNotFoundException if {@code pInput} does not exist
*/
public FileSeekableStream(final File pInput) throws FileNotFoundException {
this(new RandomAccessFile(pInput, "r"));
}
/**
* Creates a {@code FileSeekableStream} that reads from the given file.
* The {@code RandomAccessFile} needs only to be open in read
* ({@code "r"}) mode.
*
* @param pInput file to read from
*/
public FileSeekableStream(final RandomAccessFile pInput) {
mRandomAccess = pInput;
}
/// Seekable
public boolean isCached() {
return false;
}
public boolean isCachedFile() {
return false;
}
public boolean isCachedMemory() {
return false;
}
/// InputStream
@Override
public int available() throws IOException {
long length = mRandomAccess.length() - mPosition;
return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
}
public void closeImpl() throws IOException {
mRandomAccess.close();
}
public int read() throws IOException {
checkOpen();
int read = mRandomAccess.read();
if (read >= 0) {
mPosition++;
}
return read;
}
@Override
public int read(byte pBytes[], int pOffset, int pLength) throws IOException {
checkOpen();
int read = mRandomAccess.read(pBytes, pOffset, pLength);
if (read > 0) {
mPosition += read;
}
return read;
}
/**
* Does nothing, as we don't really do any caching here.
*
* @param pPosition the position to flush to
*/
protected void flushBeforeImpl(long pPosition) {
}
protected void seekImpl(long pPosition) throws IOException {
mRandomAccess.seek(pPosition);
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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 java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* FileSystem
* <p/>
*
* @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 $
*/
abstract class FileSystem {
abstract long getFreeSpace(File pPath);
abstract long getTotalSpace(File pPath);
abstract String getName();
static BufferedReader exec(String[] pArgs) throws IOException {
Process cmd = Runtime.getRuntime().exec(pArgs);
return new BufferedReader(new InputStreamReader(cmd.getInputStream()));
}
static FileSystem get() {
String os = System.getProperty("os.name");
//System.out.println("os = " + os);
os = os.toLowerCase();
if (os.indexOf("windows") != -1) {
return new Win32FileSystem();
}
else if (os.indexOf("linux") != -1 ||
os.indexOf("sun os") != -1 ||
os.indexOf("sunos") != -1 ||
os.indexOf("solaris") != -1 ||
os.indexOf("mpe/ix") != -1 ||
os.indexOf("hp-ux") != -1 ||
os.indexOf("aix") != -1 ||
os.indexOf("freebsd") != -1 ||
os.indexOf("irix") != -1 ||
os.indexOf("digital unix") != -1 ||
os.indexOf("unix") != -1 ||
os.indexOf("mac os x") != -1) {
return new UnixFileSystem();
}
else {
return new UnknownFileSystem(os);
}
}
private static class UnknownFileSystem extends FileSystem {
private final String mOSName;
UnknownFileSystem(String pOSName) {
mOSName = pOSName;
}
long getFreeSpace(File pPath) {
return 0l;
}
long getTotalSpace(File pPath) {
return 0l;
}
String getName() {
return "Unknown (" + mOSName + ")";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,239 @@
/*
* 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.util.regex.WildcardStringParser;
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 mask is given as a well-known DOS filename format, with '*' and '?' as
* wildcards.
* All other characters counts as ordinary characters.
* <p/>
* The file name masks are used as a filter input and is given to the class via
* the string array property:<br>
* <dd>{@code filenameMasksForInclusion} - Filename mask for exclusion of
* files (default if both properties are defined)
* <dd>{@code filenameMasksForExclusion} - Filename mask for exclusion of
* files.
* <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
* @see WildcardStringParser
*/
public class FilenameMaskFilter implements FilenameFilter {
// Members
private String[] mFilenameMasksForInclusion;
private String[] mFilenameMasksForExclusion;
private boolean mInclusion = true;
/**
* Creates a {@code FilenameMaskFilter}
*/
public FilenameMaskFilter() {
}
/**
* Creates a {@code FilenameMaskFilter}
*
* @param pFilenameMask the filename mask
*/
public FilenameMaskFilter(final String pFilenameMask) {
String[] filenameMask = {pFilenameMask};
setFilenameMasksForInclusion(filenameMask);
}
/**
* Creates a {@code FilenameMaskFilter}
*
* @param pFilenameMasks the filename masks
*/
public FilenameMaskFilter(final String[] pFilenameMasks) {
this(pFilenameMasks, false);
}
/**
* Creates a {@code FilenameMaskFilter}
*
* @param pFilenameMask the filename masks
* @param pExclusion if {@code true}, the masks will be excluded
*/
public FilenameMaskFilter(final String pFilenameMask, final boolean pExclusion) {
String[] filenameMask = {pFilenameMask};
if (pExclusion) {
setFilenameMasksForExclusion(filenameMask);
}
else {
setFilenameMasksForInclusion(filenameMask);
}
}
/**
* Creates a {@code FilenameMaskFilter}
*
* @param pFilenameMasks the filename masks
* @param pExclusion if {@code true}, the masks will be excluded
*/
public FilenameMaskFilter(final String[] pFilenameMasks, final boolean pExclusion) {
if (pExclusion) {
setFilenameMasksForExclusion(pFilenameMasks);
}
else {
setFilenameMasksForInclusion(pFilenameMasks);
}
}
/**
*
* @param pFilenameMasksForInclusion the filename masks to include
*/
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
mFilenameMasksForInclusion = pFilenameMasksForInclusion;
}
/**
* @return the current inclusion masks
*/
public String[] getFilenameMasksForInclusion() {
return mFilenameMasksForInclusion.clone();
}
/**
* @param pFilenameMasksForExclusion the filename masks to exclude
*/
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
mFilenameMasksForExclusion = pFilenameMasksForExclusion;
mInclusion = false;
}
/**
* @return the current exclusion masks
*/
public String[] getFilenameMasksForExclusion() {
return mFilenameMasksForExclusion.clone();
}
/**
* This method implements the {@code java.io.FilenameFilter} interface.
*
* @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(File pDir, String pName) {
WildcardStringParser parser;
// Check each filename string mask whether the file is to be accepted
if (mInclusion) { // Inclusion
for (String mask : mFilenameMasksForInclusion) {
parser = new WildcardStringParser(mask);
if (parser.parseString(pName)) {
// The filename was accepted by the filename masks provided
// - include it in filename list
return true;
}
}
// The filename not was accepted by any of the filename masks
// provided - NOT to be included in the filename list
return false;
}
else {
// Exclusion
for (String mask : mFilenameMasksForExclusion) {
parser = new WildcardStringParser(mask);
if (parser.parseString(pName)) {
// The filename was accepted by the filename masks provided
// - NOT to be included in the filename list
return false;
}
}
// The filename was not accepted by any of the filename masks
// provided - include it in filename list
return true;
}
}
/**
* @return a string representation for debug purposes
*/
public String toString() {
StringBuilder retVal = new StringBuilder();
int i;
if (mInclusion) {
// Inclusion
if (mFilenameMasksForInclusion == null) {
retVal.append("No filename masks set - property mFilenameMasksForInclusion is null!");
}
else {
retVal.append(mFilenameMasksForInclusion.length);
retVal.append(" filename mask(s) - ");
for (i = 0; i < mFilenameMasksForInclusion.length; i++) {
retVal.append("\"");
retVal.append(mFilenameMasksForInclusion[i]);
retVal.append("\", \"");
}
}
}
else {
// Exclusion
if (mFilenameMasksForExclusion == null) {
retVal.append("No filename masks set - property mFilenameMasksForExclusion is null!");
}
else {
retVal.append(mFilenameMasksForExclusion.length);
retVal.append(" exclusion filename mask(s) - ");
for (i = 0; i < mFilenameMasksForExclusion.length; i++) {
retVal.append("\"");
retVal.append(mFilenameMasksForExclusion[i]);
retVal.append("\", \"");
}
}
}
return retVal.toString();
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,427 @@
/*
* 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.
*/
/*
* From http://www.cafeaulait.org/books/javaio/ioexamples/index.html:
*
* Please feel free to use any fragment of this code you need in your own work.
* As far as I am concerned, it's in the public domain. No permission is necessary
* or required. Credit is always appreciated if you use a large chunk or base a
* significant product on one of my examples, but that's not required either.
*
* Elliotte Rusty Harold
*/
package com.twelvemonkeys.io;
import java.io.*;
/**
* A little endian input stream reads two's complement,
* little endian integers, floating point numbers, and characters
* and returns them as Java primitive types.
* <p/>
* The standard {@code java.io.DataInputStream} class
* which this class imitates reads big endian quantities.
* <p/>
* <em>Warning:
* <!-- Beware of little indians! -->
* The {@code DataInput} and {@code DataOutput} interfaces
* specifies big endian byte order in their documentation.
* This means that this class is, strictly speaking, not a proper
* implementation. However, I don't see a reason for the these interfaces to
* specify the byte order of their underlying representations.
* </em>
*
* @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
* @see java.io.DataInputStream
* @see java.io.DataInput
* @see java.io.DataOutput
*
* @author Elliotte Rusty Harold
* @version 1.0.3, 28 December 2002
*/
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
/**
* Creates a new little endian input stream and chains it to the
* input stream specified by the {@code pStream} argument.
*
* @param pStream the underlying input stream.
* @see java.io.FilterInputStream#in
*/
public LittleEndianDataInputStream(final InputStream pStream) {
super(pStream);
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
}
/**
* Reads a <code>boolean</code> from the underlying input stream by
* reading a single byte. If the byte is zero, false is returned.
* If the byte is positive, true is returned.
*
* @return the <code>boolean</code> value read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public boolean readBoolean() throws IOException {
int b = in.read();
if (b < 0) {
throw new EOFException();
}
return b != 0;
}
/**
* Reads a signed <code>byte</code> from the underlying input stream
* with value between -128 and 127
*
* @return the <code>byte</code> value read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public byte readByte() throws IOException {
int b = in.read();
if (b < 0) {
throw new EOFException();
}
return (byte) b;
}
/**
* Reads an unsigned <code>byte</code> from the underlying
* input stream with value between 0 and 255
*
* @return the <code>byte</code> value read.
* @throws EOFException if the end of the underlying input
* stream has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public int readUnsignedByte() throws IOException {
int b = in.read();
if (b < 0) {
throw new EOFException();
}
return b;
}
/**
* Reads a two byte signed <code>short</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the <code>short</code> read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public short readShort() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
// only need to test last byte read
// if byte1 is -1 so is byte2
if (byte2 < 0) {
throw new EOFException();
}
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
}
/**
* Reads a two byte unsigned <code>short</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the int value of the unsigned short read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public int readUnsignedShort() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
if (byte2 < 0) {
throw new EOFException();
}
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
return (byte2 << 8) + byte1;
}
/**
* Reads a two byte Unicode <code>char</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the int value of the unsigned short read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public char readChar() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
if (byte2 < 0) {
throw new EOFException();
}
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
}
/**
* Reads a four byte signed <code>int</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the <code>int</code> read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public int readInt() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
int byte3 = in.read();
int byte4 = in.read();
if (byte4 < 0) {
throw new EOFException();
}
return (byte4 << 24) + ((byte3 << 24) >>> 8)
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
}
/**
* Reads an eight byte signed <code>int</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the <code>int</code> read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public long readLong() throws IOException {
long byte1 = in.read();
long byte2 = in.read();
long byte3 = in.read();
long byte4 = in.read();
long byte5 = in.read();
long byte6 = in.read();
long byte7 = in.read();
long byte8 = in.read();
if (byte8 < 0) {
throw new EOFException();
}
return (byte8 << 56) + ((byte7 << 56) >>> 8)
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
}
/**
* Reads a string of no more than 65,535 characters
* from the underlying input stream using UTF-8
* encoding. This method first reads a two byte short
* in <b>big</b> endian order as required by the
* UTF-8 specification. This gives the number of bytes in
* the UTF-8 encoded version of the string.
* Next this many bytes are read and decoded as UTF-8
* encoded characters.
*
* @return the decoded string
* @throws UTFDataFormatException if the string cannot be decoded
* @throws IOException if the underlying stream throws an IOException.
*/
public String readUTF() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
if (byte2 < 0) {
throw new EOFException();
}
int numbytes = (byte1 << 8) + byte2;
char result[] = new char[numbytes];
int numread = 0;
int numchars = 0;
while (numread < numbytes) {
int c1 = readUnsignedByte();
int c2, c3;
// The first four bits of c1 determine how many bytes are in this char
int test = c1 >> 4;
if (test < 8) { // one byte
numread++;
result[numchars++] = (char) c1;
}
else if (test == 12 || test == 13) { // two bytes
numread += 2;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
if ((c2 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
}
else if (test == 14) { // three bytes
numread += 3;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
c3 = readUnsignedByte();
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException();
}
result[numchars++] = (char)
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
}
else { // malformed
throw new UTFDataFormatException();
}
} // end while
return new String(result, 0, numchars);
}
/**
* @return the next eight bytes of this input stream, interpreted as a
* little endian <code>double</code>.
* @throws EOFException if end of stream occurs before eight bytes
* have been read.
* @throws IOException if an I/O error occurs.
*/
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
/**
* @return the next four bytes of this input stream, interpreted as a
* little endian <code>int</code>.
* @throws EOFException if end of stream occurs before four bytes
* have been read.
* @throws IOException if an I/O error occurs.
*/
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
/**
* See the general contract of the <code>skipBytes</code>
* method of <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @param pLength the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @exception IOException if an I/O error occurs.
*/
public final int skipBytes(int pLength) throws IOException {
// NOTE: There was probably a bug in ERH's original code here, as skip
// never returns -1, but returns 0 if no more bytes can be skipped...
int total = 0;
int skipped;
while ((total < pLength) && ((skipped = (int) in.skip(pLength - total)) > 0)) {
total += skipped;
}
return total;
}
/**
* See the general contract of the <code>readFully</code>
* method of <code>DataInput</code>.
* <p/>
* Bytes
* for this operation are read from the contained
* input stream.
*
* @param pBytes the buffer into which the data is read.
* @throws EOFException if this input stream reaches the end before
* reading all the bytes.
* @throws IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public final void readFully(byte pBytes[]) throws IOException {
readFully(pBytes, 0, pBytes.length);
}
/**
* See the general contract of the <code>readFully</code>
* method of <code>DataInput</code>.
* <p/>
* Bytes
* for this operation are read from the contained
* input stream.
*
* @param pBytes the buffer into which the data is read.
* @param pOffset the start offset of the data.
* @param pLength the number of bytes to read.
* @throws EOFException if this input stream reaches the end before
* reading all the bytes.
* @throws IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public final void readFully(byte pBytes[], int pOffset, int pLength) throws IOException {
if (pLength < 0) {
throw new IndexOutOfBoundsException();
}
int count = 0;
while (count < pLength) {
int read = in.read(pBytes, pOffset + count, pLength - count);
if (read < 0) {
throw new EOFException();
}
count += read;
}
}
/**
* See the general contract of the <code>readLine</code>
* method of <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @deprecated This method does not properly convert bytes to characters.
*
* @return the next line of text from this input stream.
* @exception IOException if an I/O error occurs.
* @see java.io.BufferedReader#readLine()
* @see java.io.DataInputStream#readLine()
* @noinspection deprecation
*/
public String readLine() throws IOException {
DataInputStream ds = new DataInputStream(in);
return ds.readLine();
}
}

View File

@@ -0,0 +1,334 @@
/*
* 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.
*/
/*
* From http://www.cafeaulait.org/books/javaio/ioexamples/index.html:
*
* Please feel free to use any fragment of this code you need in your own work.
* As far as I am concerned, it's in the public domain. No permission is necessary
* or required. Credit is always appreciated if you use a large chunk or base a
* significant product on one of my examples, but that's not required either.
*
* Elliotte Rusty Harold
*/
package com.twelvemonkeys.io;
import java.io.*;
/**
* A little endian output stream writes primitive Java numbers
* and characters to an output stream in a little endian format.
* <p/>
* The standard {@code java.io.DataOutputStream} class which this class
* imitates uses big endian integers.
* <p/>
* <em>Warning:
* <!-- Beware of little indians! -->
* The {@code DataInput} and {@code DataOutput} interfaces
* specifies big endian byte order in their documentation.
* This means that this class is, strictly speaking, not a proper
* implementation. However, I don't see a reason for the these interfaces to
* specify the byte order of their underlying representations.
* </em>
*
* @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
* @see java.io.DataOutputStream
* @see java.io.DataInput
* @see java.io.DataOutput
*
* @author Elliotte Rusty Harold
* @version 1.0.1, 19 May 1999
*/
public class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput {
/**
* The number of bytes written so far to the little endian output stream.
*/
protected int mWritten;
/**
* Creates a new little endian output stream and chains it to the
* output stream specified by the {@code pStream} argument.
*
* @param pStream the underlying output stream.
* @see java.io.FilterOutputStream#out
*/
public LittleEndianDataOutputStream(OutputStream pStream) {
super(pStream);
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
}
/**
* Writes the specified byte value to the underlying output stream.
*
* @param pByte the <code>byte</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public synchronized void write(int pByte) throws IOException {
out.write(pByte);
mWritten++;
}
/**
* Writes <code>pLength</code> bytes from the specified byte array
* starting at <code>pOffset</code> to the underlying output stream.
*
* @param pBytes the data.
* @param pOffset the start offset in the data.
* @param pLength the number of bytes to write.
* @throws IOException if the underlying stream throws an IOException.
*/
public synchronized void write(byte[] pBytes, int pOffset, int pLength)
throws IOException {
out.write(pBytes, pOffset, pLength);
mWritten += pLength;
}
/**
* Writes a <code>boolean</code> to the underlying output stream as
* a single byte. If the argument is true, the byte value 1 is written.
* If the argument is false, the byte value <code>0</code> in written.
*
* @param pBoolean the <code>boolean</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeBoolean(boolean pBoolean) throws IOException {
if (pBoolean) {
write(1);
}
else {
write(0);
}
}
/**
* Writes out a <code>byte</code> to the underlying output stream
*
* @param pByte the <code>byte</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeByte(int pByte) throws IOException {
out.write(pByte);
mWritten++;
}
/**
* Writes a two byte <code>short</code> to the underlying output stream in
* little endian order, low byte first.
*
* @param pShort the <code>short</code> to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeShort(int pShort) throws IOException {
out.write(pShort & 0xFF);
out.write((pShort >>> 8) & 0xFF);
mWritten += 2;
}
/**
* Writes a two byte <code>char</code> to the underlying output stream
* in little endian order, low byte first.
*
* @param pChar the <code>char</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeChar(int pChar) throws IOException {
out.write(pChar & 0xFF);
out.write((pChar >>> 8) & 0xFF);
mWritten += 2;
}
/**
* Writes a four-byte <code>int</code> to the underlying output stream
* in little endian order, low byte first, high byte last
*
* @param pInt the <code>int</code> to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeInt(int pInt) throws IOException {
out.write(pInt & 0xFF);
out.write((pInt >>> 8) & 0xFF);
out.write((pInt >>> 16) & 0xFF);
out.write((pInt >>> 24) & 0xFF);
mWritten += 4;
}
/**
* Writes an eight-byte <code>long</code> to the underlying output stream
* in little endian order, low byte first, high byte last
*
* @param pLong the <code>long</code> to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeLong(long pLong) throws IOException {
out.write((int) pLong & 0xFF);
out.write((int) (pLong >>> 8) & 0xFF);
out.write((int) (pLong >>> 16) & 0xFF);
out.write((int) (pLong >>> 24) & 0xFF);
out.write((int) (pLong >>> 32) & 0xFF);
out.write((int) (pLong >>> 40) & 0xFF);
out.write((int) (pLong >>> 48) & 0xFF);
out.write((int) (pLong >>> 56) & 0xFF);
mWritten += 8;
}
/**
* Writes a 4 byte Java float to the underlying output stream in
* little endian order.
*
* @param f the <code>float</code> value to be written.
* @throws IOException if an I/O error occurs.
*/
public final void writeFloat(float f) throws IOException {
writeInt(Float.floatToIntBits(f));
}
/**
* Writes an 8 byte Java double to the underlying output stream in
* little endian order.
*
* @param d the <code>double</code> value to be written.
* @throws IOException if an I/O error occurs.
*/
public final void writeDouble(double d) throws IOException {
writeLong(Double.doubleToLongBits(d));
}
/**
* Writes a string to the underlying output stream as a sequence of
* bytes. Each character is written to the data output stream as
* if by the <code>writeByte()</code> method.
*
* @param pString the <code>String</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
* @see #writeByte(int)
* @see #out
*/
public void writeBytes(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
out.write((byte) pString.charAt(i));
}
mWritten += length;
}
/**
* Writes a string to the underlying output stream as a sequence of
* characters. Each character is written to the data output stream as
* if by the <code>writeChar</code> method.
*
* @param pString a <code>String</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
* @see #writeChar(int)
* @see #out
*/
public void writeChars(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
int c = pString.charAt(i);
out.write(c & 0xFF);
out.write((c >>> 8) & 0xFF);
}
mWritten += length * 2;
}
/**
* Writes a string of no more than 65,535 characters
* to the underlying output stream using UTF-8
* encoding. This method first writes a two byte short
* in <b>big</b> endian order as required by the
* UTF-8 specification. This gives the number of bytes in the
* UTF-8 encoded version of the string, not the number of characters
* in the string. Next each character of the string is written
* using the UTF-8 encoding for the character.
*
* @param pString the string to be written.
* @throws UTFDataFormatException if the string is longer than
* 65,535 characters.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeUTF(String pString) throws IOException {
int numchars = pString.length();
int numbytes = 0;
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
numbytes++;
}
else if (c > 0x07FF) {
numbytes += 3;
}
else {
numbytes += 2;
}
}
if (numbytes > 65535) {
throw new UTFDataFormatException();
}
out.write((numbytes >>> 8) & 0xFF);
out.write(numbytes & 0xFF);
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
out.write(c);
}
else if (c > 0x07FF) {
out.write(0xE0 | ((c >> 12) & 0x0F));
out.write(0x80 | ((c >> 6) & 0x3F));
out.write(0x80 | (c & 0x3F));
mWritten += 2;
}
else {
out.write(0xC0 | ((c >> 6) & 0x1F));
out.write(0x80 | (c & 0x3F));
mWritten += 1;
}
}
mWritten += numchars + 2;
}
/**
* Returns the number of bytes written to this little endian output stream.
* (This class is not thread-safe with respect to this method. It is
* possible that this number is temporarily less than the actual
* number of bytes written.)
* @return the value of the <code>written</code> field.
* @see #mWritten
*/
public int size() {
return mWritten;
}
}

View File

@@ -0,0 +1,600 @@
/*
* 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 java.io.*;
import java.nio.channels.FileChannel;
/**
* A replacement for {@link java.io.RandomAccessFile} that is capable of reading
* and writing data in little endian byte order.
* <p/>
* <em>Warning:
* <!-- Beware of little indians! -->
* The {@code DataInput} and {@code DataOutput} interfaces
* specifies big endian byte order in their documentation.
* This means that this class is, strictly speaking, not a proper
* implementation. However, I don't see a reason for the these interfaces to
* specify the byte order of their underlying representations.
* </em>
*
* @see com.twelvemonkeys.io.LittleEndianDataInputStream
* @see com.twelvemonkeys.io.LittleEndianDataOutputStream
* @see java.io.RandomAccessFile
* @see java.io.DataInput
* @see java.io.DataOutput
*
* @author Elliotte Rusty Harold
* @author <a href="mailto:harald.kuhr@gmail.no">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/io/LittleEndianRandomAccessFile.java#1 $
*/
public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
private RandomAccessFile mFile;
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
this(FileUtil.resolve(pName), pMode);
}
public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException {
mFile = new RandomAccessFile(pFile, pMode);
}
public void close() throws IOException {
mFile.close();
}
public FileChannel getChannel() {
return mFile.getChannel();
}
public FileDescriptor getFD() throws IOException {
return mFile.getFD();
}
public long getFilePointer() throws IOException {
return mFile.getFilePointer();
}
public long length() throws IOException {
return mFile.length();
}
public int read() throws IOException {
return mFile.read();
}
public int read(final byte[] b) throws IOException {
return mFile.read(b);
}
public int read(final byte[] b, final int off, final int len) throws IOException {
return mFile.read(b, off, len);
}
public void readFully(final byte[] b) throws IOException {
mFile.readFully(b);
}
public void readFully(final byte[] b, final int off, final int len) throws IOException {
mFile.readFully(b, off, len);
}
public String readLine() throws IOException {
return mFile.readLine();
}
/**
* Reads a <code>boolean</code> from the underlying input stream by
* reading a single byte. If the byte is zero, false is returned.
* If the byte is positive, true is returned.
*
* @return the <code>boolean</code> value read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public boolean readBoolean() throws IOException {
int b = mFile.read();
if (b < 0) {
throw new EOFException();
}
return b != 0;
}
/**
* Reads a signed <code>byte</code> from the underlying input stream
* with value between -128 and 127
*
* @return the <code>byte</code> value read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public byte readByte() throws IOException {
int b = mFile.read();
if (b < 0) {
throw new EOFException();
}
return (byte) b;
}
/**
* Reads an unsigned <code>byte</code> from the underlying
* input stream with value between 0 and 255
*
* @return the <code>byte</code> value read.
* @throws EOFException if the end of the underlying input
* stream has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public int readUnsignedByte() throws IOException {
int b = mFile.read();
if (b < 0) {
throw new EOFException();
}
return b;
}
/**
* Reads a two byte signed <code>short</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the <code>short</code> read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public short readShort() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
// only need to test last byte read
// if byte1 is -1 so is byte2
if (byte2 < 0) {
throw new EOFException();
}
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
}
/**
* Reads a two byte unsigned <code>short</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the int value of the unsigned short read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public int readUnsignedShort() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
if (byte2 < 0) {
throw new EOFException();
}
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
return (byte2 << 8) + byte1;
}
/**
* Reads a two byte Unicode <code>char</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the int value of the unsigned short read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public char readChar() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
if (byte2 < 0) {
throw new EOFException();
}
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
}
/**
* Reads a four byte signed <code>int</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the <code>int</code> read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public int readInt() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
int byte3 = mFile.read();
int byte4 = mFile.read();
if (byte4 < 0) {
throw new EOFException();
}
return (byte4 << 24) + ((byte3 << 24) >>> 8)
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
}
/**
* Reads an eight byte signed <code>int</code> from the underlying
* input stream in little endian order, low byte first.
*
* @return the <code>int</code> read.
* @throws EOFException if the end of the underlying input stream
* has been reached
* @throws IOException if the underlying stream throws an IOException.
*/
public long readLong() throws IOException {
long byte1 = mFile.read();
long byte2 = mFile.read();
long byte3 = mFile.read();
long byte4 = mFile.read();
long byte5 = mFile.read();
long byte6 = mFile.read();
long byte7 = mFile.read();
long byte8 = mFile.read();
if (byte8 < 0) {
throw new EOFException();
}
return (byte8 << 56) + ((byte7 << 56) >>> 8)
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
}
/**
* Reads a string of no more than 65,535 characters
* from the underlying input stream using UTF-8
* encoding. This method first reads a two byte short
* in <b>big</b> endian order as required by the
* UTF-8 specification. This gives the number of bytes in
* the UTF-8 encoded version of the string.
* Next this many bytes are read and decoded as UTF-8
* encoded characters.
*
* @return the decoded string
* @throws UTFDataFormatException if the string cannot be decoded
* @throws IOException if the underlying stream throws an IOException.
*/
public String readUTF() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
if (byte2 < 0) {
throw new EOFException();
}
int numbytes = (byte1 << 8) + byte2;
char result[] = new char[numbytes];
int numread = 0;
int numchars = 0;
while (numread < numbytes) {
int c1 = readUnsignedByte();
int c2, c3;
// The first four bits of c1 determine how many bytes are in this char
int test = c1 >> 4;
if (test < 8) { // one byte
numread++;
result[numchars++] = (char) c1;
}
else if (test == 12 || test == 13) { // two bytes
numread += 2;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
if ((c2 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
}
else if (test == 14) { // three bytes
numread += 3;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
c3 = readUnsignedByte();
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException();
}
result[numchars++] = (char)
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
}
else { // malformed
throw new UTFDataFormatException();
}
} // end while
return new String(result, 0, numchars);
}
/**
* @return the next eight bytes of this input stream, interpreted as a
* little endian <code>double</code>.
* @throws EOFException if end of stream occurs before eight bytes
* have been read.
* @throws IOException if an I/O error occurs.
*/
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
/**
* @return the next four bytes of this input stream, interpreted as a
* little endian <code>int</code>.
* @throws EOFException if end of stream occurs before four bytes
* have been read.
* @throws IOException if an I/O error occurs.
*/
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
/**
* Sets the file-pointer offset, measured from the beginning of this
* file, at which the next read or write occurs. The offset may be
* set beyond the end of the file. Setting the offset beyond the end
* of the file does not change the file length. The file length will
* change only by writing after the offset has been set beyond the end
* of the file.
*
* @param pos the offset position, measured in bytes from the
* beginning of the file, at which to set the file
* pointer.
* @exception IOException if <code>pos</code> is less than
* <code>0</code> or if an I/O error occurs.
*/
public void seek(final long pos) throws IOException {
mFile.seek(pos);
}
public void setLength(final long newLength) throws IOException {
mFile.setLength(newLength);
}
public int skipBytes(final int n) throws IOException {
return mFile.skipBytes(n);
}
public void write(final byte[] b) throws IOException {
mFile.write(b);
}
public void write(final byte[] b, final int off, final int len) throws IOException {
mFile.write(b, off, len);
}
public void write(final int b) throws IOException {
mFile.write(b);
}
/**
* Writes a <code>boolean</code> to the underlying output stream as
* a single byte. If the argument is true, the byte value 1 is written.
* If the argument is false, the byte value <code>0</code> in written.
*
* @param pBoolean the <code>boolean</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeBoolean(boolean pBoolean) throws IOException {
if (pBoolean) {
write(1);
}
else {
write(0);
}
}
/**
* Writes out a <code>byte</code> to the underlying output stream
*
* @param pByte the <code>byte</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeByte(int pByte) throws IOException {
mFile.write(pByte);
}
/**
* Writes a two byte <code>short</code> to the underlying output stream in
* little endian order, low byte first.
*
* @param pShort the <code>short</code> to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeShort(int pShort) throws IOException {
mFile.write(pShort & 0xFF);
mFile.write((pShort >>> 8) & 0xFF);
}
/**
* Writes a two byte <code>char</code> to the underlying output stream
* in little endian order, low byte first.
*
* @param pChar the <code>char</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeChar(int pChar) throws IOException {
mFile.write(pChar & 0xFF);
mFile.write((pChar >>> 8) & 0xFF);
}
/**
* Writes a four-byte <code>int</code> to the underlying output stream
* in little endian order, low byte first, high byte last
*
* @param pInt the <code>int</code> to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeInt(int pInt) throws IOException {
mFile.write(pInt & 0xFF);
mFile.write((pInt >>> 8) & 0xFF);
mFile.write((pInt >>> 16) & 0xFF);
mFile.write((pInt >>> 24) & 0xFF);
}
/**
* Writes an eight-byte <code>long</code> to the underlying output stream
* in little endian order, low byte first, high byte last
*
* @param pLong the <code>long</code> to be written.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeLong(long pLong) throws IOException {
mFile.write((int) pLong & 0xFF);
mFile.write((int) (pLong >>> 8) & 0xFF);
mFile.write((int) (pLong >>> 16) & 0xFF);
mFile.write((int) (pLong >>> 24) & 0xFF);
mFile.write((int) (pLong >>> 32) & 0xFF);
mFile.write((int) (pLong >>> 40) & 0xFF);
mFile.write((int) (pLong >>> 48) & 0xFF);
mFile.write((int) (pLong >>> 56) & 0xFF);
}
/**
* Writes a 4 byte Java float to the underlying output stream in
* little endian order.
*
* @param f the <code>float</code> value to be written.
* @throws IOException if an I/O error occurs.
*/
public final void writeFloat(float f) throws IOException {
writeInt(Float.floatToIntBits(f));
}
/**
* Writes an 8 byte Java double to the underlying output stream in
* little endian order.
*
* @param d the <code>double</code> value to be written.
* @throws IOException if an I/O error occurs.
*/
public final void writeDouble(double d) throws IOException {
writeLong(Double.doubleToLongBits(d));
}
/**
* Writes a string to the underlying output stream as a sequence of
* bytes. Each character is written to the data output stream as
* if by the <code>writeByte()</code> method.
*
* @param pString the <code>String</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
* @see #writeByte(int)
* @see #mFile
*/
public void writeBytes(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
mFile.write((byte) pString.charAt(i));
}
}
/**
* Writes a string to the underlying output stream as a sequence of
* characters. Each character is written to the data output stream as
* if by the <code>writeChar</code> method.
*
* @param pString a <code>String</code> value to be written.
* @throws IOException if the underlying stream throws an IOException.
* @see #writeChar(int)
* @see #mFile
*/
public void writeChars(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
int c = pString.charAt(i);
mFile.write(c & 0xFF);
mFile.write((c >>> 8) & 0xFF);
}
}
/**
* Writes a string of no more than 65,535 characters
* to the underlying output stream using UTF-8
* encoding. This method first writes a two byte short
* in <b>big</b> endian order as required by the
* UTF-8 specification. This gives the number of bytes in the
* UTF-8 encoded version of the string, not the number of characters
* in the string. Next each character of the string is written
* using the UTF-8 encoding for the character.
*
* @param pString the string to be written.
* @throws UTFDataFormatException if the string is longer than
* 65,535 characters.
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeUTF(String pString) throws IOException {
int numchars = pString.length();
int numbytes = 0;
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
numbytes++;
}
else if (c > 0x07FF) {
numbytes += 3;
}
else {
numbytes += 2;
}
}
if (numbytes > 65535) {
throw new UTFDataFormatException();
}
mFile.write((numbytes >>> 8) & 0xFF);
mFile.write(numbytes & 0xFF);
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
mFile.write(c);
}
else if (c > 0x07FF) {
mFile.write(0xE0 | ((c >> 12) & 0x0F));
mFile.write(0x80 | ((c >> 6) & 0x3F));
mFile.write(0x80 | (c & 0x3F));
}
else {
mFile.write(0xC0 | ((c >> 6) & 0x1F));
mFile.write(0x80 | (c & 0x3F));
}
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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 java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* A {@code SeekableInputStream} implementation that caches data in memory.
* <p/>
*
* @see FileCacheSeekableStream
*
* @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/MemoryCacheSeekableStream.java#3 $
*/
public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStream {
/**
* Creates a {@code MemoryCacheSeekableStream}, reading from the given
* {@code InputStream}. Data will be cached in memory.
*
* @param pStream the {@code InputStream} to read from.
*/
public MemoryCacheSeekableStream(final InputStream pStream) {
super(pStream, new MemoryCache());
}
public final boolean isCachedMemory() {
return true;
}
public final boolean isCachedFile() {
return false;
}
final static class MemoryCache extends StreamCache {
final static int BLOCK_SIZE = 1 << 13;
private final List<byte[]> mCache = new ArrayList<byte[]>();
private long mLength;
private long mPosition;
private long mStart;
private byte[] getBlock() throws IOException {
final long currPos = mPosition - mStart;
if (currPos < 0) {
throw new IOException("StreamCache flushed before read position");
}
long index = currPos / BLOCK_SIZE;
if (index >= Integer.MAX_VALUE) {
throw new IOException("Memory cache max size exceeded");
}
if (index >= mCache.size()) {
try {
mCache.add(new byte[BLOCK_SIZE]);
// System.out.println("Allocating new block, size: " + BLOCK_SIZE);
// System.out.println("New total size: " + mCache.size() * BLOCK_SIZE + " (" + mCache.size() + " blocks)");
}
catch (OutOfMemoryError e) {
throw new IOException("No more memory for cache: " + mCache.size() * BLOCK_SIZE);
}
}
//System.out.println("index: " + index);
return mCache.get((int) index);
}
public void write(final int pByte) throws IOException {
byte[] buffer = getBlock();
int idx = (int) (mPosition % BLOCK_SIZE);
buffer[idx] = (byte) pByte;
mPosition++;
if (mPosition > mLength) {
mLength = mPosition;
}
}
// TODO: OptimizeMe!!!
@Override
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
byte[] buffer = getBlock();
for (int i = 0; i < pLength; i++) {
int index = (int) mPosition % BLOCK_SIZE;
if (index == 0) {
buffer = getBlock();
}
buffer[index] = pBuffer[pOffset + i];
mPosition++;
}
if (mPosition > mLength) {
mLength = mPosition;
}
}
public int read() throws IOException {
if (mPosition >= mLength) {
return -1;
}
byte[] buffer = getBlock();
int idx = (int) (mPosition % BLOCK_SIZE);
mPosition++;
return buffer[idx] & 0xff;
}
// TODO: OptimizeMe!!!
@Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (mPosition >= mLength) {
return -1;
}
byte[] buffer = getBlock();
int bufferPos = (int) (mPosition % BLOCK_SIZE);
// Find maxIdx and simplify test in for-loop
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), mLength - mPosition);
int i;
//for (i = 0; i < pLength && i < buffer.length - idx && i < mLength - mPosition; i++) {
for (i = 0; i < maxLen; i++) {
pBytes[pOffset + i] = buffer[bufferPos + i];
}
mPosition += i;
return i;
}
public void seek(final long pPosition) throws IOException {
if (pPosition < mStart) {
throw new IOException("Seek before flush position");
}
mPosition = pPosition;
}
@Override
public void flush(final long pPosition) {
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
for (int i = 0; i < firstPos; i++) {
mCache.remove(0);
}
mStart = pPosition;
}
public long getPosition() {
return mPosition;
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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 java.io.IOException;
import java.io.InputStream;
/**
* An {@code InputStream} that contains no bytes.
* <p/>
*
* @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/NullInputStream.java#2 $
*/
public class NullInputStream extends InputStream {
/**
* Creates a {@code NullInputStream}.
*/
public NullInputStream() {
}
/**
* This implementation returns {@code -1} (EOF), always.
*
* @return {@code -1}
* @throws IOException
*/
public int read() throws IOException {
return -1;
}
/**
* This implementation returns {@code 0}, always.
*
* @return {@code 0}
* @throws IOException
*/
@Override
public int available() throws IOException {
return 0;
}
/**
* This implementation returns {@code 0}, always.
*
* @return {@code 0}
* @throws IOException
*/
@Override
public long skip(long pOffset) throws IOException {
return 0l;
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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 java.io.IOException;
import java.io.OutputStream;
/**
* An {@code OutputStream} implementation that works as a sink.
* <p/>
*
* @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/NullOutputStream.java#2 $
*/
public class NullOutputStream extends OutputStream {
/**
* Creates a {@code NullOutputStream}.
*/
public NullOutputStream() {
}
/**
* This implementation does nothing.
*/
public void write(int pByte) throws IOException {
}
/**
* This implementation does nothing.
*/
@Override
public void write(byte pBytes[]) throws IOException {
}
/**
* This implementation does nothing.
*/
@Override
public void write(byte pBytes[], int pOffset, int pLength) throws IOException {
}
}

View File

@@ -0,0 +1,240 @@
/*
* 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 java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.EOFException;
/**
* A data stream that is both readable and writable, much like a
* {@code RandomAccessFile}, except it may be backed by something other than a file.
* <p/>
*
* @see java.io.RandomAccessFile
*
* @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/io/RandomAccessStream.java#3 $
*/
public abstract class RandomAccessStream implements Seekable, DataInput, DataOutput {
// TODO: Use a RandomAcceessFile as backing in impl, probably
// TODO: Create an in-memory implementation too?
// TODO: Package private SeekableDelegate?
// TODO: Both read and write must update stream position
//private int mPosition = -1;
/** This random access stream, wrapped in an {@code InputStream} */
SeekableInputStream mInputView = null;
/** This random access stream, wrapped in an {@code OutputStream} */
SeekableOutputStream mOutputView = null;
// TODO: Create an Input and an Output interface matching InputStream and OutputStream?
public int read() throws IOException {
try {
return readByte() & 0xff;
}
catch (EOFException e) {
return -1;
}
}
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (pBytes == null) {
throw new NullPointerException("bytes == null");
}
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException();
}
else if (pLength == 0) {
return 0;
}
// Special case, allready at EOF
int c = read();
if (c == -1) {
return -1;
}
// Otherwise, read as many as bytes as possible
pBytes[pOffset] = (byte) c;
int i = 1;
try {
for (; i < pLength; i++) {
c = read();
if (c == -1) {
break;
}
pBytes[pOffset + i] = (byte) c;
}
}
catch (IOException ignore) {
// Ignore exception, just return length
}
return i;
}
public final int read(byte[] pBytes) throws IOException {
return read(pBytes, 0, pBytes != null ? pBytes.length : 1);
}
/**
* Returns an input view of this {@code RandomAccessStream}.
* Invoking this method several times, will return the same object.
* <p/>
* <em>Note that read access is NOT synchronized.</em>
*
* @return a {@code SeekableInputStream} reading from this stream
*/
public final SeekableInputStream asInputStream() {
if (mInputView == null) {
mInputView = new InputStreamView(this);
}
return mInputView;
}
/**
* Returns an output view of this {@code RandomAccessStream}.
* Invoking this method several times, will return the same object.
* <p/>
* <em>Note that write access is NOT synchronized.</em>
*
* @return a {@code SeekableOutputStream} writing to this stream
*/
public final SeekableOutputStream asOutputStream() {
if (mOutputView == null) {
mOutputView = new OutputStreamView(this);
}
return mOutputView;
}
static final class InputStreamView extends SeekableInputStream {
// TODO: Consider adding synchonization (on mStream) for all operations
// TODO: Is is a good thing that close/flush etc works on mStream?
// - Or should it rather just work on the views?
// - Allow multiple views?
final private RandomAccessStream mStream;
public InputStreamView(RandomAccessStream pStream) {
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
mStream = pStream;
}
public boolean isCached() {
return mStream.isCached();
}
public boolean isCachedFile() {
return mStream.isCachedFile();
}
public boolean isCachedMemory() {
return mStream.isCachedMemory();
}
protected void closeImpl() throws IOException {
mStream.close();
}
protected void flushBeforeImpl(long pPosition) throws IOException {
mStream.flushBefore(pPosition);
}
protected void seekImpl(long pPosition) throws IOException {
mStream.seek(pPosition);
}
public int read() throws IOException {
return mStream.read();
}
@Override
public int read(byte pBytes[], int pOffset, int pLength) throws IOException {
return mStream.read(pBytes, pOffset, pLength);
}
}
static final class OutputStreamView extends SeekableOutputStream {
// TODO: Consider adding synchonization (on mStream) for all operations
// TODO: Is is a good thing that close/flush etc works on mStream?
// - Or should it rather just work on the views?
// - Allow multiple views?
final private RandomAccessStream mStream;
public OutputStreamView(RandomAccessStream pStream) {
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
mStream = pStream;
}
public boolean isCached() {
return mStream.isCached();
}
public boolean isCachedFile() {
return mStream.isCachedFile();
}
public boolean isCachedMemory() {
return mStream.isCachedMemory();
}
protected void closeImpl() throws IOException {
mStream.close();
}
protected void flushBeforeImpl(long pPosition) throws IOException {
mStream.flushBefore(pPosition);
}
protected void seekImpl(long pPosition) throws IOException {
mStream.seek(pPosition);
}
public void write(int pByte) throws IOException {
mStream.write(pByte);
}
@Override
public void write(byte pBytes[], int pOffset, int pLength) throws IOException {
mStream.write(pBytes, pOffset, pLength);
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* 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 java.io.IOException;
/**
* Interface for seekable streams.
* <p/>
* @see SeekableInputStream
* @see SeekableOutputStream
*
* @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/Seekable.java#1 $
*/
public interface Seekable {
/**
* Returns the current byte position of the stream. The next read will take
* place starting at this offset.
*
* @return a {@code long} containing the position of the stream.
* @throws IOException if an I/O error occurs.
*/
long getStreamPosition() throws IOException;
/**
* Sets the current stream position to the desired location.
* The next read will occur at this location.
* <p/>
* An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller
* than the flushed position (as returned by {@link #getFlushedPosition()}).
* <p/>
* It is legal to seek past the end of the file; an {@code EOFException}
* will be thrown only if a read is performed.
*
* @param pPosition a long containing the desired file pointer position.
*
* @throws IndexOutOfBoundsException if {@code pPosition} is smaller than
* the flushed position.
* @throws IOException if any other I/O error occurs.
*/
void seek(long pPosition) throws IOException;
/**
* Marks a position in the stream to be returned to by a subsequent call to
* reset.
* Unlike a standard {@code InputStream}, all {@code Seekable}
* streams upport marking. Additionally, calls to {@code mark} and
* {@code reset} may be nested arbitrarily.
* <p/>
* Unlike the {@code mark} methods declared by the {@code Reader} or
* {@code InputStream}
* interfaces, no {@code readLimit} parameter is used. An arbitrary amount
* of data may be read following the call to {@code mark}.
*/
void mark();
/**
* Returns the file pointer to its previous position,
* at the time of the most recent unmatched call to mark.
* <p/>
* Calls to reset without a corresponding call to mark will either:
* <ul>
* <li>throw an {@code IOException}</li>
* <li>or, reset to the beginning of the stream.</li>
* </ul>
* An {@code IOException} will be thrown if the previous marked position
* lies in the discarded portion of the stream.
*
* @throws IOException if an I/O error occurs.
* @see java.io.InputStream#reset()
*/
void reset() throws IOException;
/**
* Discards the initial portion of the stream prior to the indicated
* postion. Attempting to seek to an offset within the flushed portion of
* the stream will result in an {@code IndexOutOfBoundsException}.
* <p/>
* Calling {@code flushBefore} may allow classes implementing this
* interface to free up resources such as memory or disk space that are
* being used to store data from the stream.
*
* @param pPosition a long containing the length of the file prefix that
* may be flushed.
*
* @throws IndexOutOfBoundsException if {@code pPosition} lies in the
* flushed portion of the stream or past the current stream position.
* @throws IOException if an I/O error occurs.
*/
void flushBefore(long pPosition) throws IOException;
/**
* Discards the initial position of the stream prior to the current stream
* position. Equivalent to {@code flushBefore(getStreamPosition())}.
*
* @throws IOException if an I/O error occurs.
*/
void flush() throws IOException;
/**
* Returns the earliest position in the stream to which seeking may be
* performed. The returned value will be the maximum of all values passed
* into previous calls to {@code flushBefore}.
*
* @return the earliest legal position for seeking, as a {@code long}.
*
* @throws IOException if an I/O error occurs.
*/
long getFlushedPosition() throws IOException;
/**
* Returns true if this {@code Seekable} stream caches data itself in order
* to allow seeking backwards. Applications may consult this in order to
* decide how frequently, or whether, to flush in order to conserve cache
* resources.
*
* @return {@code true} if this {@code Seekable} caches data.
* @see #isCachedMemory()
* @see #isCachedFile()
*/
boolean isCached();
/**
* Returns true if this {@code Seekable} stream caches data itself in order
* to allow seeking backwards, and the cache is kept in main memory.
* Applications may consult this in order to decide how frequently, or
* whether, to flush in order to conserve cache resources.
*
* @return {@code true} if this {@code Seekable} caches data in main
* memory.
* @see #isCached()
* @see #isCachedFile()
*/
boolean isCachedMemory();
/**
* Returns true if this {@code Seekable} stream caches data itself in
* order to allow seeking backwards, and the cache is kept in a
* temporary file.
* Applications may consult this in order to decide how frequently,
* or whether, to flush in order to conserve cache resources.
*
* @return {@code true} if this {@code Seekable} caches data in a
* temporary file.
* @see #isCached
* @see #isCachedMemory
*/
boolean isCachedFile();
/**
* Closes the stream.
*
* @throws java.io.IOException if the stream can't be closed.
*/
void close() throws IOException;
}

View File

@@ -0,0 +1,224 @@
/*
* 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 java.io.IOException;
import java.io.InputStream;
import java.util.Stack;
/**
* Abstract base class for {@code InputStream}s implementing the {@code Seekable} interface.
* <p/>
* @see SeekableOutputStream
*
* @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/SeekableInputStream.java#4 $
*/
public abstract class SeekableInputStream extends InputStream implements Seekable {
// 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
// updated from the read/read/read methods...
/** The stream position in this stream */
long mPosition;
long mFlushedPosition;
boolean mClosed;
protected Stack<Long> mMarkedPositions = new Stack<Long>();
/// InputStream overrides
@Override
public final int read(byte[] pBytes) throws IOException {
return read(pBytes, 0, pBytes != null ? pBytes.length : 1);
}
/**
* Implemented using {@code seek(currentPos + pLength)}.
*
* @param pLength the number of bytes to skip
* @return the actual number of bytes skipped (may be equal to or less
* than {@code pLength})
*
* @throws IOException if an I/O exception occurs during skip
*/
@Override
public final long skip(long pLength) throws IOException {
long pos = mPosition;
if (pos + pLength < mFlushedPosition) {
throw new IOException("position < flushedPosition");
}
// Stop at stream length for compatibility, even though it's allowed
// to seek past end of stream
seek(Math.min(pos + pLength, pos + available()));
return mPosition - pos;
}
@Override
public final void mark(int pLimit) {
mark();
// TODO: We don't really need to do this.. Is it a good idea?
try {
flushBefore(Math.max(mPosition - pLimit, mFlushedPosition));
}
catch (IOException ignore) {
// Ignore, as it's not really critical
}
}
/**
* Returns {@code true}, as marking is always supported.
*
* @return {@code true}.
*/
@Override
public final boolean markSupported() {
return true;
}
/// Seekable implementation
public final void seek(long pPosition) throws IOException {
checkOpen();
// NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException),
// but it's kind of inconsistent with reset that throws IOException...
if (pPosition < mFlushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition");
}
seekImpl(pPosition);
mPosition = pPosition;
}
protected abstract void seekImpl(long pPosition) throws IOException;
public final void mark() {
mMarkedPositions.push(mPosition);
}
@Override
public final void reset() throws IOException {
checkOpen();
if (!mMarkedPositions.isEmpty()) {
long newPos = mMarkedPositions.pop();
// NOTE: This is correct according to javax.imageio (IOException),
// but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
if (newPos < mFlushedPosition) {
throw new IOException("Previous marked position has been discarded");
}
seek(newPos);
}
else {
// TODO: To iron out some wrinkles due to conflicting contracts
// (InputStream and Seekable both declare reset),
// we might need to reset to the last marked position instead..
// However, that becomes REALLY confusing if that position is after
// the current position...
seek(0);
}
}
public final void flushBefore(long pPosition) throws IOException {
if (pPosition < mFlushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition");
}
if (pPosition > getStreamPosition()) {
throw new IndexOutOfBoundsException("position > stream position");
}
checkOpen();
flushBeforeImpl(pPosition);
mFlushedPosition = pPosition;
}
/**
* Discards the initial portion of the stream prior to the indicated postion.
*
* @param pPosition the position to flush to
* @throws IOException if an I/O exception occurs during the flush operation
*
* @see #flushBefore(long)
*/
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
public final void flush() throws IOException {
flushBefore(mFlushedPosition);
}
public final long getFlushedPosition() throws IOException {
checkOpen();
return mFlushedPosition;
}
public final long getStreamPosition() throws IOException {
checkOpen();
return mPosition;
}
protected final void checkOpen() throws IOException {
if (mClosed) {
throw new IOException("closed");
}
}
@Override
public final void close() throws IOException {
checkOpen();
mClosed = true;
closeImpl();
}
protected abstract void closeImpl() throws IOException;
/**
* Finalizes this object prior to garbage collection. The
* {@code close} method is called to close any open input
* source. This method should not be called from application
* code.
*
* @exception Throwable if an error occurs during superclass
* finalization.
*/
@Override
protected void finalize() throws Throwable {
if (!mClosed) {
try {
close();
}
catch (IOException ignore) {
// Ignroe
}
}
super.finalize();
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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 java.io.OutputStream;
import java.io.IOException;
import java.util.Stack;
/**
* Abstract base class for {@code OutputStream}s implementing the
* {@code Seekable} interface.
* <p/>
* @see SeekableInputStream
*
* @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/SeekableOutputStream.java#2 $
*/
public abstract class SeekableOutputStream extends OutputStream implements Seekable {
// TODO: Implement
long mPosition;
long mFlushedPosition;
boolean mClosed;
protected Stack<Long> mMarkedPositions = new Stack<Long>();
/// Outputstream overrides
@Override
public final void write(byte pBytes[]) throws IOException {
write(pBytes, 0, pBytes != null ? pBytes.length : 1);
}
/// Seekable implementation
// TODO: This is common behaviour/implementation with SeekableInputStream,
// probably a good idea to extract a delegate..?
public final void seek(long pPosition) throws IOException {
checkOpen();
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
// but it's inconsistent with reset that throws IOException...
if (pPosition < mFlushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition!");
}
seekImpl(pPosition);
mPosition = pPosition;
}
protected abstract void seekImpl(long pPosition) throws IOException;
public final void mark() {
mMarkedPositions.push(mPosition);
}
public final void reset() throws IOException {
checkOpen();
if (!mMarkedPositions.isEmpty()) {
long newPos = mMarkedPositions.pop();
// TODO: This is correct according to javax.imageio (IOException),
// but it's inconsistent with seek that throws IndexOutOfBoundsException...
if (newPos < mFlushedPosition) {
throw new IOException("Previous marked position has been discarded!");
}
seek(newPos);
}
}
public final void flushBefore(long pPosition) throws IOException {
if (pPosition < mFlushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition!");
}
if (pPosition > getStreamPosition()) {
throw new IndexOutOfBoundsException("position > getStreamPosition()!");
}
checkOpen();
flushBeforeImpl(pPosition);
mFlushedPosition = pPosition;
}
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
@Override
public final void flush() throws IOException {
flushBefore(mFlushedPosition);
}
public final long getFlushedPosition() throws IOException {
checkOpen();
return mFlushedPosition;
}
public final long getStreamPosition() throws IOException {
checkOpen();
return mPosition;
}
protected final void checkOpen() throws IOException {
if (mClosed) {
throw new IOException("closed");
}
}
@Override
public final void close() throws IOException {
checkOpen();
mClosed = true;
closeImpl();
}
protected abstract void closeImpl() throws IOException;
}

View File

@@ -0,0 +1,185 @@
/*
* 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 java.io.StringReader;
import java.io.IOException;
import java.io.Reader;
/**
* StringArrayReader
* <p/>
*
* @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/io/StringArrayReader.java#2 $
*/
public class StringArrayReader extends StringReader {
private StringReader mCurrent;
private String[] mStrings;
protected final Object mLock;
private int mCurrentSting;
private int mMarkedString;
private int mMark;
private int mNext;
/**
* Create a new string array reader.
*
* @param pStrings <tt>String</tt>s providing the character stream.
*/
public StringArrayReader(final String[] pStrings) {
super("");
if (pStrings == null) {
throw new NullPointerException("strings == null");
}
mLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
// reference can't change, only it's elements
mStrings = pStrings.clone(); // Defensive copy for content
nextReader();
}
protected final Reader nextReader() {
if (mCurrentSting >= mStrings.length) {
mCurrent = new EmptyReader();
}
else {
mCurrent = new StringReader(mStrings[mCurrentSting++]);
}
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
mNext = 0;
return mCurrent;
}
/**
* Check to make sure that the stream has not been closed
*
* @throws IOException if the stream is closed
*/
protected final void ensureOpen() throws IOException {
if (mStrings == null) {
throw new IOException("Stream closed");
}
}
public void close() {
super.close();
mStrings = null;
mCurrent.close();
}
public void mark(int pReadLimit) throws IOException {
if (pReadLimit < 0){
throw new IllegalArgumentException("Read limit < 0");
}
synchronized (mLock) {
ensureOpen();
mMark = mNext;
mMarkedString = mCurrentSting;
mCurrent.mark(pReadLimit);
}
}
public void reset() throws IOException {
synchronized (mLock) {
ensureOpen();
if (mCurrentSting != mMarkedString) {
mCurrentSting = mMarkedString - 1;
nextReader();
mCurrent.skip(mMark);
}
else {
mCurrent.reset();
}
mNext = mMark;
}
}
public boolean markSupported() {
return true;
}
public int read() throws IOException {
synchronized (mLock) {
int read = mCurrent.read();
if (read < 0 && mCurrentSting < mStrings.length) {
nextReader();
return read(); // In case of empty strings
}
mNext++;
return read;
}
}
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (mLock) {
int read = mCurrent.read(pBuffer, pOffset, pLength);
if (read < 0 && mCurrentSting < mStrings.length) {
nextReader();
return read(pBuffer, pOffset, pLength); // In case of empty strings
}
mNext += read;
return read;
}
}
public boolean ready() throws IOException {
return mCurrent.ready();
}
public long skip(long pChars) throws IOException {
synchronized (mLock) {
long skipped = mCurrent.skip(pChars);
if (skipped == 0 && mCurrentSting < mStrings.length) {
nextReader();
return skip(pChars);
}
mNext += skipped;
return skipped;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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.Validate;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* An {@code InputStream} reading up to a specified number of bytes from an
* underlying stream.
* <p/>
*
* @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/SubStream.java#2 $
*/
public final class SubStream extends FilterInputStream {
private long mLeft;
private int mMarkLimit;
/**
* Creates a {@code SubStream} of the given {@code pStream}.
*
* @param pStream the underlying input stream
* @param pLength maximum number of bytes to read drom this stream
*/
public SubStream(final InputStream pStream, final long pLength) {
super(Validate.notNull(pStream, "stream"));
mLeft = pLength;
}
/**
* Marks this stream as closed.
* This implementation does <em>not</em> close the underlying stream.
*/
@Override
public void close() throws IOException {
// NOTE: Do not close the underlying stream
while (mLeft > 0) {
//noinspection ResultOfMethodCallIgnored
skip(mLeft);
}
}
@Override
public int available() throws IOException {
return (int) Math.min(super.available(), mLeft);
}
@Override
public void mark(int pReadLimit) {
super.mark(pReadLimit);// This either succeeds or does nothing...
mMarkLimit = pReadLimit;
}
@Override
public void reset() throws IOException {
super.reset();// This either succeeds or throws IOException
mLeft += mMarkLimit;
}
@Override
public int read() throws IOException {
if (mLeft-- <= 0) {
return -1;
}
return super.read();
}
@Override
public final int read(byte[] pBytes) throws IOException {
return read(pBytes, 0, pBytes.length);
}
@Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (mLeft <= 0) {
return -1;
}
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
mLeft = read < 0 ? 0 : mLeft - read;
return read;
}
/**
* Finds the maximum number of bytes we can read or skip, from this stream.
*
* @param pLength the requested length
* @return the maximum number of bytes to read
*/
private long findMaxLen(long pLength) {
if (mLeft < pLength) {
return (int) Math.max(mLeft, 0);
}
else {
return pLength;
}
}
@Override
public long skip(long pLength) throws IOException {
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
mLeft -= skipped;
return skipped;
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.util.StringTokenIterator;
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
/**
* UnixFileSystem
* <p/>
*
* @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/UnixFileSystem.java#1 $
*/
final class UnixFileSystem extends FileSystem {
long getFreeSpace(File pPath) {
try {
return getNumber(pPath, 3);
}
catch (IOException e) {
return 0l;
}
}
long getTotalSpace(File pPath) {
try {
return getNumber(pPath, 5);
}
catch (IOException e) {
return 0l;
}
}
private long getNumber(File pPath, int pIndex) throws IOException {
// TODO: Test on other platforms
// Tested on Mac OS X, CygWin
BufferedReader reader = exec(new String[] {"df", "-k", pPath.getAbsolutePath()});
String last = null;
String line;
try {
while ((line = reader.readLine()) != null) {
last = line;
}
}
finally {
FileUtil.close(reader);
}
if (last != null) {
String blocks = null;
StringTokenIterator tokens = new StringTokenIterator(last, " ", StringTokenIterator.REVERSE);
int count = 0;
// We want the 3rd last token
while (count < pIndex && tokens.hasNext()) {
blocks = tokens.nextToken();
count++;
}
if (blocks != null) {
try {
return Long.parseLong(blocks) * 1024L;
}
catch (NumberFormatException ignore) {
// Ignore
}
}
}
return 0l;
}
String getName() {
return "Unix";
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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 java.io.*;
/**
* Win32File
* <p/>
*
* @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/Win32File.java#2 $
*/
final class Win32File extends File {
private final static boolean IS_WINDOWS = isWindows();
private static boolean isWindows() {
try {
String os = System.getProperty("os.name");
return os.toLowerCase().indexOf("windows") >= 0;
}
catch (Throwable t) {
// Ignore
}
return false;
}
private Win32File(File pPath) {
super(pPath.getPath());
}
public static void main(String[] pArgs) {
int argIdx = 0;
boolean recursive = false;
while (pArgs.length > argIdx + 1 && pArgs[argIdx].charAt(0) == '-' && pArgs[argIdx].length() > 1) {
if (pArgs[argIdx].charAt(1) == 'R' || pArgs[argIdx].equals("--recursive")) {
recursive = true;
}
else {
System.err.println("Unknown option: " + pArgs[argIdx]);
}
argIdx++;
}
File file = wrap(new File(pArgs[argIdx]));
System.out.println("file: " + file);
System.out.println("file.getClass(): " + file.getClass());
listFiles(file, 0, recursive);
}
private static void listFiles(File pFile, int pLevel, boolean pRecursive) {
if (pFile.isDirectory()) {
File[] files = pFile.listFiles();
for (int l = 0; l < pLevel; l++) {
System.out.print(" ");
}
System.out.println("Contents of " + pFile + ": ");
for (File file : files) {
for (int l = 0; l < pLevel; l++) {
System.out.print(" ");
}
System.out.println(" " + file);
if (pRecursive) {
listFiles(file, pLevel + 1, pLevel < 4);
}
}
}
}
/**
* Wraps a <tt>File</tt> object pointing to a Windows symbolic link
* (<tt>.lnk</tt> file) in a <tt>Win32Lnk</tt>.
* If the operating system is not Windows, the
* <tt>pPath</tt> parameter is returned unwrapped.
*
* @param pPath any path, possibly pointing to a Windows symbolic link file.
* May be <tt>null</tt>, in which case <tt>null</tt> is returned.
*
* @return a new <tt>Win32Lnk</tt> object if the current os is Windows, and
* the file is a Windows symbolic link (<tt>.lnk</tt> file), otherwise
* <tt>pPath</tt>
*/
public static File wrap(final File pPath) {
if (pPath == null) {
return null;
}
if (IS_WINDOWS) {
// Don't wrap if allready wrapped
if (pPath instanceof Win32File || pPath instanceof Win32Lnk) {
return pPath;
}
if (pPath.exists() && pPath.getName().endsWith(".lnk")) {
// If Win32 .lnk, let's wrap
try {
return new Win32Lnk(pPath);
}
catch (IOException e) {
// TODO: FixMe!
e.printStackTrace();
}
}
// Wwrap even if not a .lnk, as the listFiles() methods etc,
// could potentially return .lnk's, that we want to wrap later...
return new Win32File(pPath);
}
return pPath;
}
/**
* Wraps a <tt>File</tt> array, possibly pointing to Windows symbolic links
* (<tt>.lnk</tt> files) in <tt>Win32Lnk</tt>s.
*
* @param pPaths an array of <tt>File</tt>s, possibly pointing to Windows
* symbolic link files.
* May be <tt>null</tt>, in which case <tt>null</tt> is returned.
*
* @return <tt>pPaths</tt>, with any <tt>File</tt> representing a Windows
* symbolic link (<tt>.lnk</tt> file) wrapped in a <tt>Win32Lnk</tt>.
*/
public static File[] wrap(File[] pPaths) {
if (IS_WINDOWS) {
for (int i = 0; pPaths != null && i < pPaths.length; i++) {
pPaths[i] = wrap(pPaths[i]);
}
}
return pPaths;
}
// File overrides
@Override
public File getAbsoluteFile() {
return wrap(super.getAbsoluteFile());
}
@Override
public File getCanonicalFile() throws IOException {
return wrap(super.getCanonicalFile());
}
@Override
public File getParentFile() {
return wrap(super.getParentFile());
}
@Override
public File[] listFiles() {
return wrap(super.listFiles());
}
@Override
public File[] listFiles(FileFilter filter) {
return wrap(super.listFiles(filter));
}
@Override
public File[] listFiles(FilenameFilter filter) {
return wrap(super.listFiles(filter));
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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 java.io.File;
import java.io.BufferedReader;
import java.io.IOException;
/**
* WindowsFileSystem
* <p/>
*
* @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/Win32FileSystem.java#2 $
*/
final class Win32FileSystem extends FileSystem {
public long getFreeSpace(File pPath) {
try {
// Windows version
// TODO: Test on W2K/95/98/etc... (tested on XP)
BufferedReader reader = exec(new String[] {"CMD.EXE", "/C", "DIR", "/-C", pPath.getAbsolutePath()});
String last = null;
String line;
try {
while ((line = reader.readLine()) != null) {
last = line;
}
}
finally {
FileUtil.close(reader);
}
if (last != null) {
int end = last.lastIndexOf(" bytes free");
int start = last.lastIndexOf(' ', end - 1);
if (start >= 0 && end >= 0) {
try {
return Long.parseLong(last.substring(start + 1, end));
}
catch (NumberFormatException ignore) {
// Ignore
}
}
}
}
catch (IOException ignore) {
// Ignore
}
return 0l;
}
long getTotalSpace(File pPath) {
// TODO: Implement, probably need some JNI stuff...
// Distribute df.exe and execute from temp!? ;-)
return getFreeSpace(pPath);
}
String getName() {
return "Win32";
}
}

View File

@@ -0,0 +1,472 @@
/*
* 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 java.io.*;
import java.util.Arrays;
/**
* A {@code File} implementation that resolves the Windows {@code .lnk} files as symbolic links.
* <p/>
* This class is based on example code from
* <a href="http://www.oreilly.com/catalog/swinghks/index.html">Swing Hacks</a>,
* By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30.
*
* @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/Win32Lnk.java#2 $
*/
final class Win32Lnk extends File {
private final static byte[] LNK_MAGIC = {
'L', 0x00, 0x00, 0x00, // Magic
};
private final static byte[] LNK_GUID = {
0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Shell Link GUID
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
};
private final File mTarget;
private static final int FLAG_ITEM_ID_LIST = 0x01;
private static final int FLAG_FILE_LOC_INFO = 0x02;
private static final int FLAG_DESC_STRING = 0x04;
private static final int FLAG_REL_PATH_STRING = 0x08;
private static final int FLAG_WORKING_DIRECTORY = 0x10;
private static final int FLAG_COMMAND_LINE_ARGS = 0x20;
private static final int FLAG_ICON_FILENAME = 0x40;
private static final int FLAG_ADDITIONAL_INFO = 0x80;
private Win32Lnk(final String pPath) throws IOException {
super(pPath);
File target = parse(this);
if (target == this) {
// NOTE: This is a workaround
// mTarget = this causes infinite loops in some methods
target = new File(pPath);
}
mTarget = target;
}
Win32Lnk(final File pPath) throws IOException {
this(pPath.getPath());
}
/**
* Parses a {@code .lnk} file to find the real file.
*
* @param pPath the path to the {@code .lnk} file
* @return a new file object that
* @throws java.io.IOException if the {@code .lnk} cannot be parsed
*/
static File parse(final File pPath) throws IOException {
if (!pPath.getName().endsWith(".lnk")) {
return pPath;
}
File result = pPath;
LittleEndianDataInputStream in = new LittleEndianDataInputStream(new BufferedInputStream(new FileInputStream(pPath)));
try {
byte[] magic = new byte[4];
in.readFully(magic);
byte[] guid = new byte[16];
in.readFully(guid);
if (!(Arrays.equals(LNK_MAGIC, magic) && Arrays.equals(LNK_GUID, guid))) {
//System.out.println("Not a symlink");
// Not a symlink
return pPath;
}
// Get the flags
int flags = in.readInt();
//System.out.println("flags: " + Integer.toBinaryString(flags & 0xff));
// Get to the file settings
/*int attributes = */in.readInt();
// File attributes
// 0 Target is read only.
// 1 Target is hidden.
// 2 Target is a system file.
// 3 Target is a volume label. (Not possible)
// 4 Target is a directory.
// 5 Target has been modified since last backup. (archive)
// 6 Target is encrypted (NTFS EFS)
// 7 Target is Normal??
// 8 Target is temporary.
// 9 Target is a sparse file.
// 10 Target has reparse point data.
// 11 Target is compressed.
// 12 Target is offline.
//System.out.println("attributes: " + Integer.toBinaryString(attributes));
// NOTE: Cygwin .lnks are not directory links, can't rely on this.. :-/
in.skipBytes(48); // TODO: Make sense of this data...
// Skipped data:
// long time 1 (creation)
// long time 2 (modification)
// long time 3 (last access)
// int file length
// int icon number
// int ShowVnd value
// int hotkey
// int, int - unknown: 0,0
// If the shell settings are present, skip them
if ((flags & FLAG_ITEM_ID_LIST) != 0) {
// Shell Item Id List present
//System.out.println("Shell Item Id List present");
int shellLen = in.readShort(); // Short
//System.out.println("shellLen: " + shellLen);
// TODO: Probably need to parse this data, to determine
// Cygwin folders...
/*
int read = 2;
int itemLen = in.readShort();
while (itemLen > 0) {
System.out.println("--> ITEM: " + itemLen);
BufferedReader reader = new BufferedReader(new InputStreamReader(new SubStream(in, itemLen - 2)));
//byte[] itemBytes = new byte[itemLen - 2]; // NOTE: Lenght included
//in.readFully(itemBytes);
String item = reader.readLine();
System.out.println("item: \"" + item + "\"");
itemLen = in.readShort();
read += itemLen;
}
System.out.println("read: " + read);
*/
in.skipBytes(shellLen);
}
if ((flags & FLAG_FILE_LOC_INFO) != 0) {
// File Location Info Table present
//System.out.println("File Location Info Table present");
// 0h 1 dword This is the total length of this structure and all following data
// 4h 1 dword This is a pointer to first offset after this structure. 1Ch
// 8h 1 dword Flags
// Ch 1 dword Offset of local volume info
// 10h 1 dword Offset of base pathname on local system
// 14h 1 dword Offset of network volume info
// 18h 1 dword Offset of remaining pathname
// Flags:
// Bit Meaning
// 0 Available on a local volume
// 1 Available on a network share
// TODO: Make sure the path is on a local disk, etc..
int tableLen = in.readInt(); // Int
//System.out.println("tableLen: " + tableLen);
in.readInt(); // Skip
int locFlags = in.readInt();
//System.out.println("locFlags: " + Integer.toBinaryString(locFlags));
if ((locFlags & 0x01) != 0) {
//System.out.println("Available local");
}
if ((locFlags & 0x02) != 0) {
//System.err.println("Available on network path");
}
// Get the local volume and local system values
in.skipBytes(4); // TODO: see above for structure
int localSysOff = in.readInt();
//System.out.println("localSysOff: " + localSysOff);
in.skipBytes(localSysOff - 20); // Relative to start of chunk
byte[] pathBytes = new byte[tableLen - localSysOff - 1];
in.readFully(pathBytes, 0, pathBytes.length);
String path = new String(pathBytes, 0, pathBytes.length - 1);
/*
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
byte read;
// Read bytes until the null (0) character
while (true) {
read = in.readByte();
if (read == 0) {
break;
}
bytes.write(read & 0xff);
}
String path = new String(bytes.toByteArray(), 0, bytes.size());
//*/
// Recurse to end of link chain
// TODO: This may cause endless loop if cyclic chain...
//System.out.println("path: \"" + path + "\"");
try {
result = parse(new File(path));
}
catch (StackOverflowError e) {
throw new IOException("Cannot resolve cyclic link: " + e.getMessage());
}
}
if ((flags & FLAG_DESC_STRING) != 0) {
// Description String present, skip it.
//System.out.println("Description String present");
// The string length is the first word which must also be skipped.
int descLen = in.readShort();
//System.out.println("descLen: " + descLen);
byte[] descBytes = new byte[descLen];
in.readFully(descBytes, 0, descLen);
//String desc = new String(descBytes, 0, descLen);
//System.out.println("desc: " + desc);
}
if ((flags & FLAG_REL_PATH_STRING) != 0) {
// Relative Path String present
//System.out.println("Relative Path String present");
// The string length is the first word which must also be skipped.
int pathLen = in.readShort();
//System.out.println("pathLen: " + pathLen);
byte[] pathBytes = new byte[pathLen];
in.readFully(pathBytes, 0, pathLen);
String path = new String(pathBytes, 0, pathLen);
// TODO: This may cause endless loop if cyclic chain...
//System.out.println("path: \"" + path + "\"");
if (result == pPath) {
try {
result = parse(new File(pPath.getParentFile(), path));
}
catch (StackOverflowError e) {
throw new IOException("Cannot resolve cyclic link: " + e.getMessage());
}
}
}
if ((flags & FLAG_WORKING_DIRECTORY) != 0) {
//System.out.println("Working Directory present");
}
if ((flags & FLAG_COMMAND_LINE_ARGS) != 0) {
//System.out.println("Command Line Arguments present");
// NOTE: This means this .lnk is not a folder, don't follow
result = pPath;
}
if ((flags & FLAG_ICON_FILENAME) != 0) {
//System.out.println("Icon Filename present");
}
if ((flags & FLAG_ADDITIONAL_INFO) != 0) {
//System.out.println("Additional Info present");
}
}
finally {
in.close();
}
return result;
}
/*
private static String getNullDelimitedString(byte[] bytes, int off) {
int len = 0;
// Count bytes until the null (0) character
while (true) {
if (bytes[off + len] == 0) {
break;
}
len++;
}
System.err.println("--> " + len);
return new String(bytes, off, len);
}
*/
/**
* Converts two bytes into a short.
* <p/>
* NOTE: this is little endian because it's for an
* Intel only OS
*
* @ param bytes
* @ param off
* @return the bytes as a short.
*/
/*
private static int bytes2short(byte[] bytes, int off) {
return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
}
*/
public File getTarget() {
return mTarget;
}
// java.io.File overrides below
@Override
public boolean isDirectory() {
return mTarget.isDirectory();
}
@Override
public boolean canRead() {
return mTarget.canRead();
}
@Override
public boolean canWrite() {
return mTarget.canWrite();
}
// NOTE: equals is implemented using compareto == 0
/*
public int compareTo(File pathname) {
// TODO: Verify this
// Probably not a good idea, as it IS NOT THE SAME file
// It's probably better to not override
return mTarget.compareTo(pathname);
}
*/
// Should probably never allow creating a new .lnk
// public boolean createNewFile() throws IOException
// Deletes only the .lnk
// public boolean delete() {
//public void deleteOnExit() {
@Override
public boolean exists() {
return mTarget.exists();
}
// A .lnk may be absolute
//public File getAbsoluteFile() {
//public String getAbsolutePath() {
// Theses should be resolved according to the API (for Unix).
@Override
public File getCanonicalFile() throws IOException {
return mTarget.getCanonicalFile();
}
@Override
public String getCanonicalPath() throws IOException {
return mTarget.getCanonicalPath();
}
//public String getName() {
// I guess the parent should be the parent of the .lnk, not the target
//public String getParent() {
//public File getParentFile() {
// public boolean isAbsolute() {
@Override
public boolean isFile() {
return mTarget.isFile();
}
@Override
public boolean isHidden() {
return mTarget.isHidden();
}
@Override
public long lastModified() {
return mTarget.lastModified();
}
@Override
public long length() {
return mTarget.length();
}
@Override
public String[] list() {
return mTarget.list();
}
@Override
public String[] list(final FilenameFilter filter) {
return mTarget.list(filter);
}
@Override
public File[] listFiles() {
return Win32File.wrap(mTarget.listFiles());
}
@Override
public File[] listFiles(final FileFilter filter) {
return Win32File.wrap(mTarget.listFiles(filter));
}
@Override
public File[] listFiles(final FilenameFilter filter) {
return Win32File.wrap(mTarget.listFiles(filter));
}
// Makes no sense, does it?
//public boolean mkdir() {
//public boolean mkdirs() {
// Only rename the lnk
//public boolean renameTo(File dest) {
@Override
public boolean setLastModified(long time) {
return mTarget.setLastModified(time);
}
@Override
public boolean setReadOnly() {
return mTarget.setReadOnly();
}
@Override
public String toString() {
if (mTarget.equals(this)) {
return super.toString();
}
return super.toString() + " -> " + mTarget.toString();
}
}

View File

@@ -0,0 +1,236 @@
/*
* 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.DateUtil;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
/**
* Wraps a {@code Writer} in an {@code OutputStream}.
* <p/>
* <em>Instances of this class are not thread-safe.</em>
* <p/>
* <em>NOTE: This class is probably not the right way of solving your problem,
* however it might prove useful in JSPs etc.
* If possible, it's always better to use the {@code Writer}'s underlying
* {@code OutputStream}, or wrap it's native backing.
* </em>
* <p/>
*
* @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/WriterOutputStream.java#2 $
*/
public class WriterOutputStream extends OutputStream {
protected Writer mWriter;
final protected Decoder mDecoder;
final ByteArrayOutputStream mBufferStream = new FastByteArrayOutputStream(1024);
private volatile boolean mIsFlushing = false; // Ugly but critical...
private static final boolean NIO_AVAILABLE = isNIOAvailable();
private static boolean isNIOAvailable() {
try {
Class.forName("java.nio.charset.Charset");
return true;
}
catch (Throwable t) {
// Ignore
}
return false;
}
public WriterOutputStream(final Writer pWriter, final String pCharset) {
mWriter = pWriter;
mDecoder = getDecoder(pCharset);
}
public WriterOutputStream(final Writer pWriter) {
this(pWriter, null);
}
private static Decoder getDecoder(final String pCharset) {
// NOTE: The CharsetDecoder is typically 10-20% faster than
// StringDecoder according to my tests
// StringEncoder is horribly slow on 1.2 systems, but there's no
// alternative...
if (NIO_AVAILABLE) {
return new CharsetDecoder(pCharset);
}
return new StringDecoder(pCharset);
}
@Override
public void close() throws IOException {
flush();
mWriter.close();
mWriter = null;
}
@Override
public void flush() throws IOException {
flushBuffer();
mWriter.flush();
}
@Override
public final void write(byte[] pBytes) throws IOException {
if (pBytes == null) {
throw new NullPointerException("bytes == null");
}
write(pBytes, 0, pBytes.length);
}
@Override
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
flushBuffer();
mDecoder.decodeTo(mWriter, pBytes, pOffset, pLength);
}
@Override
public final void write(int pByte) {
// 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..
mBufferStream.write(pByte);
}
private void flushBuffer() throws IOException {
if (!mIsFlushing && mBufferStream.size() > 0) {
mIsFlushing = true;
mBufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
mBufferStream.reset();
mIsFlushing = false;
}
}
///////////////////////////////////////////////////////////////////////////
public static void main(String[] pArgs) throws IOException {
int iterations = 1000000;
byte[] bytes = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
Decoder d;
long start;
long time;
Writer sink = new PrintWriter(new NullOutputStream());
StringWriter writer;
String str;
d = new StringDecoder("UTF-8");
for (int i = 0; i < 10000; i++) {
d.decodeTo(sink, bytes, 0, bytes.length);
}
start = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
d.decodeTo(sink, bytes, 0, bytes.length);
}
time = DateUtil.delta(start);
System.out.println("StringDecoder");
System.out.println("time: " + time);
writer = new StringWriter();
d.decodeTo(writer, bytes, 0, bytes.length);
str = writer.toString();
System.out.println("str: \"" + str + "\"");
System.out.println("chars.length: " + str.length());
System.out.println();
if (NIO_AVAILABLE) {
d = new CharsetDecoder("UTF-8");
for (int i = 0; i < 10000; i++) {
d.decodeTo(sink, bytes, 0, bytes.length);
}
start = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
d.decodeTo(sink, bytes, 0, bytes.length);
}
time = DateUtil.delta(start);
System.out.println("CharsetDecoder");
System.out.println("time: " + time);
writer = new StringWriter();
d.decodeTo(writer, bytes, 0, bytes.length);
str = writer.toString();
System.out.println("str: \"" + str + "\"");
System.out.println("chars.length: " + str.length());
System.out.println();
}
OutputStream os = new WriterOutputStream(new PrintWriter(System.out), "UTF-8");
os.write(bytes);
os.flush();
System.out.println();
for (byte b : bytes) {
os.write(b & 0xff);
}
os.flush();
}
///////////////////////////////////////////////////////////////////////////
private static interface Decoder {
void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException;
}
private static final class CharsetDecoder implements Decoder {
final Charset mCharset;
CharsetDecoder(String pCharset) {
// Handle null-case, to get default charset
String charset = pCharset != null ? pCharset :
System.getProperty("file.encoding", "ISO-8859-1");
mCharset = Charset.forName(charset);
}
public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException {
CharBuffer cb = mCharset.decode(ByteBuffer.wrap(pBytes, pOffset, pLength));
pWriter.write(cb.array(), 0, cb.length());
}
}
private static final class StringDecoder implements Decoder {
final String mCharset;
StringDecoder(String pCharset) {
mCharset = pCharset;
}
public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException {
String str = mCharset == null ?
new String(pBytes, pOffset, pLength) :
new String(pBytes, pOffset, pLength, mCharset);
pWriter.write(str);
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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.enc;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* Abstract base class for RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @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/enc/AbstractRLEDecoder.java#1 $
*/
// TODO: Move to other package or make public
abstract class AbstractRLEDecoder implements Decoder {
protected final byte[] mRow;
protected final int mWidth;
protected int mSrcX;
protected int mSrcY;
protected int mDstX;
protected int mDstY;
/**
* Creates an RLEDecoder. As RLE encoded BMP's may contain x and y deltas,
* etc, we need to know height and width of the image.
*
* @param pWidth width of the image
* @param pHeight heigth of the image
*/
AbstractRLEDecoder(int pWidth, int pHeight) {
mWidth = pWidth;
int bytesPerRow = mWidth;
int mod = bytesPerRow % 4;
if (mod != 0) {
bytesPerRow += 4 - mod;
}
mRow = new byte[bytesPerRow];
mSrcX = 0;
mSrcY = pHeight - 1;
mDstX = mSrcX;
mDstY = mSrcY;
}
/**
* Decodes one full row of image data.
*
* @param pStream the input stream containint RLE data
*
* @throws IOException if an I/O related exception ocurs while reading
*/
protected abstract void decodeRow(InputStream pStream) throws IOException;
/**
* Decodes as much data as possible, from the stream into the buffer.
*
* @param pStream the input stream containing RLE data
* @param pBuffer tge buffer to decode the data to
*
* @return the number of bytes decoded from the stream, to the buffer
*
* @throws IOException if an I/O related exception ocurs while reading
*/
public final int decode(InputStream pStream, byte[] pBuffer) throws IOException {
int decoded = 0;
while (decoded < pBuffer.length && mDstY >= 0) {
// NOTE: Decode only full rows, don't decode if y delta
if (mDstX == 0 && mSrcY == mDstY) {
decodeRow(pStream);
}
int length = Math.min(mRow.length - mDstX, pBuffer.length - decoded);
System.arraycopy(mRow, mDstX, pBuffer, decoded, length);
mDstX += length;
decoded += length;
if (mDstX == mRow.length) {
mDstX = 0;
mDstY--;
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
// gap with zero-bytes
if (mDstY > mSrcY) {
for (int i = 0; i < mRow.length; i++) {
mRow[i] = 0x00;
}
}
}
}
return decoded;
}
/**
* Checks a read byte for EOF marker.
*
* @param pByte the byte to check
* @return the value of {@code pByte} if positive.
*
* @throws EOFException if {@code pByte} is negative
*/
protected static int checkEOF(int pByte) throws EOFException {
if (pByte < 0) {
throw new EOFException("Premature end of file");
}
return pByte;
}
}

View File

@@ -0,0 +1,671 @@
/*
* 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.
*/
/*
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
* All rights reserved.
* <p/>
* 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 of the MiG InfoCom AB nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
* <p/>
* 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.enc;
import java.util.Arrays;
/**
* A very fast and memory efficient class to encode and decode to and from
* BASE64 in full accordance with RFC 2045.
* <p/>
* On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is
* about 10 times faster on small arrays (10 - 1000 bytes) and 2-3 times as fast
* on larger arrays (10000 - 1000000 bytes) compared to
* {@code sun.misc.Encoder()/Decoder()}.
* <p/>
* On byte arrays the encoder is about 20% faster than
* <a href="http://jakarta.apache.org/commons/codec/">Jakarta Commons Base64 Codec</a>
* for encode and about 50% faster for decoding large arrays. This
* implementation is about twice as fast on very small arrays (&lt 30 bytes).
* If source/destination is a {@code String} this version is about three times
* as fast due to the fact that the Commons Codec result has to be recoded
* to a {@code String} from {@code byte[]}, which is very expensive.
* <p/>
* This encode/decode algorithm doesn't create any temporary arrays as many
* other codecs do, it only allocates the resulting array. This produces less
* garbage and it is possible to handle arrays twice as large as algorithms that
* create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
* whether Sun's {@code sun.misc.Encoder()/Decoder()} produce temporary arrays
* but since performance is quite low it probably does.
* <p/>
* The encoder produces the same output as the Sun one except that Sun's encoder
* appends a trailing line separator if the last character isn't a pad.
* Unclear why but it only adds to the length and is probably a side effect.
* Both are in conformance with RFC 2045 though.<br>
* Commons codec seem to always add a trailing line separator.
* <p/>
* <b>Note!</b>
* The encode/decode method pairs (types) come in three versions with the
* <b>exact</b> same algorithm and thus a lot of code redundancy. This is to not
* create any temporary arrays for transcoding to/from different
* format types. The methods not used can simply be commented out.
* <p/>
* There is also a "fast" version of all decode methods that works the same way
* as the normal ones, but har a few demands on the decoded input. Normally
* though, these fast verions should be used if the source if
* the input is known and it hasn't bee tampered with.
* <p/>
* If you find the code useful or you find a bug, please send me a note at
* base64 @ miginfocom . com.
* <p/>
*
* @author Mikael Grev, 2004-aug-02 11:31:11
* @version 2.2
*/
final class Base64 {
private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
private static final int[] IA = new int[256];
static {
Arrays.fill(IA, -1);
for (int i = 0, iS = CA.length; i < iS; i++) {
IA[CA[i]] = i;
}
IA['='] = 0;
}
// ****************************************************************************************
// * char[] version
// ****************************************************************************************
/**
* Encodes a raw byte array into a BASE64 {@code char[]} representation im
* accordance with RFC 2045.
*
* @param sArr The bytes to convert. If {@code null} or length 0 an
* empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.
* <br>
* No line separator will be in breach of RFC 2045 which
* specifies max 76 per line but will be a little faster.
* @return A BASE64 encoded array. Never {@code null}.
*/
public static char[] encodeToChar(byte[] sArr, boolean lineSep) {
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0) {
return new char[0];
}
int eLen = (sLen / 3) * 3;// Length of even 24-bits.
int cCnt = ((sLen - 1) / 3 + 1) << 2;// Returned character count
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0);// Length of returned array
char[] dArr = new char[dLen];
// Encode even 24-bits
for (int s = 0, d = 0, cc = 0; s < eLen;) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = CA[(i >>> 18) & 0x3f];
dArr[d++] = CA[(i >>> 12) & 0x3f];
dArr[d++] = CA[(i >>> 6) & 0x3f];
dArr[d++] = CA[i & 0x3f];
// Add optional line separator
if (lineSep && ++cc == 19 && d < dLen - 2) {
dArr[d++] = '\r';
dArr[d++] = '\n';
cc = 0;
}
}
// Pad and encode last bits if source isn't even 24 bits.
int left = sLen - eLen;// 0 - 2.
if (left > 0) {
// Prepare the int
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[dLen - 4] = CA[i >> 12];
dArr[dLen - 3] = CA[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
dArr[dLen - 1] = '=';
}
return dArr;
}
/**
* Decodes a BASE64 encoded char array. All illegal characters will be
* ignored and can handle both arrays with and without line separators.
*
* @param sArr The source array. {@code null} or length 0 will return
* an empty array.
* @return The decoded array of bytes. May be of length 0. Will be
* {@code null} if the legal characters (including '=') isn't
* divideable by 4. (I.e. definitely corrupted).
*/
public static byte[] decode(char[] sArr) {
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0) {
return new byte[0];
}
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0;// Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++)// If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
{
if (IA[sArr[i]] < 0) {
sepCnt++;
}
}
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0) {
return null;
}
int pad = 0;
for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) {
if (sArr[i] == '=') {
pad++;
}
}
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len;) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++)
{// j only increased if a valid char was found.
int c = IA[sArr[s++]];
if (c >= 0) {
i |= c << (18 - j * 6);
}
else {
j--;
}
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
/**
* Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(char[])}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param sArr The source array. Length 0 will return an empty array. {@code null} will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
public static byte[] decodeFast(char[] sArr) {
// Check special case
int sLen = sArr.length;
if (sLen == 0) {
return new byte[0];
}
int sIx = 0, eIx = sLen - 1;// Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[sArr[sIx]] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IA[sArr[eIx]] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0;// Count '=' at end.
int cCnt = eIx - sIx + 1;// Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad;// The number of decoded bytes
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= IA[sArr[sIx++]] << (18 - j * 6);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
// ****************************************************************************************
// * byte[] version
// ****************************************************************************************
/**
* Encodes a raw byte array into a BASE64 {@code byte[]} representation i accordance with RFC 2045.
*
* @param sArr The bytes to convert. If {@code null} or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never {@code null}.
*/
public static byte[] encodeToByte(byte[] sArr, boolean lineSep) {
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0) {
return new byte[0];
}
int eLen = (sLen / 3) * 3;// Length of even 24-bits.
int cCnt = ((sLen - 1) / 3 + 1) << 2;// Returned character count
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0);// Length of returned array
byte[] dArr = new byte[dLen];
// Encode even 24-bits
for (int s = 0, d = 0, cc = 0; s < eLen;) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = (byte) CA[(i >>> 18) & 0x3f];
dArr[d++] = (byte) CA[(i >>> 12) & 0x3f];
dArr[d++] = (byte) CA[(i >>> 6) & 0x3f];
dArr[d++] = (byte) CA[i & 0x3f];
// Add optional line separator
if (lineSep && ++cc == 19 && d < dLen - 2) {
dArr[d++] = '\r';
dArr[d++] = '\n';
cc = 0;
}
}
// Pad and encode last bits if source isn't an even 24 bits.
int left = sLen - eLen;// 0 - 2.
if (left > 0) {
// Prepare the int
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[dLen - 4] = (byte) CA[i >> 12];
dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '=';
dArr[dLen - 1] = '=';
}
return dArr;
}
/**
* Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
* and without line separators.
*
* @param sArr The source array. Length 0 will return an empty array. {@code null} will throw an exception.
* @return The decoded array of bytes. May be of length 0. Will be {@code null} if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*/
public static byte[] decode(byte[] sArr) {
// Check special case
int sLen = sArr.length;
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0;// Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++)// If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
{
if (IA[sArr[i] & 0xff] < 0) {
sepCnt++;
}
}
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0) {
return null;
}
int pad = 0;
for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;) {
if (sArr[i] == '=') {
pad++;
}
}
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len;) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++)
{// j only increased if a valid char was found.
int c = IA[sArr[s++] & 0xff];
if (c >= 0) {
i |= c << (18 - j * 6);
}
else {
j--;
}
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
/**
* Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(byte[])}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param sArr The source array. Length 0 will return an empty array. {@code null} will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
public static byte[] decodeFast(byte[] sArr) {
// Check special case
int sLen = sArr.length;
if (sLen == 0) {
return new byte[0];
}
int sIx = 0, eIx = sLen - 1;// Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0;// Count '=' at end.
int cCnt = eIx - sIx + 1;// Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad;// The number of decoded bytes
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= IA[sArr[sIx++]] << (18 - j * 6);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
// ****************************************************************************************
// * String version
// ****************************************************************************************
/**
* Encodes a raw byte array into a BASE64 {@code String} representation i accordance with RFC 2045.
*
* @param sArr The bytes to convert. If {@code null} or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never {@code null}.
*/
public static String encodeToString(byte[] sArr, boolean lineSep) {
// Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower.
return new String(encodeToChar(sArr, lineSep));
}
/**
* Decodes a BASE64 encoded {@code String}. All illegal characters will be ignored and can handle both strings with
* and without line separators.<br>
* <b>Note!</b> It can be up to about 2x the speed to call {@code decode(str.toCharArray())} instead. That
* will create a temporary array though. This version will use {@code str.charAt(i)} to iterate the string.
*
* @param str The source string. {@code null} or length 0 will return an empty array.
* @return The decoded array of bytes. May be of length 0. Will be {@code null} if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*/
public static byte[] decode(String str) {
// Check special case
int sLen = str != null ? str.length() : 0;
if (sLen == 0) {
return new byte[0];
}
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0;// Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++)// If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
{
if (IA[str.charAt(i)] < 0) {
sepCnt++;
}
}
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0) {
return null;
}
// Count '=' at end
int pad = 0;
for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) {
if (str.charAt(i) == '=') {
pad++;
}
}
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len;) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++)
{// j only increased if a valid char was found.
int c = IA[str.charAt(s++)];
if (c >= 0) {
i |= c << (18 - j * 6);
}
else {
j--;
}
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
/**
* Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(String)}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param s The source string. Length 0 will return an empty array. {@code null} will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
public static byte[] decodeFast(String s) {
// Check special case
int sLen = s.length();
if (sLen == 0) {
return new byte[0];
}
int sIx = 0, eIx = sLen - 1;// Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0;// Count '=' at end.
int cCnt = eIx - sIx + 1;// Content count including possible separators
int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad;// The number of decoded bytes
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= IA[s.charAt(sIx++)] << (18 - j * 6);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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.enc;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import java.io.*;
/**
* {@code Decoder} implementation for standard base64 encoding.
* <p/>
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421</a>
* @see <a href="http://tools.ietf.org/html/rfc2045"RFC 2045</a>
*
* @see Base64Encoder
*
* @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/enc/Base64Decoder.java#2 $
*/
public class Base64Decoder implements Decoder {
/**
* This array maps the characters to their 6 bit values
*/
final static char[] PEM_ARRAY = {
//0 1 2 3 4 5 6 7
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5
'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6
'4', '5', '6', '7', '8', '9', '+', '/' // 7
};
final static byte[] PEM_CONVERT_ARRAY;
private byte[] decode_buffer = new byte[4];
private ByteArrayOutputStream mWrapped;
private Object mWrappedObject;
static {
PEM_CONVERT_ARRAY = new byte[256];
for (int i = 0; i < 255; i++) {
PEM_CONVERT_ARRAY[i] = -1;
}
for (int i = 0; i < PEM_ARRAY.length; i++) {
PEM_CONVERT_ARRAY[PEM_ARRAY[i]] = (byte) i;
}
}
protected static int readFully(InputStream pStream, byte pBytes[],
int pOffset, int pLength) throws IOException {
for (int i = 0; i < pLength; i++) {
int read = pStream.read();
if (read == -1) {
return i != 0 ? i : -1;
}
pBytes[i + pOffset] = (byte) read;
}
return pLength;
}
protected boolean decodeAtom(InputStream pInput, OutputStream pOutput, int pLength)
throws IOException {
byte byte0 = -1;
byte byte1 = -1;
byte byte2 = -1;
byte byte3 = -1;
if (pLength < 2) {
throw new IOException("BASE64Decoder: Not enough bytes for an atom.");
}
int read;
// Skip linefeeds
do {
read = pInput.read();
if (read == -1) {
return false;
}
} while (read == 10 || read == 13);
decode_buffer[0] = (byte) read;
read = readFully(pInput, decode_buffer, 1, pLength - 1);
if (read == -1) {
return false;
}
if (pLength > 3 && decode_buffer[3] == 61) {
pLength = 3;
}
if (pLength > 2 && decode_buffer[2] == 61) {
pLength = 2;
}
switch (pLength) {
case 4:
byte3 = PEM_CONVERT_ARRAY[decode_buffer[3] & 255];
// fall through
case 3:
byte2 = PEM_CONVERT_ARRAY[decode_buffer[2] & 255];
// fall through
case 2:
byte1 = PEM_CONVERT_ARRAY[decode_buffer[1] & 255];
byte0 = PEM_CONVERT_ARRAY[decode_buffer[0] & 255];
// fall through
default:
switch (pLength) {
case 2:
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
break;
case 3:
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
break;
case 4:
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
pOutput.write((byte) (byte2 << 6 & 192 | byte3 & 63));
break;
}
break;
}
return true;
}
void decodeBuffer(InputStream pInput, ByteArrayOutputStream pOutput, int pLength) throws IOException {
do {
int k = 72;
int i;
for (i = 0; i + 4 < k; i += 4) {
if(!decodeAtom(pInput, pOutput, 4)) {
break;
}
}
if (!decodeAtom(pInput, pOutput, k - i)) {
break;
}
}
while (true);
}
public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
if (mWrappedObject != pBuffer) {
// NOTE: Array not cloned in FastByteArrayOutputStream
mWrapped = new FastByteArrayOutputStream(pBuffer);
mWrappedObject = pBuffer;
}
mWrapped.reset(); // NOTE: This only resets count to 0
decodeBuffer(pStream, mWrapped, pBuffer.length);
return mWrapped.size();
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.enc;
import java.io.OutputStream;
import java.io.IOException;
/**
* {@code Encoder} implementation for standard base64 encoding.
* <p/>
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421</a>
* @see <a href="http://tools.ietf.org/html/rfc2045"RFC 2045</a>
*
* @see Base64Decoder
*
* @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/enc/Base64Encoder.java#2 $
*/
public class Base64Encoder implements Encoder {
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
// TODO: Implement
// NOTE: This is impossible, given the current spec, as we need to either:
// - buffer all data in the EncoderStream
// - or have flush/end method(s) in the Encoder
// to ensure proper end of stream handling
int offset = pOffset;
// TODO: Temp impl, will only work for single writes
while ((pBuffer.length - offset) > 0) {
byte a, b, c;
if ((pBuffer.length - offset) > 2) {
pLength = 3;
}
else {
pLength = pBuffer.length - offset;
}
switch (pLength) {
case 1:
a = pBuffer[offset];
b = 0;
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
pStream.write('=');
pStream.write('=');
offset++;
break;
case 2:
a = pBuffer[offset];
b = pBuffer[offset + 1];
c = 0;
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
pStream.write('=');
offset += offset + 2; // ???
break;
default:
a = pBuffer[offset];
b = pBuffer[offset + 1];
c = pBuffer[offset + 2];
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
pStream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
offset = offset + 3;
break;
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.enc;
import java.io.IOException;
/**
* Thrown by {@code Decoder}s when encoded data is not decodable.
* <p/>
*
* @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/enc/DecodeException.java#2 $
*/
public class DecodeException extends IOException {
public DecodeException(String pMessage) {
super(pMessage);
}
public DecodeException(String pMessage, Throwable pCause) {
super(pMessage);
initCause(pCause);
}
public DecodeException(Throwable pCause) {
this(pCause.getMessage(), pCause);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.enc;
import java.io.InputStream;
import java.io.IOException;
/**
* Interface for decoders.
* A {@code Decoder} may be used with a {@code DecoderStream}, to perform
* on-the-fly decoding from an {@code InputStream}.
* <p/>
* Important note: Decoder implementations are typically not synchronized.
* <p/>
* @see Encoder
* @see DecoderStream
*
* @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/enc/Decoder.java#2 $
*/
public interface Decoder {
/**
* Decodes up to {@code pBuffer.length} bytes from the given inputstream,
* into the given buffer.
*
* @param pStream the inputstream to decode data from
* @param pBuffer buffer to store the read data
*
* @return the total number of bytes read into the buffer, or {@code -1}
* if there is no more data because the end of the stream has been reached.
*
* @throws DecodeException if encoded data is corrupt
* @throws IOException if an I/O error occurs
* @throws java.io.EOFException if a premature end-of-file is encountered
*/
int decode(InputStream pStream, byte[] pBuffer) throws IOException;
}

View File

@@ -0,0 +1,189 @@
/*
* 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.enc;
import java.io.InputStream;
import java.io.IOException;
import java.io.FilterInputStream;
/**
* An {@code InputStream} that provides on-the-fly deoding from an underlying
* stream.
* <p/>
* @see EncoderStream
* @see Decoder
*
* @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/enc/DecoderStream.java#2 $
*/
public final class DecoderStream extends FilterInputStream {
protected int mBufferPos;
protected int mBufferLimit;
protected final byte[] mBuffer;
protected final Decoder mDecoder;
/**
* Creates a new decoder stream and chains it to the
* input stream specified by the {@code pStream} argument.
*
* @param pStream the underlying input stream.
* @param pDecoder
*
* @see java.io.FilterInputStream#in
*/
public DecoderStream(InputStream pStream, Decoder pDecoder) {
super(pStream);
mDecoder = pDecoder;
mBuffer = new byte[1024];
mBufferPos = 0;
mBufferLimit = 0;
}
public int available() throws IOException {
return mBufferLimit - mBufferPos + super.available();
}
public int read() throws IOException {
if (mBufferPos == mBufferLimit) {
mBufferLimit = fill();
}
if (mBufferLimit < 0) {
return -1;
}
return mBuffer[mBufferPos++] & 0xff;
}
public int read(byte pBytes[]) throws IOException {
return read(pBytes, 0, pBytes.length);
}
public int read(byte pBytes[], int pOffset, int pLength) throws IOException {
if (pBytes == null) {
throw new NullPointerException();
}
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " lenght=" + pLength);
}
else if (pLength == 0) {
return 0;
}
// End of file?
if ((mBufferLimit - mBufferPos) < 0) {
return -1;
}
// Read until we have read pLength bytes, or have reached EOF
int count = 0;
int off = pOffset;
while (pLength > count) {
int avail = mBufferLimit - mBufferPos;
if (avail <= 0) {
mBufferLimit = fill();
if (mBufferLimit < 0) {
break;
}
}
// Copy as many bytes as possible
int dstLen = Math.min(pLength - count, avail);
System.arraycopy(mBuffer, mBufferPos, pBytes, off, dstLen);
mBufferPos += dstLen;
// Update offset (rest)
off += dstLen;
// Inrease count
count += dstLen;
}
/*
for (int i = 0; i < count; i++) {
byte b = pBytes[pOffset + i];
System.out.print("0x" + Integer.toHexString(b & 0xff));
}
*/
return count;
}
public long skip(long pLength) throws IOException {
// End of file?
if (mBufferLimit - mBufferPos < 0) {
return 0;
}
// Skip until we have skipped pLength bytes, or have reached EOF
long total = 0;
while (total < pLength) {
int avail = mBufferLimit - mBufferPos;
if (avail == 0) {
mBufferLimit = fill();
if (mBufferLimit < 0) {
break;
}
}
// NOTE: Skipped can never be more than avail, which is
// an int, so the cast is safe
int skipped = (int) Math.min(pLength - total, avail);
mBufferPos += skipped; // Just skip these bytes
total += skipped;
}
return total;
}
/**
* Fills the buffer, by decoding data from the underlying input stream.
*
* @return the number of bytes decoded, or {@code -1} if the end of the
* file is reached
*
* @throws IOException if an IO error occurs
*/
protected int fill() throws IOException {
int read = mDecoder.decode(in, mBuffer);
mBufferPos = 0;
if (read == 0) {
return -1;
}
return read;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.enc;
import java.io.OutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
/**
* {@code Encoder} implementation for standard DEFLATE encoding.
* <p/>
*
* @see <a href="http://tools.ietf.org/html/rfc1951">RFC 1951</a>
*
* @see Deflater
* @see InflateDecoder
* @see java.util.zip.DeflaterOutputStream
*
* @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/enc/DeflateEncoder.java#2 $
*/
public final class DeflateEncoder implements Encoder {
private final Deflater mDeflater;
public DeflateEncoder() {
this(new Deflater());
}
public DeflateEncoder(Deflater pDeflater) {
if (pDeflater == null) {
throw new IllegalArgumentException("deflater == null");
}
mDeflater = pDeflater;
}
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
throw new InternalError("not implemented: encode()"); // TODO: Implement
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.enc;
import java.io.IOException;
import java.io.OutputStream;
/**
* Interface for endcoders.
* An {@code Encoder} may be used with an {@code EncoderStream}, to perform
* on-the-fly enoding to an {@code OutputStream}.
* <p/>
* Important note: Encoder implementations are typically not synchronized.
*
* @see Decoder
* @see EncoderStream
*
* @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/enc/Encoder.java#2 $
*/
public interface Encoder {
/**
* Encodes up to {@code pBuffer.length} bytes into the given inputstream,
* from the given buffer.
*
* @param pStream the outputstream to encode data to
* @param pBuffer buffer to read data from
* @param pOffset offset into the buffer array
* @param pLength length of data in the buffer
*
* @throws java.io.IOException if an I/O error occurs
*/
void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength)
throws IOException;
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
// void flush()?
}

View File

@@ -0,0 +1,133 @@
/*
* 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.enc;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.IOException;
/**
* An {@code OutputStream} that provides on-the-fly encoding to an underlying
* stream.
* <p/>
* @see DecoderStream
* @see Encoder
*
* @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/enc/EncoderStream.java#2 $
*/
public final class EncoderStream extends FilterOutputStream {
protected final Encoder mEncoder;
private final boolean mFlushOnWrite;
protected int mBufferPos;
protected final byte[] mBuffer;
/**
* Creates an output stream filter built on top of the specified
* underlying output stream.
*
* @param pStream the underlying output stream
* @param pEncoder
*/
public EncoderStream(OutputStream pStream, Encoder pEncoder) {
this(pStream, pEncoder, false);
}
/**
* Creates an output stream filter built on top of the specified
* underlying output stream.
*
* @param pStream the underlying output stream
* @param pEncoder
* @param pFlushOnWrite if {@code true}, calls to the byte-array
* {@code write} methods will automatically flush the buffer.
*/
public EncoderStream(OutputStream pStream, Encoder pEncoder, boolean pFlushOnWrite) {
super(pStream);
mEncoder = pEncoder;
mFlushOnWrite = pFlushOnWrite;
mBuffer = new byte[1024];
mBufferPos = 0;
}
public void close() throws IOException {
flush();
super.close();
}
public void flush() throws IOException {
encodeBuffer();
super.flush();
}
private void encodeBuffer() throws IOException {
if (mBufferPos != 0) {
// Make sure all remaining data in buffer is written to the stream
mEncoder.encode(out, mBuffer, 0, mBufferPos);
// Reset buffer
mBufferPos = 0;
}
}
public final void write(byte[] pBytes) throws IOException {
write(pBytes, 0, pBytes.length);
}
// TODO: Verify that this works for the general case (it probably won't)...
// TODO: We might need a way to explicitly flush the encoder, or specify
// that the encoder can't buffer. In that case, the encoder should probably
// tell the EncoderStream how large buffer it prefers...
public void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
if (!mFlushOnWrite && mBufferPos + pLength < mBuffer.length) {
// Buffer data
System.arraycopy(pBytes, pOffset, mBuffer, mBufferPos, pLength);
mBufferPos += pLength;
}
else {
// Encode data allready in the buffer
if (mBufferPos != 0) {
encodeBuffer();
}
// Encode rest without buffering
mEncoder.encode(out, pBytes, pOffset, pLength);
}
}
public void write(int pByte) throws IOException {
if (mBufferPos >= mBuffer.length - 1) {
encodeBuffer(); // Resets mBufferPos to 0
}
mBuffer[mBufferPos++] = (byte) pByte;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.enc;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/**
* {@code Decoder} implementation for standard DEFLATE encoding.
* <p/>
*
* @see <a href="http://tools.ietf.org/html/rfc1951">RFC 1951</a>
*
* @see Inflater
* @see DeflateEncoder
* @see java.util.zip.InflaterInputStream
*
* @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/enc/InflateDecoder.java#2 $
*/
public final class InflateDecoder implements Decoder {
private final Inflater mInflater;
private final byte[] mBuffer;
/**
* Creates an {@code InflateDecoder}
*
*/
public InflateDecoder() {
this(new Inflater(true));
}
/**
* Creates an {@code InflateDecoder}
*
* @param pInflater the inflater instance to use
*/
public InflateDecoder(Inflater pInflater) {
if (pInflater == null) {
throw new IllegalArgumentException("inflater == null");
}
mInflater = pInflater;
mBuffer = new byte[1024];
}
public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
try {
int decoded;
while ((decoded = mInflater.inflate(pBuffer, 0, pBuffer.length)) == 0) {
if (mInflater.finished() || mInflater.needsDictionary()) {
return 0;
}
if (mInflater.needsInput()) {
fill(pStream);
}
}
return decoded;
}
catch (DataFormatException e) {
String message = e.getMessage();
throw new DecodeException(message != null ? message : "Invalid ZLIB data format", e);
}
}
private void fill(InputStream pStream) throws IOException {
int available = pStream.read(mBuffer, 0, mBuffer.length);
if (available == -1) {
throw new EOFException("Unexpected end of ZLIB stream");
}
mInflater.setInput(mBuffer, 0, available);
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.enc;
import java.io.InputStream;
import java.io.IOException;
/**
* LZWDecoder.
* <p/>
*
* @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/enc/LZWDecoder.java#2 $
*/
public class LZWDecoder implements Decoder {
public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
return 0; // TODO: Implement
// TODO: We probably need a GIF specific subclass
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.enc;
import java.io.OutputStream;
import java.io.IOException;
/**
* LZWEncoder.
* <p/>
*
* @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/enc/LZWEncoder.java#2 $
*/
public class LZWEncoder implements Encoder {
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
// TODO: Implement
// TODO: We probably need a GIF specific subclass
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.enc;
import java.io.InputStream;
import java.io.IOException;
import java.io.EOFException;
/**
* Decoder implementation for 16 bit-chunked Apple PackBits-like run-length
* encoding.
* <p/>
* This version of the decoder decodes chunk of 16 bit, instead of 8 bit.
* This format is used in certain PICT files.
*
* @see PackBitsDecoder
*
* @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/enc/PackBits16Decoder.java#2 $
*/
public final class PackBits16Decoder implements Decoder {
// TODO: Refactor this into an option for the PackBitsDecoder?
private final boolean mDisableNoop;
private int mLeftOfRun;
private boolean mSplitRun;
private boolean mEOF;
/**
* Creates a {@code PackBitsDecoder}.
*/
public PackBits16Decoder() {
this(false);
}
/**
* Creates a {@code PackBitsDecoder}.
* <p/>
* As some implementations of PackBits-like encoders treat -128 as lenght of
* a compressed run, instead of a no-op, it's possible to disable no-ops
* for compatibility.
* Should be used with caution, even though, most known encoders never write
* no-ops in the compressed streams.
*
* @param pDisableNoop
*/
public PackBits16Decoder(boolean pDisableNoop) {
mDisableNoop = pDisableNoop;
}
/**
* Decodes bytes from the given input stream, to the given buffer.
*
* @param pStream
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
* bytes long
* @return The number of bytes decoded
*
* @throws java.io.IOException
*/
public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
if (mEOF) {
throw new EOFException("Unexpected end of PackBits stream");
}
int read = 0;
final int max = pBuffer.length;
while (read < max) {
int n;
if (mSplitRun) {
// Continue run
n = mLeftOfRun;
mSplitRun = false;
}
else {
// Start new run
int b = pStream.read();
if (b < 0) {
mEOF = true;
break;
}
n = (byte) b;
}
// Split run at or before max
if (n >= 0 && 2 * (n + 1) + read > max) {
mLeftOfRun = n;
mSplitRun = true;
break;
}
else if (n < 0 && 2 * (-n + 1) + read > max) {
mLeftOfRun = n;
mSplitRun = true;
break;
}
try {
if (n >= 0) {
// Copy next n + 1 shorts literally
int len = 2 * (n + 1);
readFully(pStream, pBuffer, read, len);
read += len;
}
// Allow -128 for compatibility, see above
else if (mDisableNoop || n != -128) {
// Replicate the next short -n + 1 times
byte value1 = readByte(pStream);
byte value2 = readByte(pStream);
for (int i = -n + 1; i > 0; i--) {
pBuffer[read++] = value1;
pBuffer[read++] = value2;
}
}
// else NOOP (-128)
}
catch (IndexOutOfBoundsException e) {
throw new DecodeException("Error in PackBits decompression, data seems corrupt", e);
}
}
return read;
}
private static byte readByte(InputStream pStream) throws IOException {
int read = pStream.read();
if (read < 0) {
throw new EOFException("Unexpected end of PackBits stream");
}
return (byte) read;
}
private static void readFully(InputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
if (pLength < 0) {
throw new IndexOutOfBoundsException();
}
int read = 0;
while (read < pLength) {
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
if (count < 0) {
throw new EOFException("Unexpected end of PackBits stream");
}
read += count;
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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.enc;
import java.io.IOException;
import java.io.InputStream;
import java.io.EOFException;
/**
* Decoder implementation for Apple PackBits run-length encoding.
* <p/>
* <small>From Wikipedia, the free encyclopedia</small><br/>
* PackBits is a fast, simple compression scheme for run-length encoding of
* data.
* <p/>
* Apple introduced the PackBits format with the release of MacPaint on the
* Macintosh computer. This compression scheme is one of the types of
* compression that can be used in TIFF-files.
* <p/>
* A PackBits data stream consists of packets of one byte of header followed by
* data. The header is a signed byte; the data can be signed, unsigned, or
* packed (such as MacPaint pixels).
* <p/>
* <table><tr><th>Header byte</th><th>Data</th></tr>
* <tr><td>0 to 127</td> <td>1 + <i>n</i> literal bytes of data</td></tr>
* <tr><td>0 to -127</td> <td>One byte of data, repeated 1 - <i>n</i> times in
* the decompressed output</td></tr>
* <tr><td>-128</td> <td>No operation</td></tr></table>
* <p/>
* Note that interpreting 0 as positive or negative makes no difference in the
* output. Runs of two bytes adjacent to non-runs are typically written as
* literal data.
* <p/>
* See <a href="http://developer.apple.com/technotes/tn/tn1023.html">Understanding PackBits</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/enc/PackBitsDecoder.java#1 $
*/
public final class PackBitsDecoder implements Decoder {
private final boolean mDisableNoop;
private int mLeftOfRun;
private boolean mSplitRun;
private boolean mEOF;
/**
* Creates a {@code PackBitsDecoder}.
*/
public PackBitsDecoder() {
this(false);
}
/**
* Creates a {@code PackBitsDecoder}.
* <p/>
* As some implementations of PackBits-like encoders treat -128 as lenght of
* a compressed run, instead of a no-op, it's possible to disable no-ops
* for compatibility.
* Should be used with caution, even though, most known encoders never write
* no-ops in the compressed streams.
*
* @param pDisableNoop
*/
public PackBitsDecoder(boolean pDisableNoop) {
mDisableNoop = pDisableNoop;
}
/**
* Decodes bytes from the given input stream, to the given buffer.
*
* @param pStream
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
* bytes long
* @return The number of bytes decoded
*
* @throws IOException
*/
public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
if (mEOF) {
throw new EOFException("Unexpected end of PackBits stream");
}
int read = 0;
final int max = pBuffer.length;
while (read < max) {
int n;
if (mSplitRun) {
// Continue run
n = mLeftOfRun;
mSplitRun = false;
}
else {
// Start new run
int b = pStream.read();
if (b < 0) {
mEOF = true;
break;
}
n = (byte) b;
}
// Split run at or before max
if (n >= 0 && n + 1 + read > max) {
mLeftOfRun = n;
mSplitRun = true;
break;
}
else if (n < 0 && -n + 1 + read > max) {
mLeftOfRun = n;
mSplitRun = true;
break;
}
try {
if (n >= 0) {
// Copy next n + 1 bytes literally
readFully(pStream, pBuffer, read, n + 1);
read += n + 1;
}
// Allow -128 for compatibility, see above
else if (mDisableNoop || n != -128) {
// Replicate the next byte -n + 1 times
byte value = readByte(pStream);
for (int i = -n + 1; i > 0; i--) {
pBuffer[read++] = value;
}
}
// else NOOP (-128)
}
catch (IndexOutOfBoundsException e) {
throw new DecodeException("Error in PackBits decompression, data seems corrupt", e);
}
}
return read;
}
private static byte readByte(InputStream pStream) throws IOException {
int read = pStream.read();
if (read < 0) {
throw new EOFException("Unexpected end of PackBits stream");
}
return (byte) read;
}
private static void readFully(InputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
if (pLength < 0) {
throw new IndexOutOfBoundsException();
}
int read = 0;
while (read < pLength) {
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
if (count < 0) {
throw new EOFException("Unexpected end of PackBits stream");
}
read += count;
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.enc;
import java.io.OutputStream;
import java.io.IOException;
/**
* Encoder implementation for Apple PackBits run-length encoding.
* <p/>
* From Wikipedia, the free encyclopedia<br/>
* PackBits is a fast, simple compression scheme for run-length encoding of
* data.
* <p/>
* Apple introduced the PackBits format with the release of MacPaint on the
* Macintosh computer. This compression scheme is one of the types of
* compression that can be used in TIFF-files.
* <p/>
* A PackBits data stream consists of packets of one byte of header followed by
* data. The header is a signed byte; the data can be signed, unsigned, or
* packed (such as MacPaint pixels).
* <p/>
* <table><tr><th>Header byte</th><th>Data</th></tr>
* <tr><td>0 to 127</td> <td>1 + <i>n</i> literal bytes of data</td></tr>
* <tr><td>0 to -127</td> <td>One byte of data, repeated 1 - <i>n</i> times in
* the decompressed output</td></tr>
* <tr><td>-128</td> <td>No operation</td></tr></table>
* <p/>
* Note that interpreting 0 as positive or negative makes no difference in the
* output. Runs of two bytes adjacent to non-runs are typically written as
* literal data.
* <p/>
* See <a href="http://developer.apple.com/technotes/tn/tn1023.html">Understanding PackBits</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/enc/PackBitsEncoder.java#1 $
*/
public final class PackBitsEncoder implements Encoder {
final private byte[] mBuffer = new byte[128];
/**
* Creates a {@code PackBitsEncoder}.
*/
public PackBitsEncoder() {
}
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
// NOTE: It's best to encode a 2 byte repeat
// run as a replicate run except when preceded and followed by a
// literal run, in which case it's best to merge the three into one
// literal run. Always encode 3 byte repeats as replicate runs.
// NOTE: Worst case: output = input + (input + 127) / 128
int offset = pOffset;
final int max = pOffset + pLength - 1;
final int maxMinus1 = max - 1;
while (offset <= max) {
// Compressed run
int run = 1;
byte replicate = pBuffer[offset];
while(run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) {
offset++;
run++;
}
if (run > 1) {
offset++;
pStream.write(-(run - 1));
pStream.write(replicate);
}
// Literal run
run = 0;
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|| (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) {
mBuffer[run++] = pBuffer[offset++];
}
// If last byte, include it in literal run, if space
if (offset == max && run > 0 && run < 128) {
mBuffer[run++] = pBuffer[offset++];
}
if (run > 0) {
pStream.write(run - 1);
pStream.write(mBuffer, 0, run);
}
// If last byte, and not space, start new literal run
if (offset == max && (run <= 0 || run >= 128)) {
pStream.write(0);
pStream.write(pBuffer[offset++]);
}
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.enc;
import java.io.InputStream;
import java.io.IOException;
/**
* Implements 4 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @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/enc/RLE4Decoder.java#1 $
*/
// TODO: Move to other package or make public
final class RLE4Decoder extends AbstractRLEDecoder {
public RLE4Decoder(int pWidth, int pHeight) {
super((pWidth + 1) / 2, pHeight);
}
protected void decodeRow(final InputStream pInput) throws IOException {
int deltaX = 0;
int deltaY = 0;
while (mSrcY >= 0) {
int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read());
if (byte1 == 0x00) {
switch (byte2) {
case 0x00:
// End of line
// NOTE: Some BMPs have double EOLs..
if (mSrcX != 0) {
mSrcX = mRow.length;
}
break;
case 0x01:
// End of bitmap
mSrcX = mRow.length;
mSrcY = 0;
break;
case 0x02:
// Delta
deltaX = mSrcX + pInput.read();
deltaY = mSrcY - checkEOF(pInput.read());
mSrcX = mRow.length;
break;
default:
// Absolute mode
// Copy the next byte2 (3..255) bytes from file to output
// Two samples are packed into one byte
// If the number of bytes used to pack is not a mulitple of 2,
// an additional padding byte is in the stream and must be skipped
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
while (byte2 > 1) {
int packed = checkEOF(pInput.read());
mRow[mSrcX++] = (byte) packed;
byte2 -= 2;
}
if (byte2 == 1) {
// TODO: Half byte alignment? Seems to be ok...
int packed = checkEOF(pInput.read());
mRow[mSrcX++] = (byte) (packed & 0xf0);
}
if (paddingByte) {
checkEOF(pInput.read());
}
break;
}
}
else {
// Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says
while (byte1 > 1) {
mRow[mSrcX++] = (byte) byte2;
byte1 -= 2;
}
if (byte1 == 1) {
// TODO: Half byte alignment? Seems to be ok...
mRow[mSrcX++] = (byte) (byte2 & 0xf0);
}
}
// If we're done with a complete row, copy the data
if (mSrcX == mRow.length) {
// Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) {
mSrcX = (deltaX + 1) / 2;
if (deltaY > mSrcY) {
mSrcY = deltaY;
break;
}
deltaX = 0;
deltaY = 0;
}
else {
mSrcX = 0;
mSrcY--;
break;
}
}
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.enc;
import java.io.InputStream;
import java.io.IOException;
/**
* Implements 8 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @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/enc/RLE8Decoder.java#1 $
*/
// TODO: Move to other package or make public
final class RLE8Decoder extends AbstractRLEDecoder {
public RLE8Decoder(int pWidth, int pHeight) {
super(pWidth, pHeight);
}
protected void decodeRow(final InputStream pInput) throws IOException {
int deltaX = 0;
int deltaY = 0;
while (mSrcY >= 0) {
int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read());
if (byte1 == 0x00) {
switch (byte2) {
case 0x00:
// End of line
// NOTE: Some BMPs have double EOLs..
if (mSrcX != 0) {
mSrcX = mRow.length;
}
break;
case 0x01:
// End of bitmap
mSrcX = mRow.length;
mSrcY = 0;
break;
case 0x02:
// Delta
deltaX = mSrcX + pInput.read();
deltaY = mSrcY - checkEOF(pInput.read());
mSrcX = mRow.length;
break;
default:
// Absolute mode
// Copy the next byte2 (3..255) bytes from file to output
boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) {
mRow[mSrcX++] = (byte) checkEOF(pInput.read());
}
if (paddingByte) {
checkEOF(pInput.read());
}
}
}
else {
// Encoded mode
// Replicate byte2 as many times as byte1 says
byte value = (byte) byte2;
while (byte1-- > 0) {
mRow[mSrcX++] = value;
}
}
// If we're done with a complete row, copy the data
if (mSrcX == mRow.length) {
// Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) {
mSrcX = deltaX;
if (deltaY != mSrcY) {
mSrcY = deltaY;
break;
}
deltaX = 0;
deltaY = 0;
}
else {
mSrcX = 0;
mSrcY--;
break;
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
/**
* Contains customized stream classes, that can read or write compressed data on the fly,
* along with encoders and decoders for popular stream formats, such as ZIP (deflate), LZW, PackBits etc..
*
* @see com.twelvemonkeys.io.enc.DecoderStream
* @see com.twelvemonkeys.io.enc.EncoderStream
* @see com.twelvemonkeys.io.enc.Decoder
* @see com.twelvemonkeys.io.enc.Encoder
* @see com.twelvemonkeys.io.enc.DecodeException
*
* @version 2.0
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
package com.twelvemonkeys.io.enc;

View File

@@ -0,0 +1,761 @@
/*
* 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.ole2;
import com.twelvemonkeys.io.*;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.stream.ImageInputStream;
import java.io.*;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
/**
* Represents a read-only OLE2 compound document.
* <p/>
* <!-- TODO: Consider really detaching the entries, as this is hard for users to enforce... -->
* <em>NOTE: This class is not synchronized. Accessing the document or its
* entries from different threads, will need synchronization on the document
* instance.</em>
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/io/ole2/CompoundDocument.java#4 $
*/
public final class CompoundDocument {
// TODO: Write support...
// TODO: Properties: http://support.microsoft.com/kb/186898
private static final byte[] MAGIC = new byte[]{
(byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
(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;
/** The epoch offset of CompoundDocument time stamps */
public final static long EPOCH_OFFSET = -11644477200000L;
/**
* Creates a (for now) read only {@code CompoundDocument}.
*
* @param pFile the file to read from
*
* @throws IOException if an I/O exception occurs while reading the header
*/
public CompoundDocument(final File pFile) throws IOException {
mInput = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
// 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
// sure we're reading a valid document
readHeader();
}
/**
* Creates a read only {@code CompoundDocument}.
*
* @param pInput the input to read from
*
* @throws IOException if an I/O exception occurs while reading the header
*/
public CompoundDocument(final InputStream pInput) throws IOException {
this(new FileCacheSeekableStream(pInput));
}
// For testing only, consider exposing later
CompoundDocument(final SeekableInputStream pInput) throws IOException {
mInput = new SeekableLittleEndianDataInputStream(pInput);
// 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
// sure we're reading a valid document
readHeader();
}
/**
* Creates a read only {@code CompoundDocument}.
*
* @param pInput the input to read from
*
* @throws IOException if an I/O exception occurs while reading the header
*/
public CompoundDocument(final ImageInputStream pInput) throws IOException {
mInput = pInput;
// 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
// sure we're reading a valid document
readHeader();
}
public static boolean canRead(final DataInput pInput) {
return canRead(pInput, true);
}
// TODO: Refactor.. Figure out what we really need to expose to ImageIO for
// easy reading of the Thumbs.db file
// It's probably safer to create one version for InputStream and one for File
private static boolean canRead(final DataInput pInput, final boolean pReset) {
long pos = FREE_SID;
if (pReset) {
try {
if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) {
((InputStream) pInput).mark(8);
}
else if (pInput instanceof ImageInputStream) {
((ImageInputStream) pInput).mark();
}
else if (pInput instanceof RandomAccessFile) {
pos = ((RandomAccessFile) pInput).getFilePointer();
}
else if (pInput instanceof LittleEndianRandomAccessFile) {
pos = ((LittleEndianRandomAccessFile) pInput).getFilePointer();
}
else {
return false;
}
}
catch (IOException ignore) {
return false;
}
}
try {
byte[] magic = new byte[8];
pInput.readFully(magic);
return Arrays.equals(magic, MAGIC);
}
catch (IOException ignore) {
// Ignore
}
finally {
if (pReset) {
try {
if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) {
((InputStream) pInput).reset();
}
else if (pInput instanceof ImageInputStream) {
((ImageInputStream) pInput).reset();
}
else if (pInput instanceof RandomAccessFile) {
((RandomAccessFile) pInput).seek(pos);
}
else if (pInput instanceof LittleEndianRandomAccessFile) {
((LittleEndianRandomAccessFile) pInput).seek(pos);
}
}
catch (IOException e) {
// TODO: This isn't actually good enough...
// Means something fucked up, and will fail...
e.printStackTrace();
}
}
}
return false;
}
private void readHeader() throws IOException {
if (mMasterSAT != null) {
return;
}
if (!canRead(mInput, false)) {
throw new CorruptDocumentException("Not an OLE 2 Compound Document");
}
// UID (seems to be all 0s)
mUID = new UUID(mInput.readLong(), mInput.readLong());
/*int version = */mInput.readUnsignedShort();
//System.out.println("version: " + version);
/*int revision = */mInput.readUnsignedShort();
//System.out.println("revision: " + revision);
int byteOrder = mInput.readUnsignedShort();
if (byteOrder != 0xfffe) {
// Reversed, as I'm allready reading little-endian
throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
}
mSectorSize = 1 << mInput.readUnsignedShort();
//System.out.println("sectorSize: " + mSectorSize + " bytes");
mShortSectorSize = 1 << mInput.readUnsignedShort();
//System.out.println("shortSectorSize: " + mShortSectorSize + " bytes");
// Reserved
if (mInput.skipBytes(10) != 10) {
throw new CorruptDocumentException();
}
int SATSize = mInput.readInt();
//System.out.println("normalSATSize: " + mSATSize);
mDirectorySId = mInput.readInt();
//System.out.println("directorySId: " + mDirectorySId);
// Reserved
if (mInput.skipBytes(4) != 4) {
throw new CorruptDocumentException();
}
mMinStreamSize = mInput.readInt();
//System.out.println("minStreamSize: " + mMinStreamSize + " bytes");
mShortSATSID = mInput.readInt();
//System.out.println("shortSATSID: " + mShortSATSID);
mShortSATSize = mInput.readInt();
//System.out.println("shortSATSize: " + mShortSATSize);
int masterSATSId = mInput.readInt();
//System.out.println("masterSATSId: " + mMasterSATSID);
int masterSATSize = mInput.readInt();
//System.out.println("masterSATSize: " + mMasterSATSize);
// Read masterSAT: 436 bytes, containing up to 109 SIDs
//System.out.println("MSAT:");
mMasterSAT = new int[SATSize];
final int headerSIds = Math.min(SATSize, 109);
for (int i = 0; i < headerSIds; i++) {
mMasterSAT[i] = mInput.readInt();
//System.out.println("\tSID(" + i + "): " + mMasterSAT[i]);
}
if (masterSATSId == END_OF_CHAIN_SID) {
// End of chain
int freeSIdLength = 436 - (SATSize * 4);
if (mInput.skipBytes(freeSIdLength) != freeSIdLength) {
throw new CorruptDocumentException();
}
}
else {
// Parse the SIDs in the extended MasterSAT sectors...
seekToSId(masterSATSId, FREE_SID);
int index = headerSIds;
for (int i = 0; i < masterSATSize; i++) {
for (int j = 0; j < 127; j++) {
int sid = mInput.readInt();
switch (sid) {
case FREE_SID:// Free
break;
default:
mMasterSAT[index++] = sid;
break;
}
}
int next = mInput.readInt();
if (next == END_OF_CHAIN_SID) {// End of chain
break;
}
seekToSId(next, FREE_SID);
}
}
}
private void readSAT() throws IOException {
if (mSAT != null) {
return;
}
final int intsPerSector = mSectorSize / 4;
// Read the Sector Allocation Table
mSAT = new int[mMasterSAT.length * intsPerSector];
for (int i = 0; i < mMasterSAT.length; i++) {
seekToSId(mMasterSAT[i], FREE_SID);
for (int j = 0; j < intsPerSector; j++) {
int nextSID = mInput.readInt();
int index = (j + (i * intsPerSector));
mSAT[index] = nextSID;
}
}
// Read the short-stream Sector Allocation Table
SIdChain chain = getSIdChain(mShortSATSID, FREE_SID);
mShortSAT = new int[mShortSATSize * intsPerSector];
for (int i = 0; i < mShortSATSize; i++) {
seekToSId(chain.get(i), FREE_SID);
for (int j = 0; j < intsPerSector; j++) {
int nextSID = mInput.readInt();
int index = (j + (i * intsPerSector));
mShortSAT[index] = nextSID;
}
}
}
/**
* Gets the SIdChain for the given stream Id
*
* @param pSId the stream Id
* @param pStreamSize the size of the stream, or -1 for system control streams
* @return the SIdChain for the given stream Id
* @throws IOException if an I/O exception occurs
*/
private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException {
SIdChain chain = new SIdChain();
int[] sat = isShortStream(pStreamSize) ? mShortSAT : mSAT;
int sid = pSId;
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
chain.addSID(sid);
sid = sat[sid];
}
return chain;
}
private boolean isShortStream(final long pStreamSize) {
return pStreamSize != FREE_SID && pStreamSize < mMinStreamSize;
}
/**
* Seeks to the start pos for the given stream Id
*
* @param pSId the stream Id
* @param pStreamSize the size of the stream, or -1 for system control streams
* @throws IOException if an I/O exception occurs
*/
private void seekToSId(final int pSId, final long pStreamSize) throws IOException {
long pos;
if (isShortStream(pStreamSize)) {
// The short-stream is not continouos...
Entry root = getRootEntry();
if (mShortStreamSIdChain == null) {
mShortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
}
int shortPerStd = mSectorSize / mShortSectorSize;
int offset = pSId / shortPerStd;
int shortOffset = pSId - (offset * shortPerStd);
pos = HEADER_SIZE
+ (mShortStreamSIdChain.get(offset) * (long) mSectorSize)
+ (shortOffset * (long) mShortSectorSize);
}
else {
pos = HEADER_SIZE + pSId * (long) mSectorSize;
}
if (mInput instanceof LittleEndianRandomAccessFile) {
((LittleEndianRandomAccessFile) mInput).seek(pos);
}
else if (mInput instanceof ImageInputStream) {
((ImageInputStream) mInput).seek(pos);
}
else {
((SeekableLittleEndianDataInputStream) mInput).seek(pos);
}
}
private void seekToDId(final int pDId) throws IOException {
if (mDirectorySIdChain == null) {
mDirectorySIdChain = getSIdChain(mDirectorySId, FREE_SID);
}
int dIdsPerSId = mSectorSize / Entry.LENGTH;
int sIdOffset = pDId / dIdsPerSId;
int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
int sId = mDirectorySIdChain.get(sIdOffset);
seekToSId(sId, FREE_SID);
if (mInput instanceof LittleEndianRandomAccessFile) {
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) mInput;
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
}
else if (mInput instanceof ImageInputStream) {
ImageInputStream input = (ImageInputStream) mInput;
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
}
else {
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) mInput;
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
}
}
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
SIdChain chain = getSIdChain(pStreamId, pStreamSize);
// 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)..
int sectorSize = pStreamSize < mMinStreamSize ? mShortSectorSize : mSectorSize;
return new Stream(chain, pStreamSize, sectorSize, this);
}
private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException {
// This is always exactly 128 bytes, so we'll just read it all,
// and buffer (we might want to optimize this later).
byte[] bytes = new byte[Entry.LENGTH];
seekToDId(pDirectoryId);
mInput.readFully(bytes);
return new ByteArrayInputStream(bytes);
}
Entry getEntry(final int pDirectoryId, Entry pParent) throws IOException {
Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
getDirectoryStreamForDId(pDirectoryId)
));
entry.mParent = pParent;
entry.mDocument = this;
return entry;
}
SortedSet<Entry> getEntries(final int pDirectoryId, final Entry pParent)
throws IOException {
return getEntriesRecursive(pDirectoryId, pParent, new TreeSet<Entry>());
}
private SortedSet<Entry> getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet<Entry> pEntries)
throws IOException {
//System.out.println("pDirectoryId: " + pDirectoryId);
Entry entry = getEntry(pDirectoryId, pParent);
//System.out.println("entry: " + entry);
if (!pEntries.add(entry)) {
// TODO: This occurs in some Thumbs.db files, and Windows will
// still parse the file gracefully somehow...
// Deleting and regenerating the file will remove the cyclic
// references, but... How can Windows parse this file?
throw new CorruptDocumentException("Cyclic chain reference for entry: " + pDirectoryId);
}
if (entry.prevDId != FREE_SID) {
//System.out.println("prevDId: " + entry.prevDId);
getEntriesRecursive(entry.prevDId, pParent, pEntries);
}
if (entry.nextDId != FREE_SID) {
//System.out.println("nextDId: " + entry.nextDId);
getEntriesRecursive(entry.nextDId, pParent, pEntries);
}
return pEntries;
}
/*public*/ Entry getEntry(String pPath) throws IOException {
if (StringUtil.isEmpty(pPath) || !pPath.startsWith("/")) {
throw new IllegalArgumentException("Path must be absolute, and contain a valid path: " + pPath);
}
Entry entry = getRootEntry();
if (pPath.equals("/")) {
// '/' means root entry
return entry;
}
else {
// Otherwise get children recursively:
String[] pathElements = StringUtil.toStringArray(pPath, "/");
for (String pathElement : pathElements) {
entry = entry.getChildEntry(pathElement);
// No such child...
if (entry == null) {
break;// TODO: FileNotFoundException? Should behave like Entry.getChildEntry!!
}
}
return entry;
}
}
public Entry getRootEntry() throws IOException {
if (mRootEntry == null) {
readSAT();
mRootEntry = getEntry(0, null);
if (mRootEntry.type != Entry.ROOT_STORAGE) {
throw new CorruptDocumentException("Invalid root storage type: " + mRootEntry.type);
}
}
return mRootEntry;
}
// @Override
// public int hashCode() {
// return mUID.hashCode();
// }
//
// @Override
// public boolean equals(final Object pOther) {
// if (pOther == this) {
// return true;
// }
//
// if (pOther == null) {
// return true;
// }
//
// if (pOther.getClass() == getClass()) {
// return mUID.equals(((CompoundDocument) pOther).mUID);
// }
//
// return false;
// }
@Override
public String toString() {
return String.format(
"%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]",
getClass().getSimpleName(), mUID, mSectorSize, mShortSectorSize, mDirectorySId, mMasterSAT.length
);
}
/**
* Converts the given time stamp to standard Java time representation,
* milliseconds since January 1, 1970.
* The time stamp parameter is assumed to be in units of
* 100 nano seconds since January 1, 1601.
* <p/>
* If the timestamp is {@code 0L} (meaning not specified), no conversion
* is done, to behave like {@code java.io.File}.
*
* @param pMSTime an unsigned long value representing the time stamp (in
* units of 100 nano seconds since January 1, 1601).
*
* @return the time stamp converted to Java time stamp in milliseconds,
* or {@code 0L} if {@code pMSTime == 0L}
*/
public static long toJavaTimeInMillis(final long pMSTime) {
// NOTE: The time stamp field is an unsigned 64-bit integer value that
// contains the time elapsed since 1601-Jan-01 00:00:00 (Gregorian
// calendar).
// One unit of this value is equal to 100 nanoseconds).
// That means, each second the time stamp value will be increased by
// 10 million units.
if (pMSTime == 0L) {
return 0L; // This is just less confusing...
}
// Convert to milliseconds (signed),
// then convert to Java std epoch (1970-Jan-01 00:00:00)
return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET;
}
// TODO: Enforce stream length!
static class Stream extends SeekableInputStream {
private SIdChain mChain;
int mNextSectorPos;
byte[] mBuffer;
int mBufferPos;
private final CompoundDocument mDocument;
private final long mLength;
public Stream(final SIdChain pChain, final long pLength, final int pSectorSize, final CompoundDocument pDocument) {
mChain = pChain;
mLength = pLength;
mBuffer = new byte[pSectorSize];
mBufferPos = mBuffer.length;
mDocument = pDocument;
}
@Override
public int available() throws IOException {
return (int) Math.min(mBuffer.length - mBufferPos, mLength - getStreamPosition());
}
public int read() throws IOException {
if (available() <= 0) {
if (!fillBuffer()) {
return -1;
}
}
return mBuffer[mBufferPos++] & 0xff;
}
private boolean fillBuffer() throws IOException {
if (mNextSectorPos < mChain.length()) {
// TODO: Sync on mDocument.mInput here, and we are completely detached... :-)
// TODO: We also need to sync other places...
synchronized (mDocument) {
mDocument.seekToSId(mChain.get(mNextSectorPos), mLength);
mDocument.mInput.readFully(mBuffer);
}
mNextSectorPos++;
mBufferPos = 0;
return true;
}
return false;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
if (available() <= 0) {
if (!fillBuffer()) {
return -1;
}
}
int toRead = Math.min(len, available());
System.arraycopy(mBuffer, mBufferPos, b, off, toRead);
mBufferPos += toRead;
return toRead;
}
public boolean isCached() {
return true;
}
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 {
private final SeekableInputStream mSeekable;
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
super(pInput);
mSeekable = pInput;
}
public void seek(final long pPosition) throws IOException {
mSeekable.seek(pPosition);
}
public boolean isCachedFile() {
return mSeekable.isCachedFile();
}
public boolean isCachedMemory() {
return mSeekable.isCachedMemory();
}
public boolean isCached() {
return mSeekable.isCached();
}
public long getStreamPosition() throws IOException {
return mSeekable.getStreamPosition();
}
public long getFlushedPosition() throws IOException {
return mSeekable.getFlushedPosition();
}
public void flushBefore(final long pPosition) throws IOException {
mSeekable.flushBefore(pPosition);
}
public void flush() throws IOException {
mSeekable.flush();
}
@Override
public void reset() throws IOException {
mSeekable.reset();
}
public void mark() {
mSeekable.mark();
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.ole2;
import java.io.IOException;
/**
* Thrown when an OLE 2 compound document is considered corrupt.
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/io/ole2/CorruptDocumentException.java#3 $
* @see com.twelvemonkeys.io.ole2.CompoundDocument
*/
public class CorruptDocumentException extends IOException {
public CorruptDocumentException() {
this("Corrupt OLE 2 Compound Document");
}
public CorruptDocumentException(final String pMessage) {
super(pMessage);
}
public CorruptDocumentException(final Throwable pCause) {
super(pCause.getMessage());
initCause(pCause);
}
}

View File

@@ -0,0 +1,340 @@
/*
* 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.ole2;
import com.twelvemonkeys.io.SeekableInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Represents an OLE 2 compound document entry.
* This is similar to a file in a file system, or an entry in a ZIP or JAR file.
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/io/ole2/Entry.java#4 $
* @see com.twelvemonkeys.io.ole2.CompoundDocument
*/
// TODO: Consider extending java.io.File...
public final class Entry implements Comparable<Entry> {
String name;
byte type;
byte nodeColor;
int prevDId;
int nextDId;
int rootNodeDId;
long createdTimestamp;
long modifiedTimestamp;
int startSId;
int streamSize;
CompoundDocument mDocument;
Entry mParent;
SortedSet<Entry> mChildren;
public final static int LENGTH = 128;
static final int EMPTY = 0;
static final int USER_STORAGE = 1;
static final int USER_STREAM = 2;
static final int LOCK_BYTES = 3;
static final int PROPERTY = 4;
static final int ROOT_STORAGE = 5;
private static final SortedSet<Entry> NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet<Entry>());
private Entry() {
}
/**
* Reads an entry from the input.
*
* @param pInput the input data
* @return the {@code Entry} read from the input data
* @throws IOException if an i/o exception occurs during reading
*/
static Entry readEntry(final DataInput pInput) throws IOException {
Entry p = new Entry();
p.read(pInput);
return p;
}
/**
* Reads this entry
*
* @param pInput the input data
* @throws IOException if an i/o exception occurs during reading
*/
private void read(final DataInput pInput) throws IOException {
char[] chars = new char[32];
for (int i = 0; i < chars.length; i++) {
chars[i] = pInput.readChar();
}
// NOTE: Length is in bytes, including the null-terminator...
int nameLength = pInput.readShort();
name = new String(chars, 0, (nameLength - 1) / 2);
//System.out.println("name: " + name);
type = pInput.readByte();
//System.out.println("type: " + type);
nodeColor = pInput.readByte();
//System.out.println("nodeColor: " + nodeColor);
prevDId = pInput.readInt();
//System.out.println("prevDID: " + prevDID);
nextDId = pInput.readInt();
//System.out.println("nextDID: " + nextDID);
rootNodeDId = pInput.readInt();
//System.out.println("rootNodeDID: " + rootNodeDID);
// UID (16) + user flags (4), ignored
if (pInput.skipBytes(20) != 20) {
throw new CorruptDocumentException();
}
createdTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
startSId = pInput.readInt();
//System.out.println("startSID: " + startSID);
streamSize = pInput.readInt();
//System.out.println("streamSize: " + streamSize);
// Reserved
pInput.readInt();
}
/**
* If {@code true} this {@code Entry} is the root {@code Entry}.
*
* @return {@code true} if this is the root {@code Entry}
*/
public boolean isRoot() {
return type == ROOT_STORAGE;
}
/**
* If {@code true} this {@code Entry} is a directory
* {@code Entry}.
*
* @return {@code true} if this is a directory {@code Entry}
*/
public boolean isDirectory() {
return type == USER_STORAGE;
}
/**
* If {@code true} this {@code Entry} is a file (document)
* {@code Entry}.
*
* @return {@code true} if this is a document {@code Entry}
*/
public boolean isFile() {
return type == USER_STREAM;
}
/**
* Returns the name of this {@code Entry}
*
* @return the name of this {@code Entry}
*/
public String getName() {
return name;
}
/**
* Returns the {@code InputStream} for this {@code Entry}
*
* @return an {@code InputStream} containing the data for this
* {@code Entry} or {@code null} if this is a directory {@code Entry}
* @throws java.io.IOException if an I/O exception occurs
* @see #length()
*/
public SeekableInputStream getInputStream() throws IOException {
if (isDirectory()) {
return null;
}
return mDocument.getInputStreamForSId(startSId, streamSize);
}
/**
* Returns the length of this entry
*
* @return the length of the stream for this entry, or {@code 0} if this is
* a directory {@code Entry}
* @see #getInputStream()
*/
public long length() {
if (isDirectory()) {
return 0L;
}
return streamSize;
}
/**
* Returns the time that this entry was created.
* The time is converted from its internal representation to standard Java
* representation, milliseconds since the epoch
* (00:00:00 GMT, January 1, 1970).
* <p/>
* Note that most applications leaves this value empty ({@code 0L}).
*
* @return A {@code long} value representing the time this entry was
* created, measured in milliseconds since the epoch
* (00:00:00 GMT, January 1, 1970), or {@code 0L} if no
* creation time stamp exists for this entry.
*/
public long created() {
return createdTimestamp;
}
/**
* Returns the time that this entry was last modified.
* The time is converted from its internal representation to standard Java
* representation, milliseconds since the epoch
* (00:00:00 GMT, January 1, 1970).
* <p/>
* Note that many applications leaves this value empty ({@code 0L}).
*
* @return A {@code long} value representing the time this entry was
* last modified, measured in milliseconds since the epoch
* (00:00:00 GMT, January 1, 1970), or {@code 0L} if no
* modification time stamp exists for this entry.
*/
public long lastModified() {
return modifiedTimestamp;
}
/**
* Return the parent of this {@code Entry}
*
* @return the parent of this {@code Entry}, or {@code null} if this is
* the root {@code Entry}
*/
public Entry getParentEntry() {
return mParent;
}
/**
* Returns the child of this {@code Entry} with the given name.
*
* @param pName the name of the child {@code Entry}
* @return the child {@code Entry} or {@code null} if thee is no such
* child
* @throws java.io.IOException if an I/O exception occurs
*/
public Entry getChildEntry(final String pName) throws IOException {
if (isFile() || rootNodeDId == -1) {
return null;
}
Entry dummy = new Entry();
dummy.name = pName;
dummy.mParent = this;
SortedSet child = getChildEntries().tailSet(dummy);
return (Entry) child.first();
}
/**
* Returns the children of this {@code Entry}.
*
* @return a {@code SortedSet} of {@code Entry} objects
* @throws java.io.IOException if an I/O exception occurs
*/
public SortedSet<Entry> getChildEntries() throws IOException {
if (mChildren == null) {
if (isFile() || rootNodeDId == -1) {
mChildren = NO_CHILDREN;
}
else {
// Start at root node in R/B tree, and raed to the left and right,
// re-build tree, according to the docs
mChildren = mDocument.getEntries(rootNodeDId, this);
}
}
return mChildren;
}
@Override
public String toString() {
return "\"" + name + "\""
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
+ (mParent != null ? ", parent: \"" + mParent.getName() + "\"" : "")
+ (isFile() ? "" : ", children: " + (mChildren != null ? String.valueOf(mChildren.size()) : "(unknown)"))
+ ", SId=" + startSId + ", length=" + streamSize + ")";
}
@Override
public boolean equals(final Object pOther) {
if (pOther == this) {
return true;
}
if (!(pOther instanceof Entry)) {
return false;
}
Entry other = (Entry) pOther;
return name.equals(other.name) && (mParent == other.mParent
|| (mParent != null && mParent.equals(other.mParent)));
}
@Override
public int hashCode() {
return name.hashCode() ^ startSId;
}
public int compareTo(final Entry pOther) {
if (this == pOther) {
return 0;
}
// NOTE: This is the sorting algorthm defined by the Compound Document:
// - first sort by name length
// - if lengths are equal, sort by comparing strings, case sensitive
int diff = name.length() - pOther.name.length();
if (diff != 0) {
return diff;
}
return name.compareTo(pOther.name);
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.ole2;
import java.util.NoSuchElementException;
/**
* SIdChain
*
* @author <a href="mailto:harald.kuhr@gmail.no">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/io/ole2/SIdChain.java#1 $
*/
class SIdChain {
int[] chain;
int size = 0;
int next = 0;
public SIdChain() {
chain = new int[16];
}
void addSID(int pSID) {
ensureCapacity();
chain[size++] = pSID;
}
private void ensureCapacity() {
if (chain.length == size) {
int[] temp = new int[size << 1];
System.arraycopy(chain, 0, temp, 0, size);
chain = temp;
}
}
public int[] getChain() {
int[] result = new int[size];
System.arraycopy(chain, 0, result, 0, size);
return result;
}
public void reset() {
next = 0;
}
public boolean hasNext() {
return next < size;
}
public int next() {
if (next >= size) {
throw new NoSuchElementException("No element");
}
return chain[next++];
}
public int get(final int pIndex) {
return chain[pIndex];
}
public int length() {
return size;
}
public String toString() {
StringBuilder buf = new StringBuilder(size * 5);
buf.append('[');
for (int i = 0; i < size; i++) {
if (i != 0) {
buf.append(',');
}
buf.append(chain[i]);
}
buf.append(']');
return buf.toString();
}
}

View File

@@ -0,0 +1,11 @@
/**
* Contains classes for reading the contents of the
* Microsoft OLE 2 compound document format.
*
* @see com.twelvemonkeys.io.ole2.CompoundDocument
* @see <a href="http://sc.openoffice.org/compdocfileformat.pdf">OpenOffice.org's documentation</a>
*
* @version 2.0
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
package com.twelvemonkeys.io.ole2;

View File

@@ -0,0 +1,7 @@
<HTML>
<BODY>
Provides for system input and output through data streams, serialization and the file system.
</BODY>
</HTML>

View File

@@ -0,0 +1,7 @@
- Remove util.BASE64, make clients use io.Base64.
- Create subpackages
io.base64
io.lzw
io.packbits
io.zip

View File

@@ -0,0 +1,612 @@
/*
* 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.lang;
import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Arrays;
/**
* A utility class with some useful bean-related functions.
* <p/>
* <em>NOTE: This class is not considered part of the public API and may be
* changed without notice</em>
*
* @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/BeanUtil.java#2 $
*/
public final class BeanUtil {
// Disallow creating objects of this type
private BeanUtil() {
}
/**
* Gets a property value from the given object, using reflection.
* Now supports getting values from properties of properties
* (recursive).
*
* @param pObject The object to get the property from
* @param pProperty The name of the property
*
* @return A string containing the value of the given property, or null
* if it can not be found.
* @todo Remove System.err's... Create new Exception? Hmm..
*/
public static Object getPropertyValue(Object pObject, String pProperty) {
//
// TODO: Support get(Object) method of Collections!
// Handle lists and arrays with [] (index) operator
//
if (pObject == null || pProperty == null || pProperty.length() < 1) {
return null;
}
Class objClass = pObject.getClass();
Object result = pObject;
// Method for method...
String subProp;
int begIdx = 0;
int endIdx = begIdx;
while (begIdx < pProperty.length() && begIdx >= 0) {
endIdx = pProperty.indexOf(".", endIdx + 1);
if (endIdx > 0) {
subProp = pProperty.substring(begIdx, endIdx);
begIdx = endIdx + 1;
}
else {
// The final property!
// If there's just the first-level property, subProp will be
// equal to property
subProp = pProperty.substring(begIdx);
begIdx = -1;
}
// Check for "[" and "]"
Object[] param = null;
Class[] paramClass = new Class[0];
int begBracket;
if ((begBracket = subProp.indexOf("[")) > 0) {
// An error if there is no matching bracket
if (!subProp.endsWith("]")) {
return null;
}
String between = subProp.substring(begBracket + 1,
subProp.length() - 1);
subProp = subProp.substring(0, begBracket);
// If brackets exist, check type of argument between brackets
param = new Object[1];
paramClass = new Class[1];
//try {
// TODO: isNumber returns true, even if too big for integer...
if (StringUtil.isNumber(between)) {
// We have a number
// Integer -> array subscript -> getXXX(int i)
try {
// Insert param and it's Class
param[0] = Integer.valueOf(between);
paramClass[0] = Integer.TYPE; // int.class
}
catch (NumberFormatException e) {
// ??
// Probably too small or too large value..
}
}
else {
//catch (NumberFormatException e) {
// Not a number... Try String
// String -> Hashtable key -> getXXX(String str)
// Insert param and it's Class
param[0] = between.toLowerCase();
paramClass[0] = String.class;
}
}
Method method;
String methodName = "get" + StringUtil.capitalize(subProp);
try {
// Try to get the "get" method for the given property
method = objClass.getMethod(methodName, paramClass);
}
catch (NoSuchMethodException e) {
System.err.print("No method named \"" + methodName + "()\"");
// The array might be of size 0...
if (paramClass != null && paramClass.length > 0) {
System.err.print(" with the parameter "
+ paramClass[0].getName());
}
System.err.println(" in class " + objClass.getName() + "!");
return null;
}
// If method for some reason should be null, give up
if (method == null) {
return null;
}
try {
// We have a method, try to invoke it
// The resutling object will be either the property we are
// Looking for, or the parent
// System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")");
result = method.invoke(result, param);
}
catch (InvocationTargetException e) {
System.err.println("property=" + pProperty + " & result="
+ result + " & param=" + Arrays.toString(param));
e.getTargetException().printStackTrace();
e.printStackTrace();
return null;
}
catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
catch (NullPointerException e) {
System.err.println(objClass.getName() + "." + method.getName()
+ "(" + ((paramClass != null && paramClass.length > 0) ? paramClass[0].getName() : "") + ")");
e.printStackTrace();
return null;
}
if (result != null) {
// Get the class of the reulting object
objClass = result.getClass();
}
else {
return null;
}
} // while
return result;
}
/**
* Sets the property value to an object using reflection.
* Supports setting values of properties that are properties of
* properties (recursive).
*
* @param pObject The object to get a property from
* @param pProperty The name of the property
* @param pValue The property value
*
* @throws NoSuchMethodException if there's no write method for the
* given property
* @throws InvocationTargetException if invoking the write method failed
* @throws IllegalAccessException if the caller class has no access to the
* write method
*/
public static void setPropertyValue(Object pObject, String pProperty,
Object pValue)
throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
//
// TODO: Support set(Object, Object)/put(Object, Object) methods
// of Collections!
// Handle lists and arrays with [] (index) operator
Class paramType = pValue != null ? pValue.getClass() : Object.class;
// Preserve references
Object obj = pObject;
String property = pProperty;
// Recurse and find real parent if property contains a '.'
int dotIdx = property.indexOf('.');
if (dotIdx >= 0) {
// Get real parent
obj = getPropertyValue(obj, property.substring(0, dotIdx));
// Get the property of the parent
property = property.substring(dotIdx + 1);
}
// Find method
Object[] params = {pValue};
Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property),
new Class[] {paramType}, params);
// Invoke it
method.invoke(obj, params);
}
private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) throws NoSuchMethodException {
// NOTE: This method assumes pParams.length == 1 && pValues.length == 1
Method method = null;
Class paramType = pParams[0];
try {
method = pObject.getClass().getMethod(pName, pParams);
}
catch (NoSuchMethodException e) {
// No direct match
// 1: If primitive wrapper, try unwrap conversion first
/*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type
params[0] = ReflectUtil.wrapType(paramType);
}
else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) {
pParams[0] = ReflectUtil.unwrapType(paramType);
}
try {
// If this does not throw an excption, it works
method = pObject.getClass().getMethod(pName, pParams);
}
catch (Throwable t) {
// Ignore
}
// 2: Try any supertypes of paramType, to see if we have a match
if (method == null) {
while ((paramType = paramType.getSuperclass()) != null) {
pParams[0] = paramType;
try {
// If this does not throw an excption, it works
method = pObject.getClass().getMethod(pName, pParams);
}
catch (Throwable t) {
// Ignore/Continue
continue;
}
break;
}
}
// 3: Try to find a different method with the same name, that has
// a parameter type we can convert to...
// NOTE: There's no ordering here..
// TODO: Should we try to do that? What would the ordering be?
if (method == null) {
Method[] methods = pObject.getClass().getMethods();
for (Method candidate : methods) {
if (Modifier.isPublic(candidate.getModifiers())
&& candidate.getName().equals(pName)
&& candidate.getReturnType() == Void.TYPE
&& candidate.getParameterTypes().length == 1) {
// NOTE: Assumes paramTypes.length == 1
Class type = candidate.getParameterTypes()[0];
try {
pValues[0] = convertValueToType(pValues[0], type);
}
catch (Throwable t) {
continue;
}
// We were able to convert the parameter, let's try
method = candidate;
break;
}
}
}
// Give up...
if (method == null) {
throw e;
}
}
return method;
}
private static Object convertValueToType(Object pValue, Class pType) throws ConversionException {
if (pType.isPrimitive()) {
if (pType == Boolean.TYPE && pValue instanceof Boolean) {
return pValue;
}
else if (pType == Byte.TYPE && pValue instanceof Byte) {
return pValue;
}
else if (pType == Character.TYPE && pValue instanceof Character) {
return pValue;
}
else if (pType == Double.TYPE && pValue instanceof Double) {
return pValue;
}
else if (pType == Float.TYPE && pValue instanceof Float) {
return pValue;
}
else if (pType == Integer.TYPE && pValue instanceof Integer) {
return pValue;
}
else if (pType == Long.TYPE && pValue instanceof Long) {
return pValue;
}
else if (pType == Short.TYPE && pValue instanceof Short) {
return pValue;
}
}
// TODO: Convert other types
if (pValue instanceof String) {
Converter converter = Converter.getInstance();
return converter.toObject((String) pValue, pType);
}
else if (pType == String.class) {
Converter converter = Converter.getInstance();
return converter.toString(pValue);
}
else {
throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName());
}
}
/**
* Creates an object from the given class' single argument constructor.
*
* @param pClass The class to create instance from
* @param pParam The parameters to the constructor
*
* @return The object created from the constructor.
* If the constructor could not be invoked for any reason, null is
* returned.
*
* @throws InvocationTargetException if the constructor failed
*/
// TODO: Move to ReflectUtil
public static Object createInstance(Class pClass, Object pParam)
throws InvocationTargetException {
return createInstance(pClass, new Object[] {pParam});
}
/**
* Creates an object from the given class' constructor that matches
* the given paramaters.
*
* @param pClass The class to create instance from
* @param pParams The parameters to the constructor
*
* @return The object created from the constructor.
* If the constructor could not be invoked for any reason, null is
* returned.
*
* @throws InvocationTargetException if the constructor failed
*/
// TODO: Move to ReflectUtil
public static Object createInstance(Class pClass, Object... pParams)
throws InvocationTargetException {
Object value;
try {
// Create param and argument arrays
Class[] paramTypes = null;
if (pParams != null && pParams.length > 0) {
paramTypes = new Class[pParams.length];
for (int i = 0; i < pParams.length; i++) {
paramTypes[i] = pParams[i].getClass();
}
}
// Get constructor
//Constructor constructor = pClass.getDeclaredConstructor(paramTypes);
Constructor constructor = pClass.getConstructor(paramTypes);
// Invoke and create instance
value = constructor.newInstance(pParams);
}
/* All this to let InvocationTargetException pass on */
catch (NoSuchMethodException nsme) {
return null;
}
catch (IllegalAccessException iae) {
return null;
}
catch (IllegalArgumentException iarge) {
return null;
}
catch (InstantiationException ie) {
return null;
}
catch (ExceptionInInitializerError err) {
return null;
}
return value;
}
/**
* Gets an object from any given static method, with the given parameter.
*
* @param pClass The class to invoke method on
* @param pMethod The name of the method to invoke
* @param pParam The parameter to the method
*
* @return The object returned by the static method.
* If the return type of the method is a primitive type, it is wrapped in
* the corresponding wrapper object (int is wrapped in an Integer).
* If the return type of the method is void, null is returned.
* If the method could not be invoked for any reason, null is returned.
*
* @throws InvocationTargetException if the invocaton failed
*/
// TODO: Move to ReflectUtil
// TODO: Rename to invokeStatic?
public static Object invokeStaticMethod(Class pClass, String pMethod,
Object pParam)
throws InvocationTargetException {
return invokeStaticMethod(pClass, pMethod, new Object[] {pParam});
}
/**
* Gets an object from any given static method, with the given parameter.
*
* @param pClass The class to invoke method on
* @param pMethod The name of the method to invoke
* @param pParams The parameters to the method
*
* @return The object returned by the static method.
* If the return type of the method is a primitive type, it is wrapped in
* the corresponding wrapper object (int is wrapped in an Integer).
* If the return type of the method is void, null is returned.
* If the method could not be invoked for any reason, null is returned.
*
* @throws InvocationTargetException if the invocaton failed
*/
// TODO: Move to ReflectUtil
// TODO: Rename to invokeStatic?
public static Object invokeStaticMethod(Class pClass, String pMethod,
Object[] pParams)
throws InvocationTargetException {
Object value = null;
try {
// Create param and argument arrays
Class[] paramTypes = new Class[pParams.length];
for (int i = 0; i < pParams.length; i++) {
paramTypes[i] = pParams[i].getClass();
}
// Get method
// *** If more than one such method is found in the class, and one
// of these methods has a RETURN TYPE that is more specific than
// any of the others, that method is reflected; otherwise one of
// the methods is chosen ARBITRARILY.
// java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[])
Method method = pClass.getMethod(pMethod, paramTypes);
// Invoke public static method
if (Modifier.isPublic(method.getModifiers())
&& Modifier.isStatic(method.getModifiers())) {
value = method.invoke(null, pParams);
}
}
/* All this to let InvocationTargetException pass on */
catch (NoSuchMethodException nsme) {
return null;
}
catch (IllegalAccessException iae) {
return null;
}
catch (IllegalArgumentException iarge) {
return null;
}
return value;
}
/**
* Configures the bean according to the given mapping.
* For each <CODE>Map.Entry</CODE> in <CODE>Map.values()</CODE>,
* a method named
* <CODE>set + capitalize(entry.getKey())</CODE> is called on the bean,
* with <CODE>entry.getValue()</CODE> as its argument.
* <p/>
* Properties that has no matching set-method in the bean, are simply
* discarded.
*
* @param pBean The bean to configure
* @param pMapping The mapping for the bean
*
* @throws NullPointerException if any of the parameters are null.
* @throws InvocationTargetException if an error occurs when invoking the
* setter-method.
*/
// TODO: Add a version that takes a ConfigurationErrorListener callback interface
// TODO: ...or a boolean pFailOnError parameter
// TODO: ...or return Exceptions as an array?!
// TODO: ...or something whatsoever that makes clients able to determine something's not right
public static void configure(final Object pBean, final Map<String, ?> pMapping) throws InvocationTargetException {
configure(pBean, pMapping, false);
}
/**
* Configures the bean according to the given mapping.
* For each <CODE>Map.Entry</CODE> in <CODE>Map.values()</CODE>,
* a method named
* <CODE>set + capitalize(entry.getKey())</CODE> is called on the bean,
* with <CODE>entry.getValue()</CODE> as its argument.
* <p/>
* Optionally, lisp-style names are allowed, and automatically converted
* to Java-style camel-case names.
* <p/>
* Properties that has no matching set-method in the bean, are simply
* discarded.
*
* @see StringUtil#lispToCamel(String)
*
* @param pBean The bean to configure
* @param pMapping The mapping for the bean
* @param pLispToCamel Allow lisp-style names, and automatically convert
* them to Java-style camel-case.
*
* @throws NullPointerException if any of the parameters are null.
* @throws InvocationTargetException if an error occurs when invoking the
* setter-method.
*/
public static void configure(final Object pBean, final Map<String, ?> pMapping, final boolean pLispToCamel) throws InvocationTargetException {
// Loop over properties in mapping
for (final Map.Entry<String, ?> entry : pMapping.entrySet()) {
try {
// Configure each property in turn
final String property = StringUtil.valueOf(entry.getKey());
try {
setPropertyValue(pBean, property, entry.getValue());
}
catch (NoSuchMethodException ignore) {
// If invocation failed, convert lisp-style and try again
if (pLispToCamel && property.indexOf('-') > 0) {
setPropertyValue(pBean, StringUtil.lispToCamel(property, false),
entry.getValue());
}
}
}
catch (NoSuchMethodException nsme) {
// This property was not configured
}
catch (IllegalAccessException iae) {
// This property was not configured
}
}
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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.lang;
import java.util.Date;
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.
* <p/>
*
* @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/lang/DateUtil.java#1 $
*/
public final class DateUtil {
/** One second: 1000 milliseconds. */
public static final long SECOND = 1000l;
/** One minute: 60 seconds (60 000 milliseconds). */
public static final long MINUTE = 60l * SECOND;
/**
* One hour: 60 minutes (3 600 000 milliseconds).
* 60 minutes = 3 600 seconds = 3 600 000 milliseconds
*/
public static final long HOUR = 60l * MINUTE;
/**
* One day: 24 hours (86 400 000 milliseconds).
* 24 hours = 1 440 minutes = 86 400 seconds = 86 400 000 milliseconds.
*/
public static final long DAY = 24l * HOUR;
/**
* One calendar year: 365.2425 days (31556952000 milliseconds).
* 365.2425 days = 8765.82 hours = 525949.2 minutes = 31556952 seconds
* = 31556952000 milliseconds.
*/
public static final long CALENDAR_YEAR = 3652425l * 24l * 60l * 6l;
private DateUtil() {
}
/**
* Returns the time between the given start time and now (as defined by
* {@link System#currentTimeMillis()}).
*
* @param pStart the start time
*
* @return the time between the given start time and now.
*/
public static long delta(long pStart) {
return System.currentTimeMillis() - pStart;
}
/**
* Returns the time between the given start time and now (as defined by
* {@link System#currentTimeMillis()}).
*
* @param pStart the start time
*
* @return the time between the given start time and now.
*/
public static long delta(Date pStart) {
return System.currentTimeMillis() - pStart.getTime();
}
/**
* Gets the current time, rounded down to the closest second.
* Equivalent to invoking
* <tt>roundToSecond(System.currentTimeMillis())</tt>.
*
* @return the current time, rounded to the closest second.
*/
public static long currentTimeSecond() {
return roundToSecond(System.currentTimeMillis());
}
/**
* Gets the current time, rounded down to the closest minute.
* Equivalent to invoking
* <tt>roundToMinute(System.currentTimeMillis())</tt>.
*
* @return the current time, rounded to the closest minute.
*/
public static long currentTimeMinute() {
return roundToMinute(System.currentTimeMillis());
}
/**
* Gets the current time, rounded down to the closest hour.
* Equivalent to invoking
* <tt>roundToHour(System.currentTimeMillis())</tt>.
*
* @return the current time, rounded to the closest hour.
*/
public static long currentTimeHour() {
return roundToHour(System.currentTimeMillis());
}
/**
* Gets the current time, rounded down to the closest day.
* Equivalent to invoking
* <tt>roundToDay(System.currentTimeMillis())</tt>.
*
* @return the current time, rounded to the closest day.
*/
public static long currentTimeDay() {
return roundToDay(System.currentTimeMillis());
}
/**
* Rounds the given time down to the closest second.
*
* @param pTime time
* @return the time rounded to the closest second.
*/
public static long roundToSecond(long pTime) {
return (pTime / SECOND) * SECOND;
}
/**
* Rounds the given time down to the closest minute.
*
* @param pTime time
* @return the time rounded to the closest minute.
*/
public static long roundToMinute(long pTime) {
return (pTime / MINUTE) * MINUTE;
}
/**
* Rounds the given time down to the closest hour, using the default timezone.
*
* @param pTime time
* @return the time rounded to the closest hour.
*/
public static long roundToHour(long pTime) {
// TODO: What if timezone offset is sub hour? Are there any? I think so...
return ((pTime / HOUR) * HOUR);
}
/**
* Rounds the given time down to the closest day, using the default timezone.
*
* @param pTime time
* @return the time rounded to the closest day.
*/
public static long roundToDay(long pTime) {
return roundToDay(pTime, TimeZone.getDefault());
}
/**
* Rounds the given time down to the closest day, using the given timezone.
*
* @param pTime time
* @param pTimeZone the timezone to use when rounding
* @return the time rounded to the closest day.
*/
public static long roundToDay(long pTime, TimeZone pTimeZone) {
int offset = pTimeZone.getOffset(pTime);
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)));
}
}

View File

@@ -0,0 +1,129 @@
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"));
}
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.lang;
/**
* The class MathUtil contains methods for performing basic numeric operations
* such as the elementary exponential, logarithm, square root, and
* trigonometric functions.
*
* @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/MathUtil.java#1 $
*/
public final class 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</code> 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</code> 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.
* Equivalent to <CODE>java.lang.Math.log</CODE>, just with a proper name.
*
* @param pArg a number greater than 0.0.
* @return the value ln <CODE>pArg</CODE>, the natural logarithm of
* <CODE>pArg</CODE>.
*
* @see java.lang.Math#log(double)
*/
public static double ln(final double pArg) {
return Math.log(pArg);
}
private final static double LN_10 = Math.log(10);
/**
* Returns the base 10 logarithm of a double value.
*
* @param pArg a number greater than 0.0.
* @return the value log <CODE>pArg</CODE>, the base 10 logarithm of
* <CODE>pArg</CODE>.
*/
public static double log(final double pArg) {
return Math.log(pArg) / LN_10;
}
private final static double LN_2 = Math.log(10);
/**
* Returns the base 2 logarithm of a double value.
*
* @param pArg a number greater than 0.0.
* @return the value log<SUB>2</SUB> <CODE>pArg</CODE>, the base 2
* logarithm of <CODE>pArg</CODE>.
*/
public static double log2(final double pArg) {
return Math.log(pArg) / LN_2;
}
/**
* Returns the base <i>N</i> logarithm of a double value, for a given base
* <i>N</i>.
*
* @param pArg a number greater than 0.0.
* @param pBase a number greater than 0.0.
*
* @return the value log<SUB>pBase</SUB> <CODE>pArg</CODE>, the base
* <CODE>pBase</CODE> logarithm of <CODE>pArg</CODE>.
*/
public static double log(final double pArg, final double pBase) {
return Math.log(pArg) / Math.log(pBase);
}
/**
* A replacement for <tt>Math.abs</tt>, that never returns negative values.
* <tt>Math.abs(long)</tt> does this for <tt>Long.MIN_VALUE</tt>.
*
* @see Math#abs(long)
* @see Long#MIN_VALUE
*
* @param pNumber
* @return the absolute value of <tt>pNumber</tt>
*
* @throws ArithmeticException if <tt>pNumber == Long.MIN_VALUE</tt>
*/
public static long abs(final long pNumber) {
if (pNumber == Long.MIN_VALUE) {
throw new ArithmeticException("long overflow: 9223372036854775808");
}
return (pNumber < 0) ? -pNumber : pNumber;
}
/**
* A replacement for <tt>Math.abs</tt>, that never returns negative values.
* <tt>Math.abs(int)</tt> does this for <tt>Integer.MIN_VALUE</tt>.
*
* @see Math#abs(int)
* @see Integer#MIN_VALUE
*
* @param pNumber
* @return the absolute value of <tt>pNumber</tt>
*
* @throws ArithmeticException if <tt>pNumber == Integer.MIN_VALUE</tt>
*/
public static int abs(final int pNumber) {
if (pNumber == Integer.MIN_VALUE) {
throw new ArithmeticException("int overflow: 2147483648");
}
return (pNumber < 0) ? -pNumber : pNumber;
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.lang;
/**
* MostUnfortunateException
* <p/>
*
* @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/lang/MostUnfortunateException.java#1 $
*/
class MostUnfortunateException extends RuntimeException {
public MostUnfortunateException() {
this("Most unfortunate.");
}
public MostUnfortunateException(Throwable pCause) {
this(pCause.getMessage(), pCause);
}
public MostUnfortunateException(String pMessage, Throwable pCause) {
this(pMessage);
initCause(pCause);
}
public MostUnfortunateException(String pMessage) {
super("A most unfortunate exception has occured: " + pMessage);
}
}

View File

@@ -0,0 +1,398 @@
/*
* 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.lang;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.util.FilterIterator;
import com.twelvemonkeys.util.service.ServiceRegistry;
import java.io.*;
import java.util.Iterator;
import java.util.Arrays;
/**
* NativeLoader
* <p/>
*
* @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/lang/NativeLoader.java#2 $
*/
final class NativeLoader {
// TODO: Considerations:
// - Rename all libs like the current code, to <library>.(so|dll|dylib)?
// - Keep library filename from jar, and rather store a separate
// properties-file with the library->library-file mappings?
// - As all invocations are with library file name, we could probably skip
// both renaming and properties-file altogether...
// TODO: The real trick here, is how to load the correct library for the
// current platform...
// - Change String pResource to String[] pResources?
// - NativeResource class, that has a list of multiple resources?
// NativeResource(Map<String, String>) os->native lib mapping
// TODO: Consider exposing the method from SystemUtil
// TODO: How about a SPI based solution?!
// public interface com.twelvemonkeys.lang.NativeResourceProvider
//
// imlementations return a pointer to the correct resource for a given (by
// this class) OS.
//
// String getResourceName(...)
//
// See http://tolstoy.com/samizdat/sysprops.html
// System properties:
// "os.name"
// Windows, Linux, Solaris/SunOS,
// Mac OS/Mac OS X/Rhapsody (aka Mac OS X Server)
// General Unix (AIX, Digital Unix, FreeBSD, HP-UX, Irix)
// OS/2
// "os.arch"
// Windows: x86
// Linux: x86, i386, i686, x86_64, ia64,
// Solaris: sparc, sparcv9, x86
// Mac OS: PowerPC, ppc, i386
// AIX: x86, ppc
// Digital Unix: alpha
// FreeBSD: x86, sparc
// HP-UX: PA-RISC
// Irix: mips
// OS/2: x86
// "os.version"
// Windows: 4.0 -> NT/95, 5.0 -> 2000, 5.1 -> XP (don't care about old versions, CE etc)
// Mac OS: 8.0, 8.1, 10.0 -> OS X, 10.x.x -> OS X, 5.6 -> Rhapsody (!)
//
// Normalize os.name, os.arch and os.version?!
///** Normalized operating system constant */
//static final OperatingSystem OS_NAME = normalizeOperatingSystem();
//
///** Normalized system architecture constant */
//static final Architecture OS_ARCHITECTURE = normalizeArchitecture();
//
///** Unormalized operating system version constant (for completeness) */
//static final String OS_VERSION = System.getProperty("os.version");
static final NativeResourceRegistry sRegistry = new NativeResourceRegistry();
private NativeLoader() {
}
/*
private static Architecture normalizeArchitecture() {
String arch = System.getProperty("os.arch");
if (arch == null) {
throw new IllegalStateException("System property \"os.arch\" == null");
}
arch = arch.toLowerCase();
if (OS_NAME == OperatingSystem.Windows
&& (arch.startsWith("x86") || arch.startsWith("i386"))) {
return Architecture.X86;
// TODO: 64 bit
}
else if (OS_NAME == OperatingSystem.Linux) {
if (arch.startsWith("x86") || arch.startsWith("i386")) {
return Architecture.I386;
}
else if (arch.startsWith("i686")) {
return Architecture.I686;
}
// TODO: More Linux options?
// TODO: 64 bit
}
else if (OS_NAME == OperatingSystem.MacOS) {
if (arch.startsWith("power") || arch.startsWith("ppc")) {
return Architecture.PPC;
}
else if (arch.startsWith("i386")) {
return Architecture.I386;
}
}
else if (OS_NAME == OperatingSystem.Solaris) {
if (arch.startsWith("sparc")) {
return Architecture.SPARC;
}
if (arch.startsWith("x86")) {
// TODO: Should we use i386 as Linux and Mac does?
return Architecture.X86;
}
// TODO: 64 bit
}
return Architecture.Unknown;
}
*/
/*
private static OperatingSystem normalizeOperatingSystem() {
String os = System.getProperty("os.name");
if (os == null) {
throw new IllegalStateException("System property \"os.name\" == null");
}
os = os.toLowerCase();
if (os.startsWith("windows")) {
return OperatingSystem.Windows;
}
else if (os.startsWith("linux")) {
return OperatingSystem.Linux;
}
else if (os.startsWith("mac os")) {
return OperatingSystem.MacOS;
}
else if (os.startsWith("solaris") || os.startsWith("sunos")) {
return OperatingSystem.Solaris;
}
return OperatingSystem.Unknown;
}
*/
// TODO: We could actually have more than one resource for each lib...
private static String getResourceFor(String pLibrary) {
Iterator<NativeResourceSPI> providers = sRegistry.providers(pLibrary);
while (providers.hasNext()) {
NativeResourceSPI resourceSPI = providers.next();
try {
return resourceSPI.getClassPathResource(Platform.get());
}
catch (Throwable t) {
// Dergister and try next
sRegistry.deregister(resourceSPI);
}
}
return null;
}
/**
* Loads a native library.
*
* @param pLibrary name of the library
*
* @throws UnsatisfiedLinkError
*/
public static void loadLibrary(String pLibrary) {
loadLibrary0(pLibrary, null, null);
}
/**
* Loads a native library.
*
* @param pLibrary name of the library
* @param pLoader the class loader to use
*
* @throws UnsatisfiedLinkError
*/
public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
loadLibrary0(pLibrary, null, pLoader);
}
/**
* Loads a native library.
*
* @param pLibrary name of the library
* @param pResource name of the resource
* @param pLoader the class loader to use
*
* @throws UnsatisfiedLinkError
*/
static void loadLibrary0(String pLibrary, String pResource, ClassLoader pLoader) {
if (pLibrary == null) {
throw new IllegalArgumentException("library == null");
}
// Try loading normal way
UnsatisfiedLinkError unsatisfied;
try {
System.loadLibrary(pLibrary);
return;
}
catch (UnsatisfiedLinkError err) {
// Ignore
unsatisfied = err;
}
final ClassLoader loader = pLoader != null ? pLoader : Thread.currentThread().getContextClassLoader();
final String resource = pResource != null ? pResource : getResourceFor(pLibrary);
// TODO: resource may be null, and that MIGHT be okay, IFF the resource
// is allready unpacked to the user dir... However, we then need another
// way to resolve the library extension...
// Right now we just fail in a predictable way (no NPE)!
if (resource == null) {
throw unsatisfied;
}
// Default to load/store from user.home
File dir = new File(System.getProperty("user.home") + "/.twelvemonkeys/lib");
dir.mkdirs();
//File libraryFile = new File(dir.getAbsolutePath(), pLibrary + LIBRARY_EXTENSION);
File libraryFile = new File(dir.getAbsolutePath(), pLibrary + "." + FileUtil.getExtension(resource));
if (!libraryFile.exists()) {
try {
extractToUserDir(resource, libraryFile, loader);
}
catch (IOException ioe) {
UnsatisfiedLinkError err = new UnsatisfiedLinkError("Unable to extract resource to dir: " + libraryFile.getAbsolutePath());
err.initCause(ioe);
throw err;
}
}
// Try to load the library from the file we just wrote
System.load(libraryFile.getAbsolutePath());
}
private static void extractToUserDir(String pResource, File pLibraryFile, ClassLoader pLoader) throws IOException {
// Get resource from classpath
InputStream in = pLoader.getResourceAsStream(pResource);
if (in == null) {
throw new FileNotFoundException("Unable to locate classpath resource: " + pResource);
}
// Write to file in user dir
FileOutputStream fileOut = null;
try {
fileOut = new FileOutputStream(pLibraryFile);
byte[] tmp = new byte[1024];
// copy the contents of our resource out to the destination
// dir 1K at a time. 1K may seem arbitrary at first, but today
// is a Tuesday, so it makes perfect sense.
int bytesRead = in.read(tmp);
while (bytesRead != -1) {
fileOut.write(tmp, 0, bytesRead);
bytesRead = in.read(tmp);
}
}
finally {
FileUtil.close(fileOut);
FileUtil.close(in);
}
}
// TODO: Validate OS names?
// Windows
// Linux
// Solaris
// Mac OS (OSX+)
// Generic Unix?
// Others?
// TODO: OSes that support different architectures might require different
// resources for each architecture.. Need a namespace/flavour system
// TODO: 32 bit/64 bit issues?
// Eg: Windows, Windows/32, Windows/64, Windows/Intel/64?
// Solaris/Sparc, Solaris/Intel/64
// MacOS/PowerPC, MacOS/Intel
/*
public static class NativeResource {
private Map mMap;
public NativeResource(String[] pOSNames, String[] pReourceNames) {
if (pOSNames == null) {
throw new IllegalArgumentException("osNames == null");
}
if (pReourceNames == null) {
throw new IllegalArgumentException("resourceNames == null");
}
if (pOSNames.length != pReourceNames.length) {
throw new IllegalArgumentException("osNames.length != resourceNames.length");
}
Map map = new HashMap();
for (int i = 0; i < pOSNames.length; i++) {
map.put(pOSNames[i], pReourceNames[i]);
}
mMap = Collections.unmodifiableMap(map);
}
public NativeResource(Map pMap) {
if (pMap == null) {
throw new IllegalArgumentException("map == null");
}
Map map = new HashMap(pMap);
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
if (!(entry.getKey() instanceof String && entry.getValue() instanceof String)) {
throw new IllegalArgumentException("map contains non-string entries: " + entry);
}
}
mMap = Collections.unmodifiableMap(map);
}
protected NativeResource() {
}
public final String resourceForCurrentOS() {
throw new UnsupportedOperationException();
}
protected String getResourceName(String pOSName) {
return (String) mMap.get(pOSName);
}
}
*/
private static class NativeResourceRegistry extends ServiceRegistry {
public NativeResourceRegistry() {
super(Arrays.asList(NativeResourceSPI.class).iterator());
registerApplicationClasspathSPIs();
}
Iterator<NativeResourceSPI> providers(String pNativeResource) {
return new FilterIterator<NativeResourceSPI>(providers(NativeResourceSPI.class),
new NameFilter(pNativeResource));
}
}
private static class NameFilter implements FilterIterator.Filter<NativeResourceSPI> {
private final String mName;
NameFilter(String pName) {
if (pName == null) {
throw new IllegalArgumentException("name == null");
}
mName = pName;
}
public boolean accept(NativeResourceSPI pElement) {
return mName.equals(pElement.getResourceName());
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.lang;
/**
* Abstract base class for native reource providers to iplement.
* <p/>
*
* @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/lang/NativeResourceSPI.java#1 $
*/
public abstract class NativeResourceSPI {
private final String mResourceName;
/**
* Creates a {@code NativeResourceSPI} with the given name.
*
* The name will typically be a short string, with the common name of the
* library that is provided, like "JMagick", "JOGL" or similar.
*
* @param pResourceName name of the resource (native library) provided by
* this SPI.
*
* @throws IllegalArgumentException if {@code pResourceName == null}
*/
protected NativeResourceSPI(String pResourceName) {
if (pResourceName == null) {
throw new IllegalArgumentException("resourceName == null");
}
mResourceName = pResourceName;
}
/**
* Returns the name of the resource (native library) provided by this SPI.
*
* The name will typically be a short string, with the common name of the
* library that is provided, like "JMagick", "JOGL" or similar.
* <p/>
* NOTE: This method is intended for the SPI framework, and should not be
* invoked by client code.
*
* @return the name of the resource provided by this SPI
*/
public final String getResourceName() {
return mResourceName;
}
/**
* Returns the path to the classpath resource that is suited for the given
* runtime configuration.
* <p/>
* In the common case, the {@code pPlatform} parameter is
* normalized from the values found in
* {@code System.getProperty("os.name")} and
* {@code System.getProperty("os.arch")}.
* For unknown operating systems and architectures, {@code toString()} on
* the platforms's properties will return the the same value as these properties.
* <p/>
* NOTE: This method is intended for the SPI framework, and should not be
* invoked by client code.
*
* @param pPlatform the current platform
* @return a {@code String} containing the path to a classpath resource or
* {@code null} if no resource is available.
*
* @see com.twelvemonkeys.lang.Platform.OperatingSystem
* @see com.twelvemonkeys.lang.Platform.Architecture
* @see System#getProperties()
*/
public abstract String getClassPathResource(final Platform pPlatform);
}

View File

@@ -0,0 +1,244 @@
/*
* 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.lang;
/**
* Platform
*
* @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/Platform.java#1 $
*/
public final class Platform {
/**
* Normalized operating system constant
*/
final OperatingSystem mOS;
/**
* Unormalized operating system version constant (for completeness)
*/
final String mVersion;
/**
* Normalized system architecture constant
*/
final Architecture mArchitecture;
static final private Platform INSTANCE = new Platform();
private Platform() {
mOS = normalizeOperatingSystem();
mVersion = System.getProperty("os.version");
mArchitecture = normalizeArchitecture(mOS);
}
private static OperatingSystem normalizeOperatingSystem() {
String os = System.getProperty("os.name");
if (os == null) {
throw new IllegalStateException("System property \"os.name\" == null");
}
os = os.toLowerCase();
if (os.startsWith("windows")) {
return OperatingSystem.Windows;
}
else if (os.startsWith("linux")) {
return OperatingSystem.Linux;
}
else if (os.startsWith("mac os")) {
return OperatingSystem.MacOS;
}
else if (os.startsWith("solaris") || os.startsWith("sunos")) {
return OperatingSystem.Solaris;
}
return OperatingSystem.Unknown;
}
private static Architecture normalizeArchitecture(final OperatingSystem pOsName) {
String arch = System.getProperty("os.arch");
if (arch == null) {
throw new IllegalStateException("System property \"os.arch\" == null");
}
arch = arch.toLowerCase();
if (pOsName == OperatingSystem.Windows
&& (arch.startsWith("x86") || arch.startsWith("i386"))) {
return Architecture.X86;
// TODO: 64 bit
}
else if (pOsName == OperatingSystem.Linux) {
if (arch.startsWith("x86") || arch.startsWith("i386")) {
return Architecture.I386;
}
else if (arch.startsWith("i686")) {
return Architecture.I686;
}
// TODO: More Linux options?
// TODO: 64 bit
}
else if (pOsName == OperatingSystem.MacOS) {
if (arch.startsWith("power") || arch.startsWith("ppc")) {
return Architecture.PPC;
}
else if (arch.startsWith("i386")) {
return Architecture.I386;
}
}
else if (pOsName == OperatingSystem.Solaris) {
if (arch.startsWith("sparc")) {
return Architecture.SPARC;
}
if (arch.startsWith("x86")) {
// TODO: Should we use i386 as Linux and Mac does?
return Architecture.X86;
}
// TODO: 64 bit
}
return Architecture.Unknown;
}
/**
* Returns the current {@code Platform}.
* @return the current {@code Platform}.
*/
public static Platform get() {
return INSTANCE;
}
/**
* @return this platform's OS.
*/
public OperatingSystem getOS() {
return mOS;
}
/**
* @return this platform's OS version.
*/
public String getVersion() {
return mVersion;
}
/**
* @return this platform's architecture.
*/
public Architecture getArchitecture() {
return mArchitecture;
}
/**
* Shorthand for {@code Platform.get().getOS()}.
* @return the current {@code OperatingSystem}.
*/
public static OperatingSystem os() {
return INSTANCE.mOS;
}
/**
* Shorthand for {@code Platform.get().getVersion()}.
* @return the current OS version.
*/
public static String version() {
return INSTANCE.mVersion;
}
/**
* Shorthand for {@code Platform.get().getArchitecture()}.
* @return the current {@code Architecture}.
*/
public static Architecture arch() {
return INSTANCE.mArchitecture;
}
/**
* Enumeration of common System {@code Architecture}s.
* <p/>
* For {@link #Unknown unknown architectures}, {@code toString()} will return
* the the same value as {@code System.getProperty("os.arch")}.
*
* @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/lang/Platform.java#1 $
*/
public static enum Architecture {
X86("x86"),
I386("i386"),
I686("i686"),
PPC("ppc"),
SPARC("sparc"),
Unknown(System.getProperty("os.arch"));
final String mName;// for debug only
private Architecture(String pName) {
mName = pName;
}
public String toString() {
return mName;
}
}
/**
* Enumeration of common {@code OperatingSystem}s.
* <p/>
* For {@link #Unknown unknown operating systems}, {@code getName()} will return
* the the same value as {@code System.getProperty("os.name")}.
*
* @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/lang/Platform.java#1 $
*/
public static enum OperatingSystem {
Windows("Windows", "win"),
Linux("Linux", "lnx"),
Solaris("Solaris", "sun"),
MacOS("Mac OS", "osx"),
Unknown(System.getProperty("os.name"), "");
final String mId;
final String mName;// for debug only
private OperatingSystem(String pName, String pId) {
mName = pName;
mId = pId;
}
public String getName() {
return mName;
}
public String toString() {
return mId;
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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.lang;
/**
* Util class for various reflection-based operations.
* <p/>
* <em>NOTE: This class is not considered part of the public API and may be
* changed without notice</em>
*
* @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/lang/ReflectUtil.java#1 $
*/
public final class ReflectUtil {
/** Don't allow instances */
private ReflectUtil() {}
/**
* Returns the primitive type for the given wrapper type.
*
* @param pType the wrapper type
*
* @return the primitive type
*
* @throws IllegalArgumentException if <tt>pType</tt> is not a primitive
* wrapper
*/
public static Class unwrapType(Class pType) {
if (pType == Boolean.class) {
return Boolean.TYPE;
}
else if (pType == Byte.class) {
return Byte.TYPE;
}
else if (pType == Character.class) {
return Character.TYPE;
}
else if (pType == Double.class) {
return Double.TYPE;
}
else if (pType == Float.class) {
return Float.TYPE;
}
else if (pType == Integer.class) {
return Integer.TYPE;
}
else if (pType == Long.class) {
return Long.TYPE;
}
else if (pType == Short.class) {
return Short.TYPE;
}
throw new IllegalArgumentException("Not a primitive wrapper: " + pType);
}
/**
* Returns the wrapper type for the given primitive type.
*
* @param pType the primitive tpye
*
* @return the wrapper type
*
* @throws IllegalArgumentException if <tt>pType</tt> is not a primitive
* type
*/
public static Class wrapType(Class pType) {
if (pType == Boolean.TYPE) {
return Boolean.class;
}
else if (pType == Byte.TYPE) {
return Byte.class;
}
else if (pType == Character.TYPE) {
return Character.class;
}
else if (pType == Double.TYPE) {
return Double.class;
}
else if (pType == Float.TYPE) {
return Float.class;
}
else if (pType == Integer.TYPE) {
return Integer.class;
}
else if (pType == Long.TYPE) {
return Long.class;
}
else if (pType == Short.TYPE) {
return Short.class;
}
throw new IllegalArgumentException("Not a primitive type: " + pType);
}
/**
* Returns <tt>true</tt> if the given type is a primitive wrapper.
*
* @param pType
*
* @return <tt>true</tt> if the given type is a primitive wrapper, otherwise
* <tt>false</tt>
*/
public static boolean isPrimitiveWrapper(Class pType) {
return pType == Boolean.class || pType == Byte.class
|| pType == Character.class || pType == Double.class
|| pType == Float.class || pType == Integer.class
|| pType == Long.class || pType == Short.class;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,693 @@
/*
* 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.lang;
import com.twelvemonkeys.util.XMLProperties;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* A utility class with some useful system-related functions.
* <p/>
* <em>NOTE: This class is not considered part of the public API and may be
* changed without notice</em>
*
* @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/SystemUtil.java#3 $
*
*/
public final class SystemUtil {
/** {@code ".xml"} */
public static String XML_PROPERTIES = ".xml";
/** {@code ".properties"} */
public static String STD_PROPERTIES = ".properties";
// Disallow creating objects of this type
private SystemUtil() {
}
/** This class marks an inputstream as containing XML, does nothing */
private static class XMLPropertiesInputStream extends FilterInputStream {
public XMLPropertiesInputStream(InputStream pIS) {
super(pIS);
}
}
/**
* Gets the named resource as a stream from the given Class' Classoader.
* If the pGuessSuffix parameter is true, the method will try to append
* typical properties file suffixes, such as ".properties" or ".xml".
*
* @param pClassLoader the class loader to use
* @param pName name of the resource
* @param pGuessSuffix guess suffix
*
* @return an input stream reading from the resource
*/
private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName,
boolean pGuessSuffix) {
InputStream is;
if (!pGuessSuffix) {
is = pClassLoader.getResourceAsStream(pName);
// If XML, wrap stream
if (is != null && pName.endsWith(XML_PROPERTIES)) {
is = new XMLPropertiesInputStream(is);
}
}
else {
// Try normal properties
is = pClassLoader.getResourceAsStream(pName + STD_PROPERTIES);
// Try XML
if (is == null) {
is = pClassLoader.getResourceAsStream(pName + XML_PROPERTIES);
// Wrap stream
if (is != null) {
is = new XMLPropertiesInputStream(is);
}
}
}
// Return stream
return is;
}
/**
* Gets the named file as a stream from the current directory.
* If the pGuessSuffix parameter is true, the method will try to append
* typical properties file suffixes, such as ".properties" or ".xml".
*
* @param pName name of the resource
* @param pGuessSuffix guess suffix
*
* @return an input stream reading from the resource
*/
private static InputStream getFileAsStream(String pName,
boolean pGuessSuffix) {
InputStream is = null;
File propertiesFile;
try {
if (!pGuessSuffix) {
// Get file
propertiesFile = new File(pName);
if (propertiesFile.exists()) {
is = new FileInputStream(propertiesFile);
// If XML, wrap stream
if (pName.endsWith(XML_PROPERTIES)) {
is = new XMLPropertiesInputStream(is);
}
}
}
else {
// Try normal properties
propertiesFile = new File(pName + STD_PROPERTIES);
if (propertiesFile.exists()) {
is = new FileInputStream(propertiesFile);
}
else {
// Try XML
propertiesFile = new File(pName + XML_PROPERTIES);
if (propertiesFile.exists()) {
// Wrap stream
is = new XMLPropertiesInputStream(new FileInputStream(propertiesFile));
}
}
}
}
catch (FileNotFoundException fnf) {
// Should not happen, as we always test that the file .exists()
// before creating InputStream
// assert false;
}
return is;
}
/**
* Utility method for loading a named properties-file for a class.
* <P>
* The properties-file is loaded through either:
* <OL>
* <LI>The given class' class loader (from classpath)</LI>
* <LI>Or, the system class loader (from classpath)</LI>
* <LI>Or, if it cannot be found in the classpath, an attempt to read from
* the current directory (or full path if given).</LI>
* </OL>
* <P>
* Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
* are supported (XML-properties must have ".xml" as its file extension).
*
* @param pClass The class to load properties for. If this parameter is
* {@code null}, the method will work exactly as
* {@link #loadProperties(String)}
* @param pName The name of the properties-file. If this parameter is
* {@code null}, the method will work exactly as
* {@link #loadProperties(Class)}
*
* @return A Properties mapping read from the given file or for the given
* class. <!--If no properties-file was found, an empty Properties object is
* returned.-->
*
* @throws NullPointerException if both {@code pName} and
* {@code pClass} paramters are {@code null}
* @throws IOException if an error occurs during load.
* @throws FileNotFoundException if no properties-file could be found.
*
* @see #loadProperties(String)
* @see #loadProperties(Class)
* @see java.lang.ClassLoader#getResourceAsStream
* @see java.lang.ClassLoader#getSystemResourceAsStream
*
* @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?
*/
public static Properties loadProperties(Class pClass, String pName)
throws IOException
{
// Convert to name the classloader understands
String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/');
// Should we try to guess suffix?
boolean guessSuffix = (pName == null || pName.indexOf('.') < 0);
InputStream is;
// TODO: WHAT IF MULTIPLE RESOURCES EXISTS?!
// Try loading resource through the current class' classloader
if (pClass != null
&& (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) {
//&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) {
// Nothing to do
//System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties")
// + " from Class' ClassLoader");
}
// If that fails, try the system classloader
else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(),
name, guessSuffix)) != null) {
//else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) {
// Nothing to do
//System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties")
// + " from System ClassLoader");
}
// All failed, try loading from file
else if ((is = getFileAsStream(name, guessSuffix)) != null) {
//System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties")
// + " from System ClassLoader");
}
else {
if (guessSuffix) {
// TODO: file extension iterator or something...
throw new FileNotFoundException(name + ".properties or " + name + ".xml");
}
else {
throw new FileNotFoundException(name);
}
}
// We have inputstream now, load...
try {
return loadProperties(is);
}
finally {
// NOTE: If is == null, a FileNotFoundException must have been thrown above
try {
is.close();
}
catch (IOException ioe) {
// Not critical...
}
}
}
/**
* Utility method for loading a properties-file for a given class.
* The properties are searched for on the form
* "com/package/ClassName.properties" or
* "com/package/ClassName.xml".
* <P>
* The properties-file is loaded through either:
* <OL>
* <LI>The given class' class loader (from classpath)</LI>
* <LI>Or, the system class loader (from classpath)</LI>
* <LI>Or, if it cannot be found in the classpath, an attempt to read from
* the current directory (or full path if given).</LI>
* </OL>
* <P>
* Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
* are supported (XML-properties must have ".xml" as its file extension).
*
* @param pClass The class to load properties for
* @return A Properties mapping for the given class. <!--If no properties-
* file was found, an empty Properties object is returned.-->
*
* @throws NullPointerException if the {@code pClass} paramters is
* {@code null}
* @throws IOException if an error occurs during load.
* @throws FileNotFoundException if no properties-file could be found.
*
* @see #loadProperties(String)
* @see #loadProperties(Class, String)
* @see java.lang.ClassLoader#getResourceAsStream
* @see java.lang.ClassLoader#getSystemResourceAsStream
*
*/
public static Properties loadProperties(Class pClass) throws IOException {
return loadProperties(pClass, null);
}
/**
* Utility method for loading a named properties-file.
* <P>
* The properties-file is loaded through either:
* <OL>
* <LI>The system class loader (from classpath)</LI>
* <LI>Or, if it cannot be found in the classpath, an attempt to read from
* the current directory.</LI>
* </OL>
* <P>
* Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
* are supported (XML-properties must have ".xml" as its file extension).
*
* @param pName The name of the properties-file.
* @return A Properties mapping read from the given file. <!--If no properties-
* file was found, an empty Properties object is returned.-->
*
* @throws NullPointerException if the {@code pName} paramters is
* {@code null}
* @throws IOException if an error occurs during load.
* @throws FileNotFoundException if no properties-file could be found.
*
* @see #loadProperties(Class)
* @see #loadProperties(Class, String)
* @see java.lang.ClassLoader#getSystemResourceAsStream
*
*/
public static Properties loadProperties(String pName) throws IOException {
return loadProperties(null, pName);
}
/*
* Utility method for loading a properties-file.
* <P>
* The properties files may also be contained in a zip/jar-file named
* by the {@code com.twelvemonkeys.util.Config} system property (use "java -D"
* to override). Default is "config.zip" in the current directory.
*
* @param pName The name of the file to loaded
* @return A Properties mapping for the given class. If no properties-
* file was found, an empty Properties object is returned.
*
*/
/*
public static Properties loadProperties(String pName) throws IOException {
// Use XML?
boolean useXML = pName.endsWith(XML_PROPERTIES) ? true : false;
InputStream is = null;
File file = new File(pName);
String configName = System.getProperty("com.twelvemonkeys.util.Config");
File configArchive = new File(!StringUtil.isEmpty(configName)
? configName : DEFAULT_CONFIG);
// Get input stream to the file containing the properties
if (file.exists()) {
// Try reading from file, normal way
is = new FileInputStream(file);
}
else if (configArchive.exists()) {
// Try reading properties from zip-file
ZipFile zip = new ZipFile(configArchive);
ZipEntry ze = zip.getEntry(pName);
if (ze != null) {
is = zip.getInputStream(ze);
}
}
// Do the loading
try {
// Load the properties
return loadProperties(is, useXML);
}
finally {
// Try closing the archive to free resources
if (is != null) {
try {
is.close();
}
catch (IOException ioe) {
// Not critical...
}
}
}
}
*/
/**
* Returns a Properties, loaded from the given inputstream. If the given
* inputstream is null, then an empty Properties object is returned.
*
* @param pInput the inputstream to read from
*
* @return a Properties object read from the given stream, or an empty
* Properties mapping, if the stream is null.
*
* @throws IOException if an error occurred when reading from the input
* stream.
*
*/
private static Properties loadProperties(InputStream pInput)
throws IOException {
if (pInput == null) {
throw new IllegalArgumentException("InputStream == null!");
}
Properties mapping;
if (pInput instanceof XMLPropertiesInputStream) {
mapping = new XMLProperties();
}
else {
mapping = new Properties();
}
// Load the properties
mapping.load(pInput);
return mapping;
}
@SuppressWarnings({"SuspiciousSystemArraycopy"})
public static Object clone(final Cloneable pObject) throws CloneNotSupportedException {
if (pObject == null) {
return null; // Null is clonable.. Easy. ;-)
}
// All arrays does have a clone method, but it's invisible for reflection...
// By luck, multi-dimensional primitive arrays are instances of Object[]
if (pObject instanceof Object[]) {
return ((Object[]) pObject).clone();
}
else if (pObject.getClass().isArray()) {
// One-dimensional primitive array, cloned manually
int lenght = Array.getLength(pObject);
Object clone = Array.newInstance(pObject.getClass().getComponentType(), lenght);
System.arraycopy(pObject, 0, clone, 0, lenght);
return clone;
}
try {
// Find the clone method
Method clone = null;
Class clazz = pObject.getClass();
do {
try {
clone = clazz.getDeclaredMethod("clone");
break; // Found, or throws exception above
}
catch (NoSuchMethodException ignore) {
// Ignore
}
}
while ((clazz = clazz.getSuperclass()) != null);
// NOTE: This should never happen
if (clone == null) {
throw new CloneNotSupportedException(pObject.getClass().getName());
}
// Override access if needed
if (!clone.isAccessible()) {
clone.setAccessible(true);
}
// Invoke clone method on original object
return clone.invoke(pObject);
}
catch (SecurityException e) {
CloneNotSupportedException cns = new CloneNotSupportedException(pObject.getClass().getName());
cns.initCause(e);
throw cns;
}
catch (IllegalAccessException e) {
throw new CloneNotSupportedException(pObject.getClass().getName());
}
catch (InvocationTargetException e) {
if (e.getTargetException() instanceof CloneNotSupportedException) {
throw (CloneNotSupportedException) e.getTargetException();
}
else if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
else if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
throw new CloneNotSupportedException(pObject.getClass().getName());
}
}
public static void loadLibrary(String pLibrary) {
NativeLoader.loadLibrary(pLibrary);
}
public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
NativeLoader.loadLibrary(pLibrary, pLoader);
}
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("clone: " + args.clone().length + " (" + args.length + ")");
System.out.println("copy: " + ((String[]) clone(args)).length + " (" + args.length + ")");
int[] ints = {1,2,3};
int[] copies = (int[]) clone(ints);
System.out.println("Copies: " + copies.length + " (" + ints.length + ")");
int[][] intsToo = {{1}, {2,3}, {4,5,6}};
int[][] copiesToo = (int[][]) clone(intsToo);
System.out.println("Copies: " + copiesToo.length + " (" + intsToo.length + ")");
System.out.println("Copies0: " + copiesToo[0].length + " (" + intsToo[0].length + ")");
System.out.println("Copies1: " + copiesToo[1].length + " (" + intsToo[1].length + ")");
System.out.println("Copies2: " + copiesToo[2].length + " (" + intsToo[2].length + ")");
Map<String, String> map = new HashMap<String, String>();
for (String arg : args) {
map.put(arg, arg);
}
Map copy = (Map) clone((Cloneable) map);
System.out.println("Map : " + map);
System.out.println("Copy: " + copy);
/*
SecurityManager sm = System.getSecurityManager();
try {
System.setSecurityManager(new SecurityManager() {
public void checkPermission(Permission perm) {
if (perm.getName().equals("suppressAccessChecks")) {
throw new SecurityException();
}
//super.checkPermission(perm);
}
});
*/
Cloneable cloneable = new Cloneable() {}; // No public clone method
Cloneable clone = (Cloneable) clone(cloneable);
System.out.println("cloneable: " + cloneable);
System.out.println("clone: " + clone);
/*
}
finally {
System.setSecurityManager(sm);
}
*/
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
return null;
}
}, AccessController.getContext());
//String string = args.length > 0 ? args[0] : "jaffa";
//clone(string);
}
/**
* Tests if a named class is generally available.
* If a class is considered available, a call to
* {@code Class.forName(pClassName)} will not result in an exception.
*
* @param pClassName the class name to test
* @return {@code true} if available
*/
public static boolean isClassAvailable(String pClassName) {
return isClassAvailable(pClassName, (ClassLoader) null);
}
/**
* Tests if a named class is available from another class.
* If a class is considered available, a call to
* {@code Class.forName(pClassName, true, pFromClass.getClassLoader())}
* will not result in an exception.
*
* @param pClassName the class name to test
* @param pFromClass the class to test from
* @return {@code true} if available
*/
public static boolean isClassAvailable(String pClassName, Class pFromClass) {
ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
return isClassAvailable(pClassName, loader);
}
private static boolean isClassAvailable(String pClassName, ClassLoader pLoader) {
try {
// TODO: Sometimes init is not needed, but need to find a way to know...
getClass(pClassName, true, pLoader);
return true;
}
catch (SecurityException ignore) {
// Ignore
}
catch (ClassNotFoundException ignore) {
// Ignore
}
catch (LinkageError ignore) {
// Ignore
}
return false;
}
public static boolean isFieldAvailable(final String pClassName, final String pFieldName) {
return isFieldAvailable(pClassName, pFieldName, (ClassLoader) null);
}
public static boolean isFieldAvailable(final String pClassName, final String pFieldName, final Class pFromClass) {
ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
return isFieldAvailable(pClassName, pFieldName, loader);
}
private static boolean isFieldAvailable(final String pClassName, final String pFieldName, final ClassLoader pLoader) {
try {
Class cl = getClass(pClassName, false, pLoader);
Field field = cl.getField(pFieldName);
if (field != null) {
return true;
}
}
catch (ClassNotFoundException ignore) {
// Ignore
}
catch (LinkageError ignore) {
// Ignore
}
catch (NoSuchFieldException ignore) {
// Ignore
}
return false;
}
public static boolean isMethodAvailable(String pClassName, String pMethodName) {
// Finds void only
return isMethodAvailable(pClassName, pMethodName, null, (ClassLoader) null);
}
public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams) {
return isMethodAvailable(pClassName, pMethodName, pParams, (ClassLoader) null);
}
public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, Class pFromClass) {
ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
return isMethodAvailable(pClassName, pMethodName, pParams, loader);
}
private static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, ClassLoader pLoader) {
try {
Class cl = getClass(pClassName, false, pLoader);
Method method = cl.getMethod(pMethodName, pParams);
if (method != null) {
return true;
}
}
catch (ClassNotFoundException ignore) {
// Ignore
}
catch (LinkageError ignore) {
// Ignore
}
catch (NoSuchMethodException ignore) {
// Ignore
}
return false;
}
private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException {
// NOTE: We need the context classloader, as SystemUtil's
// classloader may have a totally different classloader than
// the original caller class (as in Class.forName(cn, false, null)).
ClassLoader loader = pLoader != null ? pLoader :
Thread.currentThread().getContextClassLoader();
return Class.forName(pClassName, pInitialize, loader);
}
}

View File

@@ -0,0 +1,124 @@
package com.twelvemonkeys.lang;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/**
* Kind of like {@code org.apache.commons.lang.Validate}. Just smarter. ;-)
* <p/>
* Uses type parameterized return values, thus making it possible to check
* constructor arguments before
* they are passed on to {@code super} or {@code this} type constructors.
*
* @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/Validate.java#1 $
*/
public final class Validate {
private static final String UNSPECIFIED_PARAM_NAME = "method parameter";
private Validate() {}
// Not null...
public static <T> T notNull(final T pParameter) {
return notNull(pParameter, null);
}
public static <T> T notNull(final T pParameter, final String pParamName) {
if (pParameter == null) {
throw new IllegalArgumentException(String.format("%s may not be null", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
// Not empty...
public static <T extends CharSequence> T notEmpty(final T pParameter) {
return notEmpty(pParameter, null);
}
public static <T extends CharSequence> T notEmpty(final T pParameter, final String pParamName) {
if (pParameter == null || pParameter.length() == 0) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
public static <T> T[] notEmpty(final T[] pParameter) {
return notEmpty(pParameter, null);
}
public static <T> T[] notEmpty(final T[] pParameter, final String pParamName) {
if (pParameter == null || pParameter.length == 0) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
public static <T> Collection<T> notEmpty(final Collection<T> pParameter) {
return notEmpty(pParameter, null);
}
public static <T> Collection<T> notEmpty(final Collection<T> pParameter, final String pParamName) {
if (pParameter == null || pParameter.isEmpty()) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
public static <K, V> Map<K, V> notEmpty(final Map<K, V> pParameter) {
return notEmpty(pParameter, null);
}
public static <K, V> Map<K, V> notEmpty(final Map<K, V> pParameter, final String pParamName) {
if (pParameter == null || pParameter.isEmpty()) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
// No null elements
public static <T> T[] noNullElements(final T[] pParameter) {
return noNullElements(pParameter, null);
}
public static <T> T[] noNullElements(final T[] pParameter, final String pParamName) {
noNullElements(Arrays.asList(pParameter), pParamName);
return pParameter;
}
public static <T> Collection<T> noNullElements(final Collection<T> pParameter) {
return noNullElements(pParameter, null);
}
public static <T> Collection<T> noNullElements(final Collection<T> pParameter, final String pParamName) {
for (T element : pParameter) {
if (element == null) {
throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
}
return pParameter;
}
public static <K, V> Map<K, V> noNullElements(final Map<K, V> pParameter) {
return noNullElements(pParameter, null);
}
public static <K, V> Map<K, V> noNullElements(final Map<K, V> pParameter, final String pParamName) {
for (V element : pParameter.values()) {
if (element == null) {
throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
}
return pParameter;
}
}

View File

@@ -0,0 +1,7 @@
<HTML>
<BODY>
Contains utils/helpers for classes in {@code java.lang}.
</BODY>
</HTML>

View File

@@ -0,0 +1,47 @@
/*
* 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.net;
import java.net.*;
/**
* Interface for filtering Authenticator requests, used by the
* SimpleAuthenticator.
*
* @see SimpleAuthenticator
* @see java.net.Authenticator
*
* @author Harald Kuhr (haraldk@iconmedialab.no),
* @version 1.0
*/
public interface AuthenticatorFilter {
public boolean accept(InetAddress pAddress, int pPort, String pProtocol,
String pPrompt, String pScheme);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,312 @@
/*
* 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.net;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.SystemUtil;
import java.io.IOException;
import java.util.*;
/**
* Contains mappings from file extension to mime-types and from mime-type to file-types.
* <p/>
*
* @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/net/MIMEUtil.java#5 $
*
* @see <A href="http://www.iana.org/assignments/media-types/">MIME Media Types</A>
*/
public final class MIMEUtil {
// TODO: Piggy-back on the mappings form the JRE? (1.6 comes with javax.activation)
// TODO: Piggy-back on mappings from javax.activation?
// See: http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/activation/MimetypesFileTypeMap.html
// See: http://java.sun.com/javase/6/docs/api/javax/activation/MimetypesFileTypeMap.html
// TODO: Use the format (and lookup) specified by the above URLs
// TODO: Allow 3rd party to add mappings? Will need application context support to do it safe.. :-P
private static Map<String, List<String>> sExtToMIME = new HashMap<String, List<String>>();
private static Map<String, List<String>> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME);
private static Map<String, List<String>> sMIMEToExt = new HashMap<String, List<String>>();
private static Map<String, List<String>> sUnmodifiableMIMEToExt = Collections.unmodifiableMap(sMIMEToExt);
static {
// Load mapping for MIMEUtil
try {
Properties mappings = SystemUtil.loadProperties(MIMEUtil.class);
for (Map.Entry entry : mappings.entrySet()) {
// Convert and break up extensions and mimeTypes
String extStr = StringUtil.toLowerCase((String) entry.getKey());
List<String> extensions =
Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, ")));
String typeStr = StringUtil.toLowerCase((String) entry.getValue());
List<String> mimeTypes =
Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(typeStr, ";, ")));
// TODO: Handle duplicates in MIME to extension mapping, like
// xhtml=application/xhtml+xml;application/xml
// xml=text/xml;application/xml
// Populate normal and reverse MIME-mappings
for (String extension : extensions) {
sExtToMIME.put(extension, mimeTypes);
}
for (String mimeType : mimeTypes) {
sMIMEToExt.put(mimeType, extensions);
}
}
}
catch (IOException e) {
System.err.println("Could not read properties for MIMEUtil: " + e.getMessage());
e.printStackTrace();
}
}
// Disallow construction
private MIMEUtil() {
}
/**
* Returns the default MIME type for the given file extension.
*
* @param pFileExt the file extension
*
* @return a {@code String} containing the MIME type, or {@code null} if
* there are no known MIME types for the given file extension.
*/
public static String getMIMEType(final String pFileExt) {
List<String> types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt));
return (types == null || types.isEmpty()) ? null : types.get(0);
}
/**
* Returns all MIME types for the given file extension.
*
* @param pFileExt the file extension
*
* @return a {@link List} of {@code String}s containing the MIME types, or an empty
* list, if there are no known MIME types for the given file extension.
*/
public static List<String> getMIMETypes(final String pFileExt) {
List<String> types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt));
return maskNull(types);
}
/**
* Returns an unmodifiabale {@link Map} view of the extension to
* MIME mapping, to use as the default mapping in client applications.
*
* @return an unmodifiabale {@code Map} view of the extension to
* MIME mapping.
*/
public static Map<String, List<String>> getMIMETypeMappings() {
return sUnmodifiableExtToMIME;
}
/**
* Returns the default file extension for the given MIME type.
* Specifying a wildcard type will return {@code null}.
*
* @param pMIME the MIME type
*
* @return a {@code String} containing the file extension, or {@code null}
* if there are no known file extensions for the given MIME type.
*/
public static String getExtension(final String pMIME) {
String mime = bareMIME(StringUtil.toLowerCase(pMIME));
List<String> extensions = sMIMEToExt.get(mime);
return (extensions == null || extensions.isEmpty()) ? null : extensions.get(0);
}
/**
* Returns all file extension for the given MIME type.
* The default extension will be the first in the list.
* Note that no specific order is given for wildcard types (image/*, *&#47;* etc).
*
* @param pMIME the MIME type
*
* @return a {@link List} of {@code String}s containing the MIME types, or an empty
* list, if there are no known file extensions for the given MIME type.
*/
public static List<String> getExtensions(final String pMIME) {
String mime = bareMIME(StringUtil.toLowerCase(pMIME));
if (mime.endsWith("/*")) {
return getExtensionForWildcard(mime);
}
List<String> extensions = sMIMEToExt.get(mime);
return maskNull(extensions);
}
// Gets all extensions for a wildcard MIME type
private static List<String> getExtensionForWildcard(final String pMIME) {
final String family = pMIME.substring(0, pMIME.length() - 1);
Set<String> extensions = new LinkedHashSet<String>();
for (Map.Entry<String, List<String>> mimeToExt : sMIMEToExt.entrySet()) {
if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) {
extensions.addAll(mimeToExt.getValue());
}
}
return Collections.unmodifiableList(new ArrayList<String>(extensions));
}
/**
* Returns an unmodifiabale {@link Map} view of the MIME to
* extension mapping, to use as the default mapping in client applications.
*
* @return an unmodifiabale {@code Map} view of the MIME to
* extension mapping.
*/
public static Map<String, List<String>> getExtensionMappings() {
return sUnmodifiableMIMEToExt;
}
/**
* Tests wehter the type is a subtype of the type family.
*
* @param pTypeFamily the MIME type family ({@code image/*, *&#47;*}, etc)
* @param pType the MIME type
* @return {@code true} if {@code pType} is a subtype of {@code pTypeFamily}, otherwise {@code false}
*/
// TODO: Rename? isSubtype?
// TODO: Make public
static boolean includes(final String pTypeFamily, final String pType) {
// TODO: Handle null in a well-defined way
// - Is null family same as */*?
// - Is null subtype of any family? Subtype of no family?
String type = bareMIME(pType);
return type.equals(pTypeFamily)
|| "*/*".equals(pTypeFamily)
|| pTypeFamily.endsWith("/*") && pTypeFamily.startsWith(type.substring(0, type.indexOf('/')));
}
/**
* Removes any charset or extra info from the mime-type string (anything after a semicolon, {@code ;}, inclusive).
*
* @param pMIME the mime-type string
* @return the bare mime-type
*/
public static String bareMIME(final String pMIME) {
int idx;
if (pMIME != null && (idx = pMIME.indexOf(';')) >= 0) {
return pMIME.substring(0, idx);
}
return pMIME;
}
// Returns the list or empty list if list is null
private static List<String> maskNull(List<String> pTypes) {
return (pTypes == null) ? Collections.<String>emptyList() : pTypes;
}
/**
* For debugging. Prints all known MIME types and file extensions.
*
* @param pArgs command line arguments
*/
public static void main(String[] pArgs) {
if (pArgs.length > 1) {
String type = pArgs[0];
String family = pArgs[1];
boolean incuded = includes(family, type);
System.out.println(
"Mime type family " + family
+ (incuded ? " includes " : " does not include ")
+ "type " + type
);
}
if (pArgs.length > 0) {
String str = pArgs[0];
if (str.indexOf('/') >= 0) {
// MIME
String extension = getExtension(str);
System.out.println("Default extension for MIME type '" + str + "' is "
+ (extension != null ? ": '" + extension + "'" : "unknown") + ".");
System.out.println("All possible: " + getExtensions(str));
}
else {
// EXT
String mimeType = getMIMEType(str);
System.out.println("Default MIME type for extension '" + str + "' is "
+ (mimeType != null ? ": '" + mimeType + "'" : "unknown") + ".");
System.out.println("All possible: " + getMIMETypes(str));
}
return;
}
Set set = sMIMEToExt.keySet();
String[] mimeTypes = new String[set.size()];
int i = 0;
for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) {
String mime = (String) iterator.next();
mimeTypes[i] = mime;
}
Arrays.sort(mimeTypes);
System.out.println("Known MIME types (" + mimeTypes.length + "):");
for (int j = 0; j < mimeTypes.length; j++) {
String mimeType = mimeTypes[j];
if (j != 0) {
System.out.print(", ");
}
System.out.print(mimeType);
}
System.out.println("\n");
set = sExtToMIME.keySet();
String[] extensions = new String[set.size()];
i = 0;
for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) {
String ext = (String) iterator.next();
extensions[i] = ext;
}
Arrays.sort(extensions);
System.out.println("Known file types (" + extensions.length + "):");
for (int j = 0; j < extensions.length; j++) {
String extension = extensions[j];
if (j != 0) {
System.out.print(", ");
}
System.out.print(extension);
}
System.out.println();
}
}

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