| /* |
| * FileBuffer.java February 2008 |
| * |
| * Copyright (C) 2008, Niall Gallagher <niallg@users.sf.net> |
| * |
| * 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 <code>FileBuffer</code> 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 <code>InputStream</code> 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 <code>FileBuffer</code> 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 <code>InputStream</code> 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 <code>InputStream</code> 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 <code>Segment</code> 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 <code>Segment</code> 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 <code>InputStream</code> 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 <code>InputStream</code> 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 <code>Range</code> 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 <code>Range</code> 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 <code>Range</code> 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; |
| } |
| } |
| } |
| } |