/* * FileBuffer.java February 2008 * * Copyright (C) 2008, Niall Gallagher * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.simpleframework.common.buffer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * The FileBuffer object is used to create a buffer * which will write the appended data to an underlying file. This * is typically used for buffers that are too large for to allocate * in memory. Data appended to the buffer can be retrieved at a * later stage by acquiring the InputStream for the * underlying file. To ensure that excessive file system space is * not occupied the buffer files are cleaned every five minutes. * * @author Niall Gallagher * * @see org.simpleframework.common.buffer.FileAllocator */ class FileBuffer implements Buffer { /** * This is the file output stream used for this buffer object. */ private OutputStream buffer; /** * This represents the last file segment that has been created. */ private Segment segment; /** * This is the path for the file that this buffer appends to. */ private File file; /** * This is the number of bytes currently appended to the buffer. */ private long count; /** * This is used to determine if this buffer has been closed. */ private boolean closed; /** * Constructor for the FileBuffer object. This will * create a buffer using the provided file. All data appended to * this buffer will effectively written to the underlying file. * If the appended data needs to be retrieved at a later stage * then it can be acquired using the buffers input stream. * * @param file this is the file used for the file buffer */ public FileBuffer(File file) throws IOException { this.buffer = new FileOutputStream(file); this.file = file; } /** * This is used to allocate a segment within this buffer. If the * buffer is closed this will throw an exception, if however the * buffer is still open then a segment is created which will * write all appended data to this buffer. However it can be * treated as an independent source of data. * * @return this returns a buffer which is a segment of this */ public Buffer allocate() throws IOException { if(closed) { throw new BufferException("Buffer has been closed"); } if(segment != null) { segment.close(); } if(!closed) { segment = new Segment(this, count); } return segment; } /** * This is used to append the specified data to the underlying * file. All bytes appended to the file can be consumed at a * later stage by acquiring the InputStream from * this buffer. Also if require the data can be encoded as a * string object in a required character set. * * @param array this is the array to write the the file * * @return this returns this buffer for further operations */ public Buffer append(byte[] array) throws IOException { return append(array, 0, array.length); } /** * This is used to append the specified data to the underlying * file. All bytes appended to the file can be consumed at a * later stage by acquiring the InputStream from * this buffer. Also if require the data can be encoded as a * string object in a required character set. * * @param array this is the array to write the the file * @param off this is the offset within the array to write * @param size this is the number of bytes to be appended * * @return this returns this buffer for further operations */ public Buffer append(byte[] array, int off, int size) throws IOException { if(closed) { throw new BufferException("Buffer has been closed"); } if(size > 0) { buffer.write(array, off, size); count += size; } return this; } /** * This method is used to acquire the buffered bytes as a string. * This is useful if the contents need to be manipulated as a * string or transferred into another encoding. If the UTF-8 * content encoding is not supported the platform default is * used, however this is unlikely as UTF-8 should be supported. * * @return this returns a UTF-8 encoding of the buffer contents */ public String encode() throws IOException { return encode("UTF-8"); } /** * This method is used to acquire the buffered bytes as a string. * This is useful if the contents need to be manipulated as a * string or transferred into another encoding. This will convert * the bytes using the specified character encoding format. * * @param charset this is the charset to encode the data with * * @return this returns the encoding of the buffer contents */ public String encode(String charset) throws IOException { InputStream source = open(); int size = (int)count; if(count <= 0) { return new String(); } return convert(source, charset, size); } /** * This method is used to acquire the buffered bytes as a string. * This is useful if the contents need to be manipulated as a * string or transferred into another encoding. This will convert * the bytes using the specified character encoding format. * * @param source this is the source stream that is to be encoded * @param charset this is the charset to encode the data with * @param count this is the number of bytes to be encoded * * @return this returns the encoding of the buffer contents */ private String convert(InputStream source, String charset, int count) throws IOException { byte[] buffer = new byte[count]; int left = count; while(left > 0) { int size = source.read(buffer, 0, left); if(size == -1) { throw new BufferException("Could not read buffer"); } left -= count; } return new String(buffer, charset); } /** * This method is used so that a buffer can be represented as a * stream of bytes. This provides a quick means to access the data * that has been written to the buffer. It wraps the buffer within * an input stream so that it can be read directly. * * @return a stream that can be used to read the buffered bytes */ public InputStream open() throws IOException { if(!closed) { close(); } return open(file); } /** * This method is used so that a buffer can be represented as a * stream of bytes. This provides a quick means to access the data * that has been written to the buffer. It wraps the buffer within * an input stream so that it can be read directly. * * @param file this is the file used to create the input stream * * @return a stream that can be used to read the buffered bytes */ private InputStream open(File file) throws IOException { InputStream source = new FileInputStream(file); if(count <= 0) { source.close(); // release file descriptor } return new Range(source, count); } /** * This will clear all data from the buffer. This simply sets the * count to be zero, it will not clear the memory occupied by the * instance as the internal buffer will remain. This allows the * memory occupied to be reused as many times as is required. */ public void clear() throws IOException { if(closed) { throw new BufferException("Buffer has been closed"); } } /** * This method is used to ensure the buffer can be closed. Once * the buffer is closed it is an immutable collection of bytes and * can not longer be modified. This ensures that it can be passed * by value without the risk of modification of the bytes. */ public void close() throws IOException { if(!closed) { buffer.close(); closed = true; } if(segment != null) { segment.close(); } } /** * This is used to provide the number of bytes that have been * written to the buffer. This increases as bytes are appended * to the buffer. if the buffer is cleared this resets to zero. * * @return this returns the number of bytes within the buffer */ public long length() { return count; } /** * The Segment object is used to create a segment of * the parent buffer. The segment will write to the parent however * if can be read as a unique range of bytes starting with the * first sequence of bytes appended to the segment. A segment can * be used to create a collection of buffers backed by the same * underlying file, as is require with multipart uploads. */ private class Segment implements Buffer { /** * This is an internal segment created from this buffer object. */ private Segment segment; /** * This is the parent buffer that bytes are to be appended to. */ private Buffer parent; /** * This is the offset of the first byte within the sequence. */ private long first; /** * This is the last byte within the segment for this segment. */ private long last; /** * This determines if the segment is currently open or closed. */ private boolean closed; /** * Constructor for the Segment object. This is used * to create a segment from a parent buffer. A segment is a part * of the parent buffer and appends its bytes to the parent. It * can however be treated as an independent source of bytes. * * @param parent this is the parent buffer to be appended to * @param first this is the offset for the first byte in this */ public Segment(Buffer parent, long first) { this.parent = parent; this.first = first; this.last = first; } /** * This is used to allocate a segment within this buffer. If the * buffer is closed this will throw an exception, if however the * buffer is still open then a segment is created which will * write all appended data to this buffer. However it can be * treated as an independent source of data. * * @return this returns a buffer which is a segment of this */ public Buffer allocate() throws IOException { if(closed) { throw new BufferException("Buffer has been closed"); } if(segment != null) { segment.close(); } if(!closed) { segment = new Segment(this, last); } return segment; } /** * This is used to append the specified data to the underlying * file. All bytes appended to the file can be consumed at a * later stage by acquiring the InputStream from * this buffer. Also if require the data can be encoded as a * string object in a required character set. * * @param array this is the array to write the the file * * @return this returns this buffer for further operations */ public Buffer append(byte[] array) throws IOException { return append(array, 0, array.length); } /** * This is used to append the specified data to the underlying * file. All bytes appended to the file can be consumed at a * later stage by acquiring the InputStream from * this buffer. Also if require the data can be encoded as a * string object in a required character set. * * @param array this is the array to write the the file * @param off this is the offset within the array to write * @param size this is the number of bytes to be appended * * @return this returns this buffer for further operations */ public Buffer append(byte[] array, int off, int size) throws IOException { if(closed) { throw new BufferException("Buffer has been closed"); } if(size > 0) { parent.append(array, off, size); last += size; } return this; } /** * This method is used to acquire the buffered bytes as a string. * This is useful if the contents need to be manipulated as a * string or transferred into another encoding. If the UTF-8 * content encoding is not supported the platform default is * used, however this is unlikely as UTF-8 should be supported. * * @return this returns a UTF-8 encoding of the buffer contents */ public String encode() throws IOException { return encode("UTF-8"); } /** * This method is used to acquire the buffered bytes as a string. * This is useful if the contents need to be manipulated as a * string or transferred into another encoding. This will convert * the bytes using the specified character encoding format. * * @param charset this is the charset to encode the data with * * @return this returns the encoding of the buffer contents */ public String encode(String charset) throws IOException { InputStream source = open(); long count = last - first; int size = (int)count; if(count <= 0) { return new String(); } return convert(source, charset, size); } /** * This method is used so that a buffer can be represented as a * stream of bytes. This provides a quick means to access the data * that has been written to the buffer. It wraps the buffer within * an input stream so that it can be read directly. * * @return a stream that can be used to read the buffered bytes */ public InputStream open() throws IOException { InputStream source = new FileInputStream(file); long length = last - first; if(first > 0) { source.skip(first); } return new Range(source, length); } /** * This will clear all data from the buffer. This simply sets the * count to be zero, it will not clear the memory occupied by the * instance as the internal buffer will remain. This allows the * memory occupied to be reused as many times as is required. */ public void clear() throws IOException { if(closed) { throw new BufferException("Buffer is closed"); } } /** * This method is used to ensure the buffer can be closed. Once * the buffer is closed it is an immutable collection of bytes and * can not longer be modified. This ensures that it can be passed * by value without the risk of modification of the bytes. */ public void close() throws IOException { if(!closed) { closed = true; } if(segment != null) { segment.close(); } } /** * This determines how much space is left in the buffer. If there * is no limit to the buffer size this will return the maximum * long value. Typically this is the capacity minus the length. * * @return this is the space that is available within the buffer */ public long space() { return Long.MAX_VALUE; } /** * This is used to provide the number of bytes that have been * written to the buffer. This increases as bytes are appended * to the buffer. if the buffer is cleared this resets to zero. * * @return this returns the number of bytes within the buffer */ public long length() { return last - first; } } /** * The Range object is used to provide a stream that * can read a range of bytes from a provided input stream. This * allows buffer segments to be allocated from the main buffer. * Providing a range in this manner ensures that only one backing * file is needed for the primary buffer allocated. */ private class Range extends FilterInputStream { /** * This is the length of the bytes that exist in the range. */ private long length; /** * This is used to close the stream once it has been read. */ private boolean closed; /** * Constructor for the Range object. This ensures * that only a limited number of bytes can be consumed from a * backing input stream giving the impression of an independent * stream of bytes for a segmented region of the parent buffer. * * @param source this is the input stream used to read data * @param length this is the number of bytes that can be read */ public Range(InputStream source, long length) { super(source); this.length = length; } /** * This will read data from the underlying stream up to the * number of bytes this range is allowed to read. When all of * the bytes are exhausted within the stream this returns -1. * * @return this returns the octet from the underlying stream */ @Override public int read() throws IOException { if(length-- > 0) { return in.read(); } if(length <= 0) { close(); } return -1; } /** * This will read data from the underlying stream up to the * number of bytes this range is allowed to read. When all of * the bytes are exhausted within the stream this returns -1. * * @param array this is the array to read the bytes in to * @param off this is the start offset to append the bytes to * @param size this is the number of bytes that are required * * @return this returns the number of bytes that were read */ @Override public int read(byte[] array, int off, int size) throws IOException { int left = (int)Math.min(length, size); if(left > 0) { int count = in.read(array, off, left); if(count > 0){ length -= count; } if(length <= 0) { close(); } return count; } return -1; } /** * This returns the number of bytes that can be read from the * range. This will be the actual number of bytes the range * contains as the underlying file will not block reading. * * @return this returns the number of bytes within the range */ @Override public int available() throws IOException { return (int)length; } /** * This is the number of bytes to skip from the buffer. This * will allow up to the number of remaining bytes within the * range to be read. When all the bytes have been read this * will return zero indicating no bytes were skipped. * * @param size this returns the number of bytes to skip * * @return this returns the number of bytes that were skipped */ @Override public long skip(long size) throws IOException { long left = Math.min(length, size); long skip = in.skip(left); if(skip > 0) { length -= skip; } if(length <= 0) { close(); } return skip; } /** * This is used to close the range once all of the content has * been fully read. The Range object forces the * close of the stream once all the content has been consumed * to ensure that excessive file descriptors are used. Also * this will ensure that the files can be deleted. */ @Override public void close() throws IOException { if(!closed) { in.close(); closed =true; } } } }