/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name 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.io;
import com.twelvemonkeys.lang.
Validate;
import java.io.*;
/**
* A {@code SeekableInputStream} implementation that caches data in a temporary {@code File}.
* <p/>
* Temporary files are created as specified in {@link File#createTempFile(String, String, java.io.File)}.
*
* @see MemoryCacheSeekableStream
* @see FileSeekableStream
*
* @see File#createTempFile(String, String)
* @see RandomAccessFile
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java#5 $
*/
public final class
FileCacheSeekableStream extends
AbstractCachedSeekableStream {
private byte[]
buffer;
/**
* Creates a {@code FileCacheSeekableStream} reading from the given
* {@code InputStream}. Data will be cached in a temporary file.
*
* @param pStream the {@code InputStream} to read from
*
* @throws IOException if the temporary file cannot be created,
* or cannot be opened for random access.
*/
public
FileCacheSeekableStream(final
InputStream pStream) throws
IOException {
this(
pStream, "iocache", null);
}
/**
* Creates a {@code FileCacheSeekableStream} reading from the given
* {@code InputStream}. Data will be cached in a temporary file, with
* the given base name.
*
* @param pStream the {@code InputStream} to read from
* @param pTempBaseName optional base name for the temporary file
*
* @throws IOException if the temporary file cannot be created,
* or cannot be opened for random access.
*/
public
FileCacheSeekableStream(final
InputStream pStream, final
String pTempBaseName) throws
IOException {
this(
pStream,
pTempBaseName, null);
}
/**
* Creates a {@code FileCacheSeekableStream} reading from the given
* {@code InputStream}. Data will be cached in a temporary file, with
* the given base name, in the given directory
*
* @param pStream the {@code InputStream} to read from
* @param pTempBaseName optional base name for the temporary file
* @param pTempDir optional temp directory
*
* @throws IOException if the temporary file cannot be created,
* or cannot be opened for random access.
*/
public
FileCacheSeekableStream(final
InputStream pStream, final
String pTempBaseName, final
File pTempDir) throws
IOException {
// NOTE: We do validation BEFORE we create temp file, to avoid orphan files
this(
Validate.
notNull(
pStream, "stream"),
createTempFile(
pTempBaseName,
pTempDir));
}
/*protected*/ static
File createTempFile(
String pTempBaseName,
File pTempDir) throws
IOException {
Validate.
notNull(
pTempBaseName, "tempBaseName");
File file =
File.
createTempFile(
pTempBaseName, null,
pTempDir);
file.
deleteOnExit();
return
file;
}
// TODO: Consider exposing this for external use
/*protected*/
FileCacheSeekableStream(final
InputStream pStream, final
File pFile) throws
FileNotFoundException {
super(
pStream, new
FileCache(
pFile));
// TODO: Allow for custom buffer sizes?
buffer = new byte[1024];
}
public final boolean
isCachedMemory() {
return false;
}
public final boolean
isCachedFile() {
return true;
}
@
Override
protected void
closeImpl() throws
IOException {
super.closeImpl();
buffer = null;
}
@
Override
public int
read() throws
IOException {
checkOpen();
int
read;
if (
position ==
streamPosition) {
// Read ahead into buffer, for performance
read =
readAhead(
buffer, 0,
buffer.length);
if (
read >= 0) {
read =
buffer[0] & 0xff;
}
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff));
}
else {
// ..or read byte from the cache
syncPosition();
read =
getCache().
read();
//System.out.println("Read 1 byte from cache: " + Integer.toHexString(read & 0xff));
}
// TODO: This field is not REALLY considered accessible.. :-P
if (
read != -1) {
position++;
}
return
read;
}
@
Override
public int
read(byte[]
pBytes, int
pOffset, int
pLength) throws
IOException {
checkOpen();
int
length;
if (
position ==
streamPosition) {
// Read bytes from the stream
length =
readAhead(
pBytes,
pOffset,
pLength);
//System.out.println("Read " + length + " byte from stream");
}
else {
// ...or read bytes from the cache
syncPosition();
length =
getCache().
read(
pBytes,
pOffset, (int)
Math.
min(
pLength,
streamPosition -
position));
//System.out.println("Read " + length + " byte from cache");
}
// TODO: This field is not REALLY considered accessible.. :-P
if (
length > 0) {
position +=
length;
}
return
length;
}
private int
readAhead(final byte[]
pBytes, final int
pOffset, final int
pLength) throws
IOException {
int
length;
length =
stream.
read(
pBytes,
pOffset,
pLength);
if (
length > 0) {
streamPosition +=
length;
getCache().
write(
pBytes,
pOffset,
length);
}
return
length;
}
final static class
FileCache extends
StreamCache {
private
RandomAccessFile mCacheFile;
public
FileCache(final
File pFile) throws
FileNotFoundException {
Validate.
notNull(
pFile, "file");
mCacheFile = new
RandomAccessFile(
pFile, "rw");
}
public void
write(final int
pByte) throws
IOException {
mCacheFile.
write(
pByte);
}
@
Override
public void
write(final byte[]
pBuffer, final int
pOffset, final int
pLength) throws
IOException {
mCacheFile.
write(
pBuffer,
pOffset,
pLength);
}
public int
read() throws
IOException {
return
mCacheFile.
read();
}
@
Override
public int
read(final byte[]
pBuffer, final int
pOffset, final int
pLength) throws
IOException {
return
mCacheFile.
read(
pBuffer,
pOffset,
pLength);
}
public void
seek(final long
pPosition) throws
IOException {
mCacheFile.
seek(
pPosition);
}
public long
getPosition() throws
IOException {
return
mCacheFile.
getFilePointer();
}
}
}