org.exist.util.VirtualTempFile.java Source code

Java tutorial

Introduction

Here is the source code for org.exist.util.VirtualTempFile.java

Source

/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2011 The eXist Project
 *  http://exist.sourceforge.net
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.exist.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;

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.commons.io.IOUtils;
import org.apache.log4j.Logger;

import org.apache.commons.io.output.ByteArrayOutputStream;;

/**
 * 
 * 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());

                final OutputStream tmpBuffer = new FileOutputStream(tempFile);
                try {
                    tmpBuffer.write(theBlock);
                } finally {
                    tmpBuffer.close();
                }
            } catch (final IOException ioe) {
                // Do Nothing(R)
            }
        }
    }

    /**
     * The prefix string used when the temp file is going to be created
     * @return prefix string
     */
    public String getTempPrefix() {
        return temp_prefix;
    }

    /**
     * The postfix string used when the temp file is going to be created
     * @return  postfix string
     */
    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) {
            final 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 (final IOException ioe) {
                // IgnoreIT(R)
            }
        }

        return vLength;
    }

    /**
     * Method from File
     * @return Always returns true
     */
    public boolean delete() {
        if (os != null) {
            try {
                close();
            } catch (final IOException ioe) {
                // IgnoreIT(R)
            }
        }

        if (tempFile != null) {
            if (strBuffer != null) {
                try {
                    strBuffer.close();
                } catch (final IOException ioe) {
                    // IgnoreIT(R)
                }
                strBuffer = null;
            }

            if (deleteTempFile) {
                tempFile.delete();
            }
            tempFile = null;
        }

        if (baBuffer != null) {
            try {
                baBuffer.close();
            } catch (final 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();
        }
    }

    /**
     * A commodity method to write the whole content of an InputStream
     */
    public void write(InputStream is) throws IOException {
        write(is, -1L);
    }

    /**
     * A commodity method to write the whole content of an InputStream,
     * giving an optional max length (honored when it is bigger than 0)
     */
    public void write(InputStream is, long lengthHint) throws IOException {
        if (os == null) {
            throw new IOException("No stream to write to");
        }

        final byte[] buffer = new byte[maxChunkSize];
        long off = 0;
        int count = 0;
        do {
            count = is.read(buffer);
            if (count > 0) {
                os.write(buffer, 0, count);
                off += count;
            }
            if (baBuffer != null && baBuffer.size() > maxMemorySize) {
                writeSwitch();
            }
        } while (count != -1 && (lengthHint <= 0 || off < lengthHint));
    }

    /**
     * An easy way to obtain an InputStream
     * @return byte stream
     * @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;
    }

    /**
     * It returns either a byte array or a File
     * with the content. The initial threshold rules
     * which kind of object you are getting
     * @return Either a File or a byte[] object
     */
    public Object getContent() {
        try {
            if (os != null) {
                close();
            }
        } catch (final IOException ioe) {
            // IgnoreIT(R)
        }

        return (tempFile != null) ? tempFile : tempBuffer;
    }

    /**
     * Method to force materialization as a (temp)file the VirtualTempFile instance
     * @return A (temporal) file with the content
     * @throws IOException
     */
    public File toFile() throws IOException {
        // First, forcing the write to temp file
        writeSwitch();
        // Second, close
        if (os != null) {
            close();
        }

        final File retFile = tempFile;

        // From this point the tempFile is not managed any more by this VirtualTempFile
        tempFile = null;

        return retFile;
    }

    /**
     * Method to materialize the accumulated content in an OutputStream
     * @param out The output stream where the content is going to be written
     */
    public void writeToStream(OutputStream out) throws IOException {
        final InputStream result = null;
        if (tempFile != null) {
            //         byte[] writeBuffer=new byte[65536];
            final InputStream input = new BufferedInputStream(new FileInputStream(tempFile));
            IOUtils.copy(input, out);
            IOUtils.closeQuietly(input);

            //         try {
            //            int readBytes;
            //            while((readBytes = input.read(writeBuffer,0,writeBuffer.length))!=-1) {
            //               out.write(writeBuffer,0,readBytes);
            //            }
            //         } finally {
            //            input.close();
            //         }
        } else if (tempBuffer != null) {
            out.write(tempBuffer);
        }
    }
}