summaryrefslogtreecommitdiffstats
path: root/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileBuffer.java
diff options
context:
space:
mode:
Diffstat (limited to 'simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileBuffer.java')
-rw-r--r--simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileBuffer.java622
1 files changed, 622 insertions, 0 deletions
diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileBuffer.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileBuffer.java
new file mode 100644
index 0000000..8fcea77
--- /dev/null
+++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileBuffer.java
@@ -0,0 +1,622 @@
+/*
+ * 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;
+ }
+ }
+ }
+}