package org.exist.util;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import org.apache.log4j.Logger;
/**
*
* This class is a cross-over of many others, but mainly File and OutputStream
*
* @author jmfernandez
*
*/
public class VirtualTempFile
extends OutputStream
{
private final static Logger LOG = Logger.getLogger(VirtualTempFile.class);
private final static int DEFAULT_MAX_CHUNK_SIZE = 0x40000;
private final static String DEFAULT_TEMP_PREFIX = "eXistRPCV";
private final static String DEFAULT_TEMP_POSTFIX = ".res";
protected File tempFile;
protected boolean deleteTempFile;
protected ByteArrayOutputStream baBuffer;
protected FileOutputStream strBuffer;
protected OutputStream os;
protected byte[] tempBuffer;
protected int maxMemorySize;
protected int maxChunkSize;
protected long vLength;
protected String temp_prefix;
protected String temp_postfix;
/**
* Constructor for a fresh VirtualTempFile
*/
public VirtualTempFile() {
this(DEFAULT_MAX_CHUNK_SIZE,DEFAULT_MAX_CHUNK_SIZE);
}
/**
* Constructor for a fresh VirtualTempFile, with some params
* @param maxMemorySize
* @param maxChunkSize
*/
public VirtualTempFile(int maxMemorySize,int maxChunkSize) {
this.maxMemorySize = maxMemorySize;
this.maxChunkSize = maxChunkSize;
vLength = -1L;
baBuffer = new ByteArrayOutputStream(maxMemorySize);
strBuffer = null;
tempFile = null;
tempBuffer = null;
deleteTempFile = true;
os = baBuffer;
temp_prefix = DEFAULT_TEMP_PREFIX;
temp_postfix = DEFAULT_TEMP_POSTFIX;
}
/**
* Constructor for an already known file
* @param theFile
*/
public VirtualTempFile(File theFile) {
this(theFile,DEFAULT_MAX_CHUNK_SIZE);
}
/**
* Constructor for an already known file, with params
* @param theFile
* @param maxChunkSize
*/
public VirtualTempFile(File theFile,int maxChunkSize) {
// This one is not going to be used, but it is set to avoid uninitialized variables
this.maxMemorySize = maxChunkSize;
this.maxChunkSize = maxChunkSize;
baBuffer = null;
strBuffer = null;
os = null;
tempFile = theFile;
deleteTempFile = false;
vLength = theFile.length();
tempBuffer = null;
temp_prefix = DEFAULT_TEMP_PREFIX;
temp_postfix = DEFAULT_TEMP_POSTFIX;
}
/**
* Constructor for an already known memory block
* @param theBlock
*/
public VirtualTempFile(byte[] theBlock) {
this(theBlock,theBlock.length,DEFAULT_MAX_CHUNK_SIZE);
}
/**
* Constructor for an already known memory block, with params
* @param theBlock
* @param maxMemorySize
* @param maxChunkSize
*/
public VirtualTempFile(byte[] theBlock,int maxMemorySize, int maxChunkSize) {
// This one is not going to be used, but it is set to avoid uninitialized variables
this.maxMemorySize = maxMemorySize;
this.maxChunkSize = maxChunkSize;
baBuffer = null;
strBuffer = null;
os = null;
temp_prefix = DEFAULT_TEMP_PREFIX;
temp_postfix = DEFAULT_TEMP_POSTFIX;
tempFile = null;
deleteTempFile = true;
vLength = theBlock.length;
if(vLength<=maxMemorySize) {
tempBuffer = theBlock;
} else {
try {
tempFile = File.createTempFile(temp_prefix, temp_postfix);
tempFile.deleteOnExit();
LOG.debug("Writing to temporary file: " + tempFile.getName());
OutputStream tmpBuffer = new FileOutputStream(tempFile);
try {
tmpBuffer.write(theBlock);
} finally {
tmpBuffer.close();
}
} catch(IOException ioe) {
// Do Nothing(R)
}
}
}
/**
* The prefix string used when the temp file is going to be created.
*/
public String getTempPrefix() {
return temp_prefix;
}
/**
* The postfix string used when the temp file is going to be createds
*/
public String getTempPostfix() {
return temp_postfix;
}
/**
* It sets the used prefix string on temp filename creation
* @param newPrefix
*/
public void setTempPrefix(String newPrefix) {
if(newPrefix==null)
newPrefix=DEFAULT_TEMP_PREFIX;
temp_prefix = newPrefix;
}
/**
* It sets the used prefix string on temp filename creation
* @param newPostfix
*/
public void setTempPostfix(String newPostfix) {
if(newPostfix==null)
newPostfix=DEFAULT_TEMP_POSTFIX;
temp_postfix = newPostfix;
}
/**
* Method from OutputStream
*/
public void close()
throws IOException
{
if(baBuffer!=null) {
tempBuffer = baBuffer.toByteArray();
baBuffer = null;
vLength = tempBuffer.length;
}
if(strBuffer!=null) {
strBuffer.close();
strBuffer = null;
vLength = tempFile.length();
}
if(os!=null)
os=null;
}
/**
* Method from OutputStream
*/
public void flush()
throws IOException
{
if(os==null)
throw new IOException("No stream to flush");
os.flush();
}
/**
* The method <code>getChunk</code>
*
* @param offset a <code>long</code> value
* @return a <code>byte[]</code> value
* @exception IOException if an error occurs
*/
public byte[] getChunk(long offset)
throws IOException
{
byte[] data = null;
if(os!=null) close();
if(tempFile!=null) {
RandomAccessFile raf = new RandomAccessFile(tempFile, "r");
raf.seek(offset);
long remaining = raf.length() - offset;
if(remaining > maxChunkSize)
remaining = maxChunkSize;
else if(remaining<0)
remaining = 0;
data = new byte[(int)remaining];
raf.readFully(data);
raf.close();
} else if(tempBuffer!=null) {
long remaining = tempBuffer.length - offset;
if(remaining > maxChunkSize)
remaining = maxChunkSize;
else if(remaining<0)
remaining = 0;
data = new byte[(int)remaining];
if(remaining>0)
System.arraycopy(tempBuffer,(int)offset,data,0,(int)remaining);
}
return data;
}
public boolean exists() {
return tempFile!=null || tempBuffer!=null || baBuffer!=null;
}
public long length() {
if(os!=null) {
try {
close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
}
return vLength;
}
/**
* Method from File
* @return Always returns true
*/
public boolean delete() {
if(os!=null) {
try {
close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
}
if(tempFile!=null) {
if(strBuffer!=null) {
try {
strBuffer.close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
strBuffer=null;
}
if(deleteTempFile)
tempFile.delete();
tempFile=null;
}
if(baBuffer!=null) {
try {
baBuffer.close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
baBuffer = null;
}
if(tempBuffer!=null) {
tempBuffer = null;
}
return true;
}
private void writeSwitch()
throws IOException
{
if(tempFile==null) {
tempFile = File.createTempFile(temp_prefix, temp_postfix);
tempFile.deleteOnExit();
LOG.debug("Writing to temporary file: " + tempFile.getName());
strBuffer = new FileOutputStream(tempFile);
strBuffer.write(baBuffer.toByteArray());
os = strBuffer;
}
}
@Override
public void write(int b)
throws IOException
{
if(os==null) {
throw new IOException("No stream to write to");
}
os.write(b);
if(baBuffer!=null && baBuffer.size()>maxMemorySize) {
writeSwitch();
}
}
public void write(byte[] b, int off, int len)
throws IOException
{
if(os==null) {
throw new IOException("No stream to write to");
}
os.write(b,off,len);
if(baBuffer!=null && baBuffer.size()>maxMemorySize) {
writeSwitch();
}
}
/**
* An easy way to obtain an InputStream
* s
* @throws IOException
*/
public InputStream getByteStream()
throws IOException
{
if(os!=null) close();
InputStream result = null;
if(tempFile!=null) {
result = new BufferedInputStream(new FileInputStream(tempFile),655360);
} else if(tempBuffer!=null) {
result = new ByteArrayInputStream(tempBuffer);
}
return result;
}
public Object getContent()
{
try {
if(os!=null) close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
return (tempFile!=null)?tempFile:tempBuffer;
}
/**
* Method to force materialization as a (temp)file the VirtualTempFile instance
*
* @throws IOException
*/
public File toFile()
throws IOException
{
// First, forcing the write to temp file
writeSwitch();
// Second, close
if(os!=null) close();
File retFile = tempFile;
// From this point the tempFile is not managed any more by this VirtualTempFile
tempFile = null;
return retFile;
}
}