mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 03:25:28 -04:00
parent
b9b1a35408
commit
6f9b9bee01
@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.imageio.stream;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStreamImpl;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
|
||||||
|
* and provides greatly improved performance
|
||||||
|
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
|
||||||
|
* for shorter reads, like single byte or bit reads.
|
||||||
|
*/
|
||||||
|
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
|
||||||
|
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
|
||||||
|
private byte[] buffer = byteBuffer.array();
|
||||||
|
private int bufferPos;
|
||||||
|
private int bufferLimit;
|
||||||
|
|
||||||
|
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
|
||||||
|
private final byte[] integralCacheArray = integralCache.array();
|
||||||
|
|
||||||
|
private SeekableByteChannel channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
|
||||||
|
*
|
||||||
|
* @param file a {@code File} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||||
|
* @throws IOException if an I/O error occurs while opening the file.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final File file) throws IOException {
|
||||||
|
this(notNull(file, "file").toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
|
||||||
|
*
|
||||||
|
* @param file a {@code Path} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
|
||||||
|
* @throws IOException if an I/O error occurs while opening the file.
|
||||||
|
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final Path file) throws IOException {
|
||||||
|
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will close the {@code RandomAccessFile}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param file a {@code RandomAccessFile} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final RandomAccessFile file) {
|
||||||
|
// Assumption: Closing the FileChannel will also close its backing RandomAccessFile
|
||||||
|
// (it does in the OpenJDK implementation, and it makes sense, although I can't see this is documented behaviour).
|
||||||
|
this(notNull(file, "file").getChannel());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will close the {@code FileInputStream}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param inputStream a {@code FileInputStream} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
|
||||||
|
// Assumption: Closing the FileChannel will also close its backing FileInputStream (it does in the OpenJDK implementation, although I can't see this is documented).
|
||||||
|
this(notNull(inputStream, "inputStream").getChannel());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will close the {@code SeekableByteChannel}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param channel a {@code SeekableByteChannel} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private boolean fillBuffer() throws IOException {
|
||||||
|
byteBuffer.rewind();
|
||||||
|
int length = channel.read(byteBuffer);
|
||||||
|
bufferPos = 0;
|
||||||
|
bufferLimit = max(length, 0);
|
||||||
|
|
||||||
|
return bufferLimit > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean bufferEmpty() {
|
||||||
|
return bufferPos >= bufferLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setByteOrder(ByteOrder byteOrder) {
|
||||||
|
super.setByteOrder(byteOrder);
|
||||||
|
integralCache.order(byteOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (bufferEmpty() && !fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
streamPos++;
|
||||||
|
|
||||||
|
return buffer[bufferPos++] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
if (bufferEmpty()) {
|
||||||
|
// Bypass buffer if buffer is empty for reads longer than buffer
|
||||||
|
if (length >= buffer.length) {
|
||||||
|
return readDirect(bytes, offset, length);
|
||||||
|
}
|
||||||
|
else if (!fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fromBuffer = readBuffered(bytes, offset, length);
|
||||||
|
|
||||||
|
if (length > fromBuffer) {
|
||||||
|
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
|
||||||
|
// we'll read as much as possible from the buffer, and the rest directly after
|
||||||
|
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
|
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
|
||||||
|
bufferLimit = 0;
|
||||||
|
|
||||||
|
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
|
||||||
|
int read = 0;
|
||||||
|
while (wrapped.hasRemaining()) {
|
||||||
|
int count = channel.read(wrapped);
|
||||||
|
if (count == -1) {
|
||||||
|
if (read == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
read += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos += read;
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBuffered(final byte[] bytes, final int offset, final int length) {
|
||||||
|
// Read as much as possible from buffer
|
||||||
|
int available = Math.min(bufferLimit - bufferPos, length);
|
||||||
|
|
||||||
|
if (available > 0) {
|
||||||
|
System.arraycopy(buffer, bufferPos, bytes, offset, available);
|
||||||
|
bufferPos += available;
|
||||||
|
streamPos += available;
|
||||||
|
}
|
||||||
|
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long length() {
|
||||||
|
// WTF?! This method is allowed to throw IOException in the interface...
|
||||||
|
try {
|
||||||
|
checkClosed();
|
||||||
|
return channel.size();
|
||||||
|
}
|
||||||
|
catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
buffer = null;
|
||||||
|
byteBuffer = null;
|
||||||
|
|
||||||
|
channel.close();
|
||||||
|
channel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to override the readShort(), readInt() and readLong() methods,
|
||||||
|
// because the implementations in ImageInputStreamImpl expects the
|
||||||
|
// read(byte[], int, int) to always read the expected number of bytes,
|
||||||
|
// causing uninitialized values, alignment issues and EOFExceptions at
|
||||||
|
// random places...
|
||||||
|
// Notes:
|
||||||
|
// * readUnsignedXx() is covered by their signed counterparts
|
||||||
|
// * readChar() is covered by readShort()
|
||||||
|
// * readFloat() and readDouble() is covered by readInt() and readLong()
|
||||||
|
// respectively.
|
||||||
|
// * readLong() may be covered by two readInt()s, we'll override to be safe
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short readShort() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 2);
|
||||||
|
|
||||||
|
return integralCache.getShort(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readInt() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 4);
|
||||||
|
|
||||||
|
return integralCache.getInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readLong() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 8);
|
||||||
|
|
||||||
|
return integralCache.getLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long position) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (position < flushedPos) {
|
||||||
|
throw new IndexOutOfBoundsException("position < flushedPos!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
if (streamPos == position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized to not invalidate buffer if new position is within current buffer
|
||||||
|
long newBufferPos = bufferPos + position - streamPos;
|
||||||
|
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
|
||||||
|
bufferPos = (int) newBufferPos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Will invalidate buffer
|
||||||
|
bufferLimit = 0;
|
||||||
|
channel.position(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushBefore(final long pos) throws IOException {
|
||||||
|
super.flushBefore(pos);
|
||||||
|
|
||||||
|
if (channel instanceof MemoryCache) {
|
||||||
|
// In case of memory cache, free up memory
|
||||||
|
((MemoryCache) channel).flushBefore(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,7 @@ import static java.lang.Math.max;
|
|||||||
* {@link File} or {@link RandomAccessFile} can be used as input.
|
* {@link File} or {@link RandomAccessFile} can be used as input.
|
||||||
*
|
*
|
||||||
* @see javax.imageio.stream.FileImageInputStream
|
* @see javax.imageio.stream.FileImageInputStream
|
||||||
|
* @deprecated Use {@link BufferedChannelImageInputStream} instead.
|
||||||
*/
|
*/
|
||||||
// TODO: Create a memory-mapped version?
|
// TODO: Create a memory-mapped version?
|
||||||
// Or not... From java.nio.channels.FileChannel.map:
|
// Or not... From java.nio.channels.FileChannel.map:
|
||||||
@ -57,6 +58,7 @@ import static java.lang.Math.max;
|
|||||||
// the usual {@link #read read} and {@link #write write} methods. From the
|
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||||
// standpoint of performance it is generally only worth mapping relatively
|
// standpoint of performance it is generally only worth mapping relatively
|
||||||
// large files into memory.
|
// large files into memory.
|
||||||
|
@Deprecated
|
||||||
public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
||||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
@ -190,10 +192,10 @@ public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
|||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
super.close();
|
super.close();
|
||||||
|
|
||||||
raf.close();
|
|
||||||
|
|
||||||
raf = null;
|
|
||||||
buffer = null;
|
buffer = null;
|
||||||
|
|
||||||
|
raf.close();
|
||||||
|
raf = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to override the readShort(), readInt() and readLong() methods,
|
// Need to override the readShort(), readInt() and readLong() methods,
|
||||||
|
@ -37,18 +37,19 @@ import javax.imageio.spi.ServiceRegistry;
|
|||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BufferedFileImageInputStreamSpi
|
* BufferedFileImageInputStreamSpi
|
||||||
* Experimental
|
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
public BufferedFileImageInputStreamSpi() {
|
public BufferedFileImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
}
|
}
|
||||||
@ -69,12 +70,13 @@ public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
|
@Override
|
||||||
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||||
if (input instanceof File) {
|
if (input instanceof File) {
|
||||||
try {
|
try {
|
||||||
return new BufferedFileImageInputStream((File) input);
|
return new BufferedChannelImageInputStream((File) input);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException | NoSuchFileException e) {
|
||||||
// For consistency with the JRE bundled SPIs, we'll return null here,
|
// For consistency with the JRE bundled SPIs, we'll return null here,
|
||||||
// even though the spec does not say that's allowed.
|
// even though the spec does not say that's allowed.
|
||||||
// The problem is that the SPIs can only declare that they support an input type like a File,
|
// The problem is that the SPIs can only declare that they support an input type like a File,
|
||||||
@ -91,7 +93,8 @@ public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
return "Service provider that instantiates an ImageInputStream from a File";
|
return "Service provider that instantiates an ImageInputStream from a File";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedInputStreamImageInputStreamSpi.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
|
public BufferedInputStreamImageInputStreamSpi() {
|
||||||
|
this(new StreamProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
|
||||||
|
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||||
|
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
|
||||||
|
|
||||||
|
while (providers.hasNext()) {
|
||||||
|
ImageInputStreamSpi provider = providers.next();
|
||||||
|
if (provider != this) {
|
||||||
|
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||||
|
if (input instanceof InputStream) {
|
||||||
|
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
|
||||||
|
|
||||||
|
if (channel instanceof SeekableByteChannel) {
|
||||||
|
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
|
||||||
|
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, create a cache for backwards seeking
|
||||||
|
return new BufferedChannelImageInputStream(useCacheFile ? new DiskCache(channel, cacheDir): new MemoryCache(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUseCacheFile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
|
return "Service provider that instantiates an ImageInputStream from an InputStream";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InputStreamFilter implements ServiceRegistry.Filter {
|
||||||
|
@Override
|
||||||
|
public boolean filter(final Object provider) {
|
||||||
|
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@ import java.util.Locale;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
public BufferedRAFImageInputStreamSpi() {
|
public BufferedRAFImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
}
|
}
|
||||||
@ -69,9 +69,10 @@ public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
|
@Override
|
||||||
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
|
||||||
if (input instanceof RandomAccessFile) {
|
if (input instanceof RandomAccessFile) {
|
||||||
return new BufferedFileImageInputStream((RandomAccessFile) input);
|
return new BufferedChannelImageInputStream((RandomAccessFile) input);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
|
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
|
||||||
@ -82,7 +83,8 @@ public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
|
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ import java.util.Locale;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
|
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
|
|
||||||
public ByteArrayImageInputStreamSpi() {
|
public ByteArrayImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
@ -55,16 +55,17 @@ public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
|
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
|
@Override
|
||||||
if (pInput instanceof byte[]) {
|
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) {
|
||||||
return new ByteArrayImageInputStream((byte[]) pInput);
|
if (input instanceof byte[]) {
|
||||||
}
|
return new ByteArrayImageInputStream((byte[]) input);
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(Locale pLocale) {
|
throw new IllegalArgumentException("Expected input of type byte[]: " + input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(Locale locale) {
|
||||||
return "Service provider that instantiates an ImageInputStream from a byte array";
|
return "Service provider that instantiates an ImageInputStream from a byte array";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ public final class DirectImageInputStream extends ImageInputStreamImpl {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// We could seek to EOF here, but the usual case
|
// We could seek to EOF here, but the usual case is we know where the next chunk of data is
|
||||||
|
|
||||||
stream.close();
|
stream.close();
|
||||||
super.close();
|
super.close();
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.NonWritableChannelException;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
|
||||||
|
import static java.nio.file.StandardOpenOption.READ;
|
||||||
|
import static java.nio.file.StandardOpenOption.WRITE;
|
||||||
|
|
||||||
|
// Note: We could consider creating a memory-mapped version...
|
||||||
|
// But, from java.nio.channels.FileChannel.map:
|
||||||
|
// For most operating systems, mapping a file into memory is more
|
||||||
|
// expensive than reading or writing a few tens of kilobytes of data via
|
||||||
|
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||||
|
// standpoint of performance it is generally only worth mapping relatively
|
||||||
|
// large files into memory.
|
||||||
|
final class DiskCache implements SeekableByteChannel {
|
||||||
|
final static int BLOCK_SIZE = 1 << 13;
|
||||||
|
|
||||||
|
private final FileChannel cache;
|
||||||
|
private final ReadableByteChannel channel;
|
||||||
|
|
||||||
|
// TODO: Perhaps skip this constructor?
|
||||||
|
DiskCache(InputStream stream, File cacheDir) throws IOException {
|
||||||
|
// Stream will be closed with channel, documented behavior
|
||||||
|
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiskCache(ReadableByteChannel channel, File cacheDir) throws IOException {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
|
||||||
|
|
||||||
|
// Create a temp file to hold our cache,
|
||||||
|
// will be deleted when this channel is closed, as we close the cache
|
||||||
|
Path cacheFile = cacheDir == null
|
||||||
|
? Files.createTempFile("imageio", ".tmp")
|
||||||
|
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
|
||||||
|
|
||||||
|
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
void fetch() throws IOException {
|
||||||
|
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
|
||||||
|
// Continue transfer...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return channel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
cache.close();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ByteBuffer dest) throws IOException {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (cache.position() >= cache.size()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.read(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long position() throws IOException {
|
||||||
|
return cache.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||||
|
cache.position(newPosition);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long size() {
|
||||||
|
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int write(ByteBuffer src) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel truncate(long size) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.NonWritableChannelException;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
|
public final class MemoryCache implements SeekableByteChannel {
|
||||||
|
|
||||||
|
final static int BLOCK_SIZE = 1 << 13;
|
||||||
|
|
||||||
|
private final List<byte[]> cache = new ArrayList<>();
|
||||||
|
private final ReadableByteChannel channel;
|
||||||
|
private long length;
|
||||||
|
private long position;
|
||||||
|
private long start;
|
||||||
|
|
||||||
|
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
|
||||||
|
MemoryCache(InputStream stream) {
|
||||||
|
this(Channels.newChannel(notNull(stream, "stream")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryCache(ReadableByteChannel channel) {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fetchBlock() throws IOException {
|
||||||
|
long currPos = position;
|
||||||
|
|
||||||
|
long index = currPos / BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (index >= Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Memory cache max size exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index >= cache.size()) {
|
||||||
|
byte[] block;
|
||||||
|
try {
|
||||||
|
block = new byte[BLOCK_SIZE];
|
||||||
|
}
|
||||||
|
catch (OutOfMemoryError e) {
|
||||||
|
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.add(block);
|
||||||
|
length += readBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.get((int) index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBlock(final byte[] block) throws IOException {
|
||||||
|
ByteBuffer wrapped = ByteBuffer.wrap(block);
|
||||||
|
|
||||||
|
while (wrapped.hasRemaining()) {
|
||||||
|
int count = channel.read(wrapped);
|
||||||
|
if (count == -1) {
|
||||||
|
// Last block
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return channel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ByteBuffer dest) throws IOException {
|
||||||
|
byte[] buffer = fetchBlock();
|
||||||
|
int bufferPos = (int) (position % BLOCK_SIZE);
|
||||||
|
|
||||||
|
if (position >= length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
|
||||||
|
dest.put(buffer, bufferPos, len);
|
||||||
|
|
||||||
|
position += len;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long position() throws IOException {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||||
|
if (newPosition < start) {
|
||||||
|
throw new IOException("Seek before flush position");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.position = newPosition;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long size() throws IOException {
|
||||||
|
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int write(ByteBuffer src) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel truncate(long size) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushBefore(long pos) {
|
||||||
|
if (pos < start) {
|
||||||
|
throw new IndexOutOfBoundsException("pos < flushed position");
|
||||||
|
}
|
||||||
|
if (pos > position) {
|
||||||
|
throw new IndexOutOfBoundsException("pos > current position");
|
||||||
|
}
|
||||||
|
|
||||||
|
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
|
||||||
|
|
||||||
|
// Clear blocks no longer needed
|
||||||
|
for (int i = 0; i < blocks; i++) {
|
||||||
|
cache.set(i, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,9 +33,7 @@ package com.twelvemonkeys.imageio.stream;
|
|||||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||||
|
|
||||||
import javax.imageio.spi.ImageInputStreamSpi;
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
import javax.imageio.stream.FileCacheImageInputStream;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -52,7 +50,7 @@ import java.util.Locale;
|
|||||||
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
// TODO: URI instead of URL?
|
// TODO: URI instead of URL?
|
||||||
public class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
public URLImageInputStreamSpi() {
|
public URLImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
}
|
}
|
||||||
@ -64,53 +62,28 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
|
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
|
||||||
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
|
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
|
||||||
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
|
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
|
||||||
public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
|
@Override
|
||||||
if (pInput instanceof URL) {
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||||
URL url = (URL) pInput;
|
if (input instanceof URL) {
|
||||||
|
URL url = (URL) input;
|
||||||
|
|
||||||
// Special case for file protocol, a lot faster than FileCacheImageInputStream
|
// Special case for file protocol, a lot faster than FileCacheImageInputStream
|
||||||
if ("file".equals(url.getProtocol())) {
|
if ("file".equals(url.getProtocol())) {
|
||||||
try {
|
try {
|
||||||
return new BufferedFileImageInputStream(new File(url.toURI()));
|
return new BufferedChannelImageInputStream(new File(url.toURI()));
|
||||||
}
|
}
|
||||||
catch (URISyntaxException ignore) {
|
catch (URISyntaxException shouldNeverHappen) {
|
||||||
// This should never happen, but if it does, we'll fall back to using the stream
|
// This should never happen, but if it does, we'll fall back to using the stream
|
||||||
ignore.printStackTrace();
|
shouldNeverHappen.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise revert to cached
|
// Otherwise revert to cached
|
||||||
final InputStream urlStream = url.openStream();
|
InputStream urlStream = url.openStream();
|
||||||
if (pUseCache) {
|
return new BufferedChannelImageInputStream(useCacheFile ? new DiskCache(urlStream, cacheDir) : new MemoryCache(urlStream));
|
||||||
return new FileCacheImageInputStream(urlStream, pCacheDir) {
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
try {
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new MemoryCacheImageInputStream(urlStream) {
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
try {
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException("Expected input of type URL: " + pInput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Expected input of type URL: " + input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,7 +91,7 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Service provider that instantiates an ImageInputStream from a URL";
|
return "Service provider that instantiates an ImageInputStream from a URL";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
||||||
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
||||||
|
com.twelvemonkeys.imageio.stream.BufferedInputStreamImageInputStreamSpi
|
||||||
# Use SPI loading as a hook for early profile activation
|
# Use SPI loading as a hook for early profile activation
|
||||||
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
|
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
|
||||||
|
@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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 of the copyright holder 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 HOLDER 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.imageio.stream;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.function.ThrowingRunnable;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.only;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedFileImageInputStreamTestCase
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
// TODO: Remove this test, and instead test the disk cache directly!
|
||||||
|
public class BufferedChannelImageInputStreamDiskCacheTest {
|
||||||
|
private final Random random = new Random(170984354357234566L);
|
||||||
|
|
||||||
|
private InputStream randomDataToInputStream(byte[] data) {
|
||||||
|
random.nextBytes(data);
|
||||||
|
|
||||||
|
return new ByteArrayInputStream(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate() throws IOException {
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(new ByteArrayInputStream(new byte[0]), null))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullStream() throws IOException {
|
||||||
|
try {
|
||||||
|
new DiskCache((InputStream) null, null);
|
||||||
|
fail("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
assertNotNull("Null exception message", expected.getMessage());
|
||||||
|
String message = expected.getMessage().toLowerCase();
|
||||||
|
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
|
||||||
|
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullChannel() throws IOException {
|
||||||
|
try {
|
||||||
|
new DiskCache((ReadableByteChannel) null, null);
|
||||||
|
fail("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
assertNotNull("Null exception message", expected.getMessage());
|
||||||
|
String message = expected.getMessage().toLowerCase();
|
||||||
|
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||||
|
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
for (byte value : data) {
|
||||||
|
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Wrong data read", -1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadArray() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[1024];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i++) {
|
||||||
|
stream.readFully(result);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Wrong data read", -1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSkip() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 14];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[7];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||||
|
stream.readFully(result);
|
||||||
|
stream.skipBytes(result.length);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSeek() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 18];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[9];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i++) {
|
||||||
|
// Read backwards
|
||||||
|
long newPos = data.length - result.length - i * result.length;
|
||||||
|
stream.seek(newPos);
|
||||||
|
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||||
|
stream.readFully(result);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||||
|
byte[] data = new byte[256];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] buffer = new byte[data.length * 2];
|
||||||
|
stream.read(buffer);
|
||||||
|
stream.seek(0);
|
||||||
|
assertNotEquals(-1, stream.read());
|
||||||
|
assertNotEquals(-1, stream.read(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
stream.seek(0);
|
||||||
|
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||||
|
assertEquals(i % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandomOffset() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
for (int i = 1; i <= 60; i++) {
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setBitOffset(i % 8);
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||||
|
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadShort() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 2; i++) {
|
||||||
|
assertEquals(buffer.getShort(), stream.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 2; i++) {
|
||||||
|
assertEquals(buffer.getShort(), stream.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInt() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 4; i++) {
|
||||||
|
assertEquals(buffer.getInt(), stream.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 4; i++) {
|
||||||
|
assertEquals(buffer.getInt(), stream.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadLong() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 8; i++) {
|
||||||
|
assertEquals(buffer.getLong(), stream.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 8; i++) {
|
||||||
|
assertEquals(buffer.getLong(), stream.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSeekPastEOF() throws IOException {
|
||||||
|
byte[] bytes = new byte[9];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
stream.seek(1000);
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readFully(new byte[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readByte();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
for (byte value : bytes) {
|
||||||
|
assertEquals(value, stream.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClose() throws IOException {
|
||||||
|
// Create wrapper stream
|
||||||
|
InputStream mock = mock(InputStream.class);
|
||||||
|
ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(mock, null));
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
verify(mock, only()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||||
|
// See #606 for details.
|
||||||
|
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||||
|
// Ie: Relies on read to return all bytes at once, without blocking
|
||||||
|
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
|
||||||
|
byte[] result = new byte[size];
|
||||||
|
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||||
|
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||||
|
|
||||||
|
assertEquals(size, len + head);
|
||||||
|
assertArrayEquals(bytes, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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 of the copyright holder 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 HOLDER 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.imageio.stream;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.function.ThrowingRunnable;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.only;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedFileImageInputStreamTestCase
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
// TODO: Remove this test, and instead test the memory cache directly!
|
||||||
|
public class BufferedChannelImageInputStreamMemoryCacheTest {
|
||||||
|
private final Random random = new Random(170984354357234566L);
|
||||||
|
|
||||||
|
private InputStream randomDataToInputStream(byte[] data) {
|
||||||
|
random.nextBytes(data);
|
||||||
|
|
||||||
|
return new ByteArrayInputStream(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate() throws IOException {
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(new ByteArrayInputStream(new byte[0])))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullStream() {
|
||||||
|
try {
|
||||||
|
new MemoryCache((InputStream) null);
|
||||||
|
fail("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
assertNotNull("Null exception message", expected.getMessage());
|
||||||
|
String message = expected.getMessage().toLowerCase();
|
||||||
|
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
|
||||||
|
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullChannel() {
|
||||||
|
try {
|
||||||
|
new MemoryCache((ReadableByteChannel) null);
|
||||||
|
fail("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
assertNotNull("Null exception message", expected.getMessage());
|
||||||
|
String message = expected.getMessage().toLowerCase();
|
||||||
|
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||||
|
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
for (byte value : data) {
|
||||||
|
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Wrong data read", -1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadArray() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[1024];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i++) {
|
||||||
|
stream.readFully(result);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Wrong data read", -1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSkip() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 14];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[7];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||||
|
stream.readFully(result);
|
||||||
|
stream.skipBytes(result.length);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSeek() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 18];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[9];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i++) {
|
||||||
|
// Read backwards
|
||||||
|
long newPos = data.length - result.length - i * result.length;
|
||||||
|
stream.seek(newPos);
|
||||||
|
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||||
|
stream.readFully(result);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||||
|
byte[] data = new byte[256];
|
||||||
|
InputStream input = randomDataToInputStream(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||||
|
|
||||||
|
byte[] buffer = new byte[data.length * 2];
|
||||||
|
stream.read(buffer);
|
||||||
|
stream.seek(0);
|
||||||
|
assertNotEquals(-1, stream.read());
|
||||||
|
assertNotEquals(-1, stream.read(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
stream.seek(0);
|
||||||
|
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||||
|
assertEquals(i % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandomOffset() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
for (int i = 1; i <= 60; i++) {
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setBitOffset(i % 8);
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||||
|
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadShort() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 2; i++) {
|
||||||
|
assertEquals(buffer.getShort(), stream.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 2; i++) {
|
||||||
|
assertEquals(buffer.getShort(), stream.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInt() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 4; i++) {
|
||||||
|
assertEquals(buffer.getInt(), stream.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 4; i++) {
|
||||||
|
assertEquals(buffer.getInt(), stream.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadLong() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 8; i++) {
|
||||||
|
assertEquals(buffer.getLong(), stream.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 8; i++) {
|
||||||
|
assertEquals(buffer.getLong(), stream.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSeekPastEOF() throws IOException {
|
||||||
|
byte[] bytes = new byte[9];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
stream.seek(1000);
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readFully(new byte[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readByte();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
for (byte value : bytes) {
|
||||||
|
assertEquals(value, stream.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClose() throws IOException {
|
||||||
|
// Create wrapper stream
|
||||||
|
InputStream mock = mock(InputStream.class);
|
||||||
|
ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(mock));
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
verify(mock, only()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||||
|
// See #606 for details.
|
||||||
|
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||||
|
// Ie: Relies on read to return all bytes at once, without blocking
|
||||||
|
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
InputStream input = randomDataToInputStream(bytes);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||||
|
byte[] result = new byte[size];
|
||||||
|
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||||
|
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||||
|
|
||||||
|
assertEquals(size, len + head);
|
||||||
|
assertArrayEquals(bytes, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,442 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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 of the copyright holder 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 HOLDER 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.imageio.stream;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.function.ThrowingRunnable;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedFileImageInputStreamTestCase
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class BufferedChannelImageInputStreamTest {
|
||||||
|
private final Random random = new Random(170984354357234566L);
|
||||||
|
|
||||||
|
private File randomDataToFile(byte[] data) throws IOException {
|
||||||
|
random.nextBytes(data);
|
||||||
|
|
||||||
|
File file = File.createTempFile("read", ".tmp");
|
||||||
|
Files.write(file.toPath(), data);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate() throws IOException {
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(File.createTempFile("empty", ".tmp")))) {
|
||||||
|
assertEquals("Data length should be same as stream length", 0, stream.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullFileInputStream() {
|
||||||
|
try {
|
||||||
|
new BufferedChannelImageInputStream((FileInputStream) null);
|
||||||
|
fail("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
assertNotNull("Null exception message", expected.getMessage());
|
||||||
|
String message = expected.getMessage().toLowerCase();
|
||||||
|
assertTrue("Exception message does not contain parameter name", message.contains("inputstream"));
|
||||||
|
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullByteChannel() {
|
||||||
|
try {
|
||||||
|
new BufferedChannelImageInputStream((SeekableByteChannel) null);
|
||||||
|
fail("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
assertNotNull("Null exception message", expected.getMessage());
|
||||||
|
String message = expected.getMessage().toLowerCase();
|
||||||
|
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||||
|
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
File file = randomDataToFile(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||||
|
|
||||||
|
for (byte value : data) {
|
||||||
|
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadArray() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
File file = randomDataToFile(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[1024];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i++) {
|
||||||
|
stream.readFully(result);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSkip() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 14];
|
||||||
|
File file = randomDataToFile(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[7];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||||
|
stream.readFully(result);
|
||||||
|
stream.skipBytes(result.length);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSeek() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 18];
|
||||||
|
File file = randomDataToFile(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||||
|
|
||||||
|
byte[] result = new byte[9];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / result.length; i++) {
|
||||||
|
// Read backwards
|
||||||
|
long newPos = stream.length() - result.length - i * result.length;
|
||||||
|
stream.seek(newPos);
|
||||||
|
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||||
|
stream.readFully(result);
|
||||||
|
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||||
|
byte[] data = new byte[256];
|
||||||
|
File file = randomDataToFile(data);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||||
|
|
||||||
|
byte[] buffer = new byte[data.length * 2];
|
||||||
|
stream.read(buffer);
|
||||||
|
stream.seek(0);
|
||||||
|
assertNotEquals(-1, stream.read());
|
||||||
|
assertNotEquals(-1, stream.read(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
stream.seek(0);
|
||||||
|
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||||
|
assertEquals(i % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandomOffset() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
for (int i = 1; i <= 60; i++) {
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setBitOffset(i % 8);
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||||
|
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadShort() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 2; i++) {
|
||||||
|
assertEquals(buffer.getShort(), stream.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 2; i++) {
|
||||||
|
assertEquals(buffer.getShort(), stream.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInt() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 4; i++) {
|
||||||
|
assertEquals(buffer.getInt(), stream.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 4; i++) {
|
||||||
|
assertEquals(buffer.getInt(), stream.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadLong() throws IOException {
|
||||||
|
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 8; i++) {
|
||||||
|
assertEquals(buffer.getLong(), stream.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.position(0);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length / 8; i++) {
|
||||||
|
assertEquals(buffer.getLong(), stream.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSeekPastEOF() throws IOException {
|
||||||
|
byte[] bytes = new byte[9];
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
|
||||||
|
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
stream.seek(1000);
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||||
|
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readFully(new byte[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readByte();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readShort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readInt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Throwable {
|
||||||
|
stream.readLong();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.seek(0);
|
||||||
|
for (byte value : bytes) {
|
||||||
|
assertEquals(value, stream.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloseStream() throws IOException {
|
||||||
|
// Create wrapper stream
|
||||||
|
FileInputStream mock = mock(FileInputStream.class);
|
||||||
|
when(mock.getChannel()).thenCallRealMethod();
|
||||||
|
ImageInputStream stream = new BufferedChannelImageInputStream(mock);
|
||||||
|
reset(mock);
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
verify(mock, only()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloseChannel() throws IOException {
|
||||||
|
// Create wrapper stream
|
||||||
|
SeekableByteChannel mock = mock(SeekableByteChannel.class);
|
||||||
|
ImageInputStream stream = new BufferedChannelImageInputStream(mock);
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
verify(mock, only()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||||
|
// See #606 for details.
|
||||||
|
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||||
|
// Ie: Relies on read to return all bytes at once, without blocking
|
||||||
|
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
File file = randomDataToFile(bytes);
|
||||||
|
|
||||||
|
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||||
|
byte[] result = new byte[size];
|
||||||
|
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||||
|
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||||
|
|
||||||
|
assertEquals(size, len + head);
|
||||||
|
assertArrayEquals(bytes, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,7 +45,9 @@ import java.util.Random;
|
|||||||
|
|
||||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.only;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BufferedFileImageInputStreamTestCase
|
* BufferedFileImageInputStreamTestCase
|
||||||
@ -54,6 +56,7 @@ import static org.mockito.Mockito.*;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class BufferedFileImageInputStreamTest {
|
public class BufferedFileImageInputStreamTest {
|
||||||
private final Random random = new Random(170984354357234566L);
|
private final Random random = new Random(170984354357234566L);
|
||||||
|
|
||||||
@ -72,6 +75,7 @@ public class BufferedFileImageInputStreamTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
@Test
|
@Test
|
||||||
public void testCreateNullFile() throws IOException {
|
public void testCreateNullFile() throws IOException {
|
||||||
try {
|
try {
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedInputStreamImageInputStreamSpiTest.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class BufferedFileInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
|
||||||
|
@Override
|
||||||
|
protected ImageInputStreamSpi createProvider() {
|
||||||
|
return new BufferedInputStreamImageInputStreamSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream createInput() throws IOException {
|
||||||
|
return Files.newInputStream(File.createTempFile("test-", ".tst").toPath());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedInputStreamImageInputStreamSpiTest.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class BufferedInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
|
||||||
|
@Override
|
||||||
|
protected ImageInputStreamSpi createProvider() {
|
||||||
|
return new BufferedInputStreamImageInputStreamSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream createInput() {
|
||||||
|
return new ByteArrayInputStream(new byte[0]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user