# TemporaryData.java

    package org.apache.synapse.commons.util;

    import org.apache.commons.io.IOUtils;
    import org.apache.commons.io.FileUtils;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;

    import java.io.*;

    /**
     * Class representing some temporary data in the form of a byte stream.
     * <p>
     * Data is stored by writing to the output stream obtained using
     * {@link #getOutputStream()}. It can then be read back using
     * the input stream obtained from {@link #getInputStream()}.
     * The data is first stored into a fixed size buffer. Once this
     * buffer overflows, it is transferred to a temporary file. The buffer
     * is divided into a given number of fixed size chunks that are allocated
     * on demand. Since a temporary file may be created it is mandatory to
     * call {@link #release()} to discard the temporary data.
     *
     * @deprecated this class is deprecated and will be removed from the next release,
     * please use the {@link org.apache.axiom.util.blob.OverflowBlob} from axiom instead
     */
    @Deprecated
    public class TemporaryData {

        private static final Log log = LogFactory.getLog(TemporaryData.class);

        class OutputStreamImpl extends OutputStream {

            private FileOutputStream fileOutputStream;

            public void write(byte[] b, int off, int len) throws IOException {

                if (fileOutputStream != null) {
                    fileOutputStream.write(b, off, len);
                } else if (len > (chunks.length-chunkIndex)*chunkSize - chunkOffset) {

                    // The buffer will overflow. Switch to a temporary file.
                    fileOutputStream = switchToTempFile();

                    // Write the new data to the temporary file.
                    fileOutputStream.write(b, off, len);

                } else {

                    // The data will fit into the buffer.
                    while (len > 0) {

                        byte[] chunk = getCurrentChunk();

                        // Determine number of bytes that can be copied to the current chunk.
                        int c = Math.min(len, chunkSize-chunkOffset);
                        // Copy data to the chunk.
                        System.arraycopy(b, off, chunk, chunkOffset, c);

                        // Update variables.
                        len -= c;
                        off += c;
                        chunkOffset += c;
                        if (chunkOffset == chunkSize) {
                            chunkIndex++;
                            chunkOffset = 0;
                        }
                    }
                }
            }

            public void write(byte[] b) throws IOException {
                write(b, 0, b.length);
            }

            public void write(int b) throws IOException {
                write(new byte[] { (byte)b }, 0, 1);
            }

            public void flush() throws IOException {
                if (fileOutputStream != null) {
                    fileOutputStream.flush();
                }
            }

            public void close() throws IOException {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            }
        }

        class InputStreamImpl extends InputStream {

            private int currentChunkIndex;
            private int currentChunkOffset;
            private int markChunkIndex;
            private int markChunkOffset;

            public int available() throws IOException {
                return (chunkIndex-currentChunkIndex)*chunkSize + chunkOffset - currentChunkOffset;
            }

            public int read(byte[] b, int off, int len) throws IOException {

                if (len == 0) {
                    return 0;
                }

                int read = 0;
                while (len > 0 && !(currentChunkIndex == chunkIndex
                        && currentChunkOffset == chunkOffset)) {

                    int c;
                    if (currentChunkIndex == chunkIndex) {
                        // The current chunk is the last one => take into account the offset
                        c = Math.min(len, chunkOffset-currentChunkOffset);
                    } else {
                        c = Math.min(len, chunkSize-currentChunkOffset);
                    }

                    // Copy the data.
                    System.arraycopy(chunks[currentChunkIndex], currentChunkOffset, b, off, c);

                    // Update variables
                    len -= c;
                    off += c;
                    currentChunkOffset += c;
                    read += c;
                    if (currentChunkOffset == chunkSize) {
                        currentChunkIndex++;
                        currentChunkOffset = 0;
                    }
                }

                if (read == 0) {
                    // We didn't read anything (and the len argument was not 0) => we reached the end of the buffer.
                    return -1;
                } else {
                    return read;
                }
            }

            public int read(byte[] b) throws IOException {
                return read(b, 0, b.length);
            }

            public int read() throws IOException {
                byte[] b = new byte[1];
                return read(b) == -1 ? -1 : (int)b[0] & 0xFF;
            }

            public boolean markSupported() {
                return true;
            }

            public void mark(int readlimit) {
                markChunkIndex = currentChunkIndex;
                markChunkOffset = currentChunkOffset;
            }

            public void reset() throws IOException {
                currentChunkIndex = markChunkIndex;
                currentChunkOffset = markChunkOffset;
            }

            public long skip(long n) throws IOException {

                int available = available();
                int c = n < available ? (int)n : available;
                int newOffset = currentChunkOffset + c;
                int chunkDelta = newOffset/chunkSize;
                currentChunkIndex += chunkDelta;
                currentChunkOffset = newOffset - (chunkDelta*chunkSize);
                return c;
            }

            public void close() throws IOException {
            }
        }

        /**
         * Size of the chunks that will be allocated in the buffer.
         */
        final int chunkSize;

        /**
         * The prefix to be used in generating the name of the temporary file.
         */
        final String tempPrefix;

        /**
         * The suffix to be used in generating the name of the temporary file.
         */
        final String tempSuffix;

        /**
         * Array of <code>byte[]</code> representing the chunks of the buffer.
         * A chunk is only allocated when the first byte is written to it.
         * This attribute is set to <code>null</code> when the buffer overflows and
         * is written out to a temporary file.
         */
        byte[][] chunks;

        /**
         * Index of the chunk the next byte will be written to.
         */
        int chunkIndex;

        /**
         * Offset into the chunk where the next byte will be written.
         */
        int chunkOffset;

        /**
         * The handle of the temporary file. This is only set when the memory buffer
         * overflows and is written out to a temporary file.
         */
        File temporaryFile;

        public TemporaryData(int numberOfChunks, int chunkSize, String tempPrefix, String tempSuffix) {
            this.chunkSize = chunkSize;
            this.tempPrefix = tempPrefix;
            this.tempSuffix = tempSuffix;
            chunks = new byte[numberOfChunks][];
        }

        /**
         * Get the current chunk to write to, allocating it if necessary.
         *
         * @return the current chunk to write to (never null)
         */
        byte[] getCurrentChunk() {
            if (chunkOffset == 0) {
                // We will write the first byte to the current chunk. Allocate it.
                byte[] chunk = new byte[chunkSize];
                chunks[chunkIndex] = chunk;
                return chunk;
            } else {
                // The chunk has already been allocated.
                return chunks[chunkIndex];
            }
        }

        /**
         * Create a temporary file and write the existing in memory data to it.
         *
         * @return an open FileOutputStream to the temporary file
         * @throws IOException in case of an error in switching to the temp file
         */
        FileOutputStream switchToTempFile() throws IOException {
            temporaryFile = File.createTempFile(tempPrefix, tempSuffix);
            if (log.isDebugEnabled()) {
                log.debug("Using temporary file " + temporaryFile);
            }
            temporaryFile.deleteOnExit();

            FileOutputStream fileOutputStream = new FileOutputStream(temporaryFile);
            // Write the buffer to the temporary file.
            for (int i=0; i<chunkIndex; i++) {
                fileOutputStream.write(chunks[i]);
            }

            if (chunkOffset > 0) {
                fileOutputStream.write(chunks[chunkIndex], 0, chunkOffset);
            }

            // Release references to the buffer so that it can be garbage collected.
            chunks = null;

            return fileOutputStream;
        }

        public OutputStream getOutputStream() {
            return new OutputStreamImpl();
        }

        /**
         * Fill this object with data read from a given InputStream.
         * <p>
         * A call <code>tmp.readFrom(in)</code> has the same effect as the
         * following code:
         * <pre>
         * OutputStream out = tmp.getOutputStream();
         * IOUtils.copy(in, out);
         * out.close();
         * </pre>
         * However it does so in a more efficient way.
         *
         * @param in An InputStream to read data from. This method will not
         *           close the stream.
         * @throws IOException in case of an error in reading from <code>InputStream</code>
         */
        public void readFrom(InputStream in) throws IOException {
            while (true) {
                int c = in.read(getCurrentChunk(), chunkOffset, chunkSize-chunkOffset);
                if (c == -1) {
                    break;
                }
                chunkOffset += c;
                if (chunkOffset == chunkSize) {
                    chunkIndex++;
                    chunkOffset = 0;
                    if (chunkIndex == chunks.length) {
                        FileOutputStream fileOutputStream = switchToTempFile();
                        IOUtils.copy(in, fileOutputStream);
                        fileOutputStream.close();
                        break;
                    }
                }
            }
        }

        public InputStream getInputStream() throws IOException {
            if (temporaryFile != null) {
                return new FileInputStream(temporaryFile);
            } else {
                return new InputStreamImpl();
            }
        }

        /**
         * Write the data to a given output stream.
         *
         * @param out The output stream to write the data to. This method will
         *            not close the stream.
         * @throws IOException  in case of an error in writing to the <code>OutputStream</code>
         */
        public void writeTo(OutputStream out) throws IOException {
            if (temporaryFile != null) {
                FileInputStream in = new FileInputStream(temporaryFile);
                try {
                    IOUtils.copy(in, out);
                } finally {
                    in.close();
                }
            } else {
                for (int i=0; i<chunkIndex; i++) {
                    out.write(chunks[i]);
                }
                if (chunkOffset > 0) {
                    out.write(chunks[chunkIndex], 0, chunkOffset);
                }
            }
        }

        public long getLength() {
            if (temporaryFile != null) {
                return temporaryFile.length();
            } else {
                return chunkIndex*chunkSize + chunkOffset;
            }
        }

        @SuppressWarnings({"ResultOfMethodCallIgnored"})
        public void release() {
            if (temporaryFile != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Deleting temporary file " + temporaryFile);
                }
                FileUtils.deleteQuietly(temporaryFile);
                temporaryFile = null;
            }
        }

        @SuppressWarnings({"FinalizeDoesntCallSuperFinalize", "ResultOfMethodCallIgnored"})
        @Override
        protected void finalize() throws Throwable {
            if (temporaryFile != null) {
                log.warn("Cleaning up unreleased temporary file " + temporaryFile);
                FileUtils.deleteQuietly(temporaryFile);
            }
        }
    }