/*
* @(#)FileCacheImageInputStream.java 1.29 05/08/17
*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.imageio.stream;
import java.io.DataInput;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import com.sun.imageio.stream.StreamCloser;
/**
* An implementation of ImageInputStream
that gets its
* input from a regular InputStream
. A file is used to
* cache previously read data.
*
* @version 0.5
*/
public class FileCacheImageInputStream extends ImageInputStreamImpl {
private InputStream stream;
private File cacheFile;
private RandomAccessFile cache;
private static final int BUFFER_LENGTH = 1024;
private byte[] buf = new byte[BUFFER_LENGTH];
private long length = 0L;
private boolean foundEOF = false;
/**
* Constructs a FileCacheImageInputStream
that will read
* from a given InputStream
.
*
*
A temporary file is used as a cache. If
* cacheDir
is non-null
and is a
* directory, the file will be created there. If it is
* null
, the system-dependent default temporary-file
* directory will be used (see the documentation for
* File.createTempFile
for details).
*
* @param stream an InputStream
to read from.
* @param cacheDir a File
indicating where the
* cache file should be created, or null
to use the
* system directory.
*
* @exception IllegalArgumentException if stream
is
* null
.
* @exception IllegalArgumentException if cacheDir
is
* non-null
but is not a directory.
* @exception IOException if a cache file cannot be created.
*/
public FileCacheImageInputStream(InputStream stream, File cacheDir)
throws IOException {
if (stream == null) {
throw new IllegalArgumentException("stream == null!");
}
if ((cacheDir != null) && !(cacheDir.isDirectory())) {
throw new IllegalArgumentException("Not a directory!");
}
this.stream = stream;
this.cacheFile =
File.createTempFile("imageio", ".tmp", cacheDir);
this.cache = new RandomAccessFile(cacheFile, "rw");
StreamCloser.addToQueue(this);
}
/**
* Ensures that at least pos
bytes are cached,
* or the end of the source is reached. The return value
* is equal to the smaller of pos
and the
* length of the source file.
*/
private long readUntil(long pos) throws IOException {
// We've already got enough data cached
if (pos < length) {
return pos;
}
// pos >= length but length isn't getting any bigger, so return it
if (foundEOF) {
return length;
}
long len = pos - length;
cache.seek(length);
while (len > 0) {
// Copy a buffer's worth of data from the source to the cache
// BUFFER_LENGTH will always fit into an int so this is safe
int nbytes =
stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));
if (nbytes == -1) {
foundEOF = true;
return length;
}
cache.write(buf, 0, nbytes);
len -= nbytes;
length += nbytes;
}
return pos;
}
public int read() throws IOException {
bitOffset = 0;
long next = streamPos + 1;
long pos = readUntil(next);
if (pos >= next) {
cache.seek(streamPos++);
return cache.read();
} else {
return -1;
}
}
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
}
// Fix 4430357 - if off + len < 0, overflow occurred
if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
throw new IndexOutOfBoundsException();
}
if (len == 0) {
return 0;
}
checkClosed();
bitOffset = 0;
long pos = readUntil(streamPos + len);
// len will always fit into an int so this is safe
len = (int)Math.min((long)len, pos - streamPos);
if (len > 0) {
cache.seek(streamPos);
cache.readFully(b, off, len);
streamPos += len;
return len;
} else {
return -1;
}
}
/**
* Returns true
since this
* ImageInputStream
caches data in order to allow
* seeking backwards.
*
* @return true
.
*
* @see #isCachedMemory
* @see #isCachedFile
*/
public boolean isCached() {
return true;
}
/**
* Returns true
since this
* ImageInputStream
maintains a file cache.
*
* @return true
.
*
* @see #isCached
* @see #isCachedMemory
*/
public boolean isCachedFile() {
return true;
}
/**
* Returns false
since this
* ImageInputStream
does not maintain a main memory
* cache.
*
* @return false
.
*
* @see #isCached
* @see #isCachedFile
*/
public boolean isCachedMemory() {
return false;
}
/**
* Closes this FileCacheImageInputStream
, closing
* and removing the cache file. The source InputStream
* is not closed.
*
* @exception IOException if an error occurs.
*/
public void close() throws IOException {
super.close();
cache.close();
cacheFile.delete();
stream = null;
StreamCloser.removeFromQueue(this);
}
}