diff options
Diffstat (limited to 'simple/simple-common/src')
57 files changed, 6753 insertions, 0 deletions
diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/KeyMap.java b/simple/simple-common/src/main/java/org/simpleframework/common/KeyMap.java new file mode 100644 index 00000000..6f450bbd --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/KeyMap.java @@ -0,0 +1,93 @@ +/* + * KeyMap.java May 2007 + * + * Copyright (C) 2007, 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; + +/** + * The <code>KeyMap</code> object is used to represent a map of values + * keyed using a known string. This also ensures that the keys and + * the values added to this hash map can be acquired in an independent + * list of values, ensuring that modifications to the map do not have + * an impact on the lists provided, and vice versa. The key map can + * also be used in a fore each look using the string keys. + * + * @author Niall Gallagher + */ +public class KeyMap<T> extends LinkedHashMap<String, T> implements Iterable<String> { + + /** + * Constructor for the <code>KeyMap</code> object. This creates + * a hash map that can expose the keys and values of the map as + * an independent <code>List</code> containing the values. This + * can also be used within a for loop for convenience. + */ + public KeyMap() { + super(); + } + + /** + * This is used to produce an <code>Iterator</code> of values + * that can be used to acquire the contents of the key map within + * a for each loop. The key map can be modified while it is been + * iterated as the iterator is an independent list of values. + * + * @return this returns an iterator of the keys in the map + */ + public Iterator<String> iterator() { + return getKeys().iterator(); + } + + /** + * This is used to produce a <code>List</code> of the keys in + * the map. The list produced is a copy of the internal keys and + * so can be modified and used without affecting this map object. + * + * @return this returns an independent list of the key values + */ + public List<String> getKeys() { + Set<String> keys = keySet(); + + if(keys == null) { + return new ArrayList<String>(); + } + return new ArrayList<String>(keys); + } + + /** + * This is used to produce a <code>List</code> of the values in + * the map. The list produced is a copy of the internal values and + * so can be modified and used without affecting this map object. + * + * @return this returns an independent list of the values + */ + public List<T> getValues() { + Collection<T> values = values(); + + if(values == null) { + return new ArrayList<T>(); + } + return new ArrayList<T>(values); + } + }
\ No newline at end of file diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/Allocator.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/Allocator.java new file mode 100644 index 00000000..aa20b76c --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/Allocator.java @@ -0,0 +1,55 @@ +/* + * Allocator.java February 2001 + * + * Copyright (C) 2001, 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.IOException; + +/** + * The <code>Allocator</code> interface is used to describe a resource + * that can allocate a buffer. This is used so that memory allocation + * can be implemented as a strategy allowing many different sources of + * memory. Typically memory will be allocated as an array of bytes but + * can be a mapped region of shared memory or a file. + * + * @author Niall Gallagher + */ +public interface Allocator { + + /** + * This method is used to allocate a default buffer. Typically this + * will allocate a buffer of predetermined size, allowing it to + * grow to an upper limit to accommodate extra data. If the buffer + * can not be allocated for some reason this throws an exception. + * + * @return this returns an allocated buffer with a default size + */ + Buffer allocate() throws IOException; + + /** + * This method is used to allocate a default buffer. This is used + * to allocate a buffer of the specified size, allowing it to + * grow to an upper limit to accommodate extra data. If the buffer + * can not be allocated for some reason this throws an exception. + * + * @param size this is the initial capacity the buffer should have + * + * @return this returns an allocated buffer with a specified size + */ + Buffer allocate(long size) throws IOException; +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/ArrayAllocator.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/ArrayAllocator.java new file mode 100644 index 00000000..e2dbb3c7 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/ArrayAllocator.java @@ -0,0 +1,111 @@ +/* + * ArrayAllocator.java February 2001 + * + * Copyright (C) 2001, 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.IOException; + +/** + * The <code>ArrayAllocator</code> object is used to provide a means + * to allocate buffers using a single byte array. This essentially uses + * the heap to allocate all buffers. As a result the performance of the + * resulting buffers is good, however for very large buffers this will + * use quote allot of the usable heap space. For very large buffers a + * mapped region of shared memory of a file should be considered. + * + * @author Niall Gallagher + */ +public class ArrayAllocator implements Allocator { + + /** + * This represents the largest portion of memory that is allowed. + */ + private int limit; + + /** + * This represents the default capacity of all allocated buffers. + */ + private int size; + + /** + * Constructor for the <code>ArrayAllocator</code> object. This is + * used to instantiate the allocator with a default buffer size of + * half a kilobyte. This ensures that it can be used for general + * purpose byte storage and for minor I/O tasks. + */ + public ArrayAllocator() { + this(512); + } + + /** + * Constructor for the <code>ArrayAllocator</code> object. This is + * used to instantiate the allocator with a specified buffer size. + * This is typically used when a very specific buffer capacity is + * required, for example a request body with a known length. + * + * @param size the initial capacity of the allocated buffers + */ + public ArrayAllocator(int size) { + this(size, 1048576); + } + + /** + * Constructor for the <code>ArrayAllocator</code> object. This is + * used to instantiate the allocator with a specified buffer size. + * This is typically used when a very specific buffer capacity is + * required, for example a request body with a known length. + * + * @param size the initial capacity of the allocated buffers + * @param limit this is the maximum buffer size created by this + */ + public ArrayAllocator(int size, int limit) { + this.limit = Math.max(size, limit); + this.size = size; + } + + /** + * This method is used to allocate a default buffer. This will + * allocate a buffer of predetermined size, allowing it to grow + * to an upper limit to accommodate extra data. If the buffer + * requested is larger than the limit an exception is thrown. + * + * @return this returns an allocated buffer with a default size + */ + public Buffer allocate() throws IOException { + return allocate(size); + } + + /** + * This method is used to allocate a default buffer. This will + * allocate a buffer of predetermined size, allowing it to grow + * to an upper limit to accommodate extra data. If the buffer + * requested is larger than the limit an exception is thrown. + * + * @param size the initial capacity of the allocated buffer + * + * @return this returns an allocated buffer with a default size + */ + public Buffer allocate(long size) throws IOException { + int required = (int)size; + + if(size > limit) { + throw new BufferException("Specified size %s beyond limit", size); + } + return new ArrayBuffer(required, limit); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/ArrayBuffer.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/ArrayBuffer.java new file mode 100644 index 00000000..972ad924 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/ArrayBuffer.java @@ -0,0 +1,397 @@ +/* + * ArrayBuffer.java February 2001 + * + * Copyright (C) 2001, 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * The <code>ArrayBuffer</code> is intended to be a general purpose + * byte buffer that stores bytes in an single internal byte array. The + * intended use of this buffer is to provide a simple buffer object to + * read and write bytes with. In particular this provides a high + * performance buffer that can be used to read and write bytes fast. + * <p> + * This provides several convenience methods which make the use of the + * buffer easy and useful. This buffer allows an initial capacity to be + * specified however if there is a need for extra space to be added to + * buffer then the <code>append</code> methods will expand the capacity + * of the buffer as needed. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.buffer.ArrayAllocator + */ +public class ArrayBuffer implements Buffer { + + /** + * This is the internal array used to store the buffered bytes. + */ + private byte[] buffer; + + /** + * This is used to determine whether this buffer has been closed. + */ + private boolean closed; + + /** + * This is the count of the number of bytes buffered. + */ + private int count; + + /** + * This is the maximum allowable buffer capacity for this. + */ + private int limit; + + /** + * Constructor for the <code>ArrayBuffer</code> object. The initial + * capacity of the default buffer object is set to 16, the capacity + * will be expanded when the append methods are used and there is + * not enough space to accommodate the extra bytes. + */ + public ArrayBuffer() { + this(16); + } + + /** + * Constructor for the <code>ArrayBuffer</code> object. The initial + * capacity of the buffer object is set to given size, the capacity + * will be expanded when the append methods are used and there is + * not enough space to accommodate the extra bytes. + * + * @param size the initial capacity of this buffer instance + */ + public ArrayBuffer(int size) { + this(size, size); + } + + /** + * Constructor for the <code>ArrayBuffer</code> object. The initial + * capacity of the buffer object is set to given size, the capacity + * will be expanded when the append methods are used and there is + * not enough space to accommodate the extra bytes. + * + * @param size the initial capacity of this buffer instance + * @param limit this is the maximum allowable buffer capacity + */ + public ArrayBuffer(int size, int limit) { + this.buffer = new byte[size]; + this.limit = limit; + } + + /** + * This method is used so that the 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() { + return new ByteArrayInputStream(buffer, 0, count); + } + + /** + * This method is used to allocate a segment of this buffer as a + * separate buffer object. This allows the buffer to be sliced in + * to several smaller independent buffers, while still allowing the + * parent buffer to manage a single buffer. This is useful if the + * parent is split in to logically smaller segments. + * + * @return this returns a buffer which is a segment of this buffer + */ + public Buffer allocate() throws IOException { + return new Segment(this,count); + } + + /** + * 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. + * + * @return this returns the encoding of the buffer contents + */ + public String encode(String charset) throws IOException { + return new String(buffer,0,count, charset); + } + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * + * @return this returns this buffer for another operation + */ + public Buffer append(byte[] array) throws IOException { + return append(array, 0, array.length); + } + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * @param off this is the offset to begin reading the bytes from + * @param size the number of bytes to be read from the array + * + * @return this returns this buffer for another operation + */ + public Buffer append(byte[] array, int off, int size) throws IOException { + if(closed) { + throw new BufferException("Buffer is closed"); + } + if(size + count > buffer.length) { + expand(count + size); + } + if(size > 0) { + System.arraycopy(array, off, buffer, count, size); + count += size; + } + return this; + } + + /** + * This is used to ensure that there is enough space in the buffer + * to allow for more bytes to be added. If the buffer is already + * larger than the required capacity the this will do nothing. + * + * @param capacity the minimum size needed for this buffer object + */ + private void expand(int capacity) throws IOException { + if(capacity > limit) { + throw new BufferException("Capacity limit %s exceeded", limit); + } + int resize = buffer.length * 2; + int size = Math.max(capacity, resize); + byte[] temp = new byte[size]; + + System.arraycopy(buffer, 0, temp, 0, count); + buffer = temp; + } + + /** + * 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"); + } + count = 0; + } + + /** + * 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 { + closed = true; + } + + /** + * 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; + } + + /** + * A <code>Segment</code> represents a segment within a buffer. It + * is used to allow a buffer to be split in to several logical parts + * without the need to create several separate buffers. This means + * that the buffer can be represented in a single memory space, as + * both a single large buffer and as several individual buffers. + */ + private class Segment implements Buffer { + + /** + * This is the parent buffer which is used for collecting data. + */ + private Buffer parent; + + /** + * This is used to determine if the buffer has closed or not. + */ + private boolean closed; + + /** + * This represents the start of the segment within the buffer. + */ + private int start; + + /** + * This represents the number of bytes this segment contains. + */ + private int length; + + /** + * Constructor for the <code>Segment</code> object. This is used + * to create a buffer within a buffer. A segment is a region of + * bytes within the original buffer. It allows the buffer to be + * split in to several logical parts of a single buffer. + * + * @param parent this is the parent buffer used to append to + * @param start this is the start within the buffer to read + */ + public Segment(Buffer parent, int start) { + this.parent = parent; + this.start = start; + } + + /** + * This method is used so that the 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 { + return new ByteArrayInputStream(buffer,start,length); + } + + /** + * This method is used to allocate a segment of this buffer as a + * separate buffer object. This allows the buffer to be sliced in + * to several smaller independent buffers, while still allowing the + * parent buffer to manage a single buffer. This is useful if the + * parent is split in to logically smaller segments. + * + * @return this returns a buffer which is a segment of this buffer + */ + public Buffer allocate() throws IOException { + return new Segment(this,count); + } + + /** + * 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. + * + * @return this returns the encoding of the buffer contents + */ + public String encode(String charset) throws IOException { + return new String(buffer,start,length, charset); + } + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + */ + public Buffer append(byte[] array) throws IOException { + return append(array, 0, array.length); + } + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * @param off this is the offset to begin reading the bytes from + * @param size the number of bytes to be read from the array + */ + public Buffer append(byte[] array, int off, int size) throws IOException { + if(closed) { + throw new BufferException("Buffer is closed"); + } + if(size > 0) { + parent.append(array, off, size); + length += size; + } + return this; + } + + /** + * 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 { + length = 0; + } + + /** + * 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 { + closed = true; + } + + /** + * 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 length; + } + } +} + diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/Buffer.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/Buffer.java new file mode 100644 index 00000000..ebde2807 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/Buffer.java @@ -0,0 +1,129 @@ +/* + * Buffer.java February 2001 + * + * Copyright (C) 2001, 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.IOException; +import java.io.InputStream; + +/** + * The <code>Buffer</code> interface represents a collection of bytes + * that can be written to and later read. This is used to provide a + * region of memory is such a way that the underlying representation + * of that memory is independent of its use. Typically buffers are + * implemented as either allocated byte arrays or files. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.buffer.Allocator + */ +public interface Buffer { + + /** + * This method is used to allocate a segment of this buffer as a + * separate buffer object. This allows the buffer to be sliced in + * to several smaller independent buffers, while still allowing the + * parent buffer to manage a single buffer. This is useful if the + * parent is split in to logically smaller segments. + * + * @return this returns a buffer which is a segment of this buffer + */ + Buffer allocate() throws IOException; + + /** + * 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 + */ + InputStream open() throws IOException; + + /** + * 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 + */ + String encode() throws IOException; + + /** + * 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 + */ + String encode(String charset) throws IOException; + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * + * @return this returns this buffer for another operation + */ + Buffer append(byte[] array) throws IOException; + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * @param len the number of bytes to be read from the array + * @param off this is the offset to begin reading the bytes from + * + * @return this returns this buffer for another operation + */ + Buffer append(byte[] array, int off, int len) throws IOException; + + /** + * 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. + */ + void clear() throws IOException; + + /** + * 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. + */ + void close() throws IOException; + + /** + * 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 + */ + long length(); +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/BufferAllocator.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/BufferAllocator.java new file mode 100644 index 00000000..033ba3ab --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/BufferAllocator.java @@ -0,0 +1,229 @@ +/* + * BufferAllocator.java February 2001 + * + * Copyright (C) 2001, 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.IOException; +import java.io.InputStream; + +/** + * The <code>BufferAllocator</code> object is used to provide a means + * to allocate buffers using a single underlying buffer. This uses a + * buffer from a existing allocator to create the region of memory to + * use to allocate all other buffers. As a result this allows a single + * buffer to acquire the bytes in a number of associated buffers. This + * has the advantage of allowing bytes to be read in sequence without + * joining data from other buffers or allocating multiple regions. + * + * @author Niall Gallagher + */ +public class BufferAllocator extends FilterAllocator implements Buffer { + + /** + * This is the underlying buffer all other buffers are within. + */ + private Buffer buffer; + + /** + * Constructor for the <code>BufferAllocator</code> object. This is + * used to instantiate the allocator with a default buffer size of + * half a kilobyte. This ensures that it can be used for general + * purpose byte storage and for minor I/O tasks. + * + * @param source this is where the underlying buffer is allocated + */ + public BufferAllocator(Allocator source) { + super(source); + } + + /** + * Constructor for the <code>BufferAllocator</code> object. This is + * used to instantiate the allocator with a specified buffer size. + * This is typically used when a very specific buffer capacity is + * required, for example a request body with a known length. + * + * @param source this is where the underlying buffer is allocated + * @param capacity the initial capacity of the allocated buffers + */ + public BufferAllocator(Allocator source, long capacity) { + super(source, capacity); + } + + /** + * Constructor for the <code>BufferAllocator</code> object. This is + * used to instantiate the allocator with a specified buffer size. + * This is typically used when a very specific buffer capacity is + * required, for example a request body with a known length. + * + * @param source this is where the underlying buffer is allocated + * @param capacity the initial capacity of the allocated buffers + * @param limit this is the maximum buffer size created by this + */ + public BufferAllocator(Allocator source, long capacity, long limit) { + super(source, capacity, limit); + } + + /** + * 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(buffer == null) { + allocate(); + } + return buffer.open(); + } + /** + * 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 { + if(buffer == null) { + allocate(); + } + return buffer.encode(); + } + + /** + * 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. + * + * @return this returns the encoding of the buffer contents + */ + public String encode(String charset) throws IOException { + if(buffer == null) { + allocate(); + } + return buffer.encode(charset); + } + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * + * @return this returns this buffer for another operation + */ + public Buffer append(byte[] array) throws IOException { + return append(array, 0, array.length); + } + + /** + * This method is used to append bytes to the end of the buffer. + * This will expand the capacity of the buffer if there is not + * enough space to accommodate the extra bytes. + * + * @param array this is the byte array to append to this buffer + * @param size the number of bytes to be read from the array + * @param off this is the offset to begin reading the bytes from + * + * @return this returns this buffer for another operation + */ + public Buffer append(byte[] array, int off, int size) throws IOException { + if(buffer == null) { + allocate(size); + } + return buffer.append(array, off, size); + } + + /** + * 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(buffer != null) { + buffer.clear(); + } + } + + /** + * 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(buffer == null) { + allocate(); + } + buffer.close(); + } + + /** + * This method is used to allocate a default buffer. This will + * allocate a buffer of predetermined size, allowing it to grow + * to an upper limit to accommodate extra data. If the buffer + * requested is larger than the limit an exception is thrown. + * + * @return this returns an allocated buffer with a default size + */ + @Override + public Buffer allocate() throws IOException { + return allocate(capacity); + } + + /** + * This method is used to allocate a default buffer. This will + * allocate a buffer of predetermined size, allowing it to grow + * to an upper limit to accommodate extra data. If the buffer + * requested is larger than the limit an exception is thrown. + * + * @param size the initial capacity of the allocated buffer + * + * @return this returns an allocated buffer with a default size + */ + @Override + public Buffer allocate(long size) throws IOException { + if(size > limit) { + throw new BufferException("Specified size %s beyond limit", size); + } + if(capacity > size) { // lazily create backing buffer + size = capacity; + } + if(buffer == null) { + buffer = source.allocate(size); + } + return buffer.allocate(); + } + + /** + * 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 buffer.length(); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/BufferException.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/BufferException.java new file mode 100644 index 00000000..4ec2019e --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/BufferException.java @@ -0,0 +1,43 @@ +/* + * BufferException.java February 2001 + * + * Copyright (C) 2001, 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.IOException; + +/** + * The <code>BufferException</code> is used to report problems that + * can occur during the use or allocation of a buffer. Typically + * this is thrown if the upper capacity limit is exceeded. + * + * @author Niall Gallagher + */ +public class BufferException extends IOException { + + /** + * Constructor for the <code>BufferException</code> object. The + * exception can be provided with a message describing the issue + * that has arisen in the use or allocation of the buffer. + * + * @param format this is the template for the exception + * @param values these are the values to be added to the template + */ + public BufferException(String format, Object... values) { + super(String.format(format, values)); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileAllocator.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileAllocator.java new file mode 100644 index 00000000..c91b1dc5 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileAllocator.java @@ -0,0 +1,137 @@ +/* + * FileAllocator.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.IOException; + +/** + * The <code>FileAllocator</code> object is used to create buffers + * that can be written to the file system. This creates buffers as + * files if they are larger than the specified limit. This ensures + * that buffers of arbitrary large size can be created. All buffer + * sizes under the limit are created using byte arrays allocated + * on the executing VM heap. This ensures that optimal performance + * is maintained for buffers of reasonable size. + * + * @author Niall Gallagher + */ +public class FileAllocator implements Allocator { + + /** + * This is the default prefix used when none has been specified. + */ + private static final String PREFIX = "temp"; + + /** + * This is the file manager used to create the buffer files. + */ + private FileWatcher manager; + + /** + * This is the limit up to which buffers are allocated in memory. + */ + private int limit; + + /** + * Constructor for the <code>FileAllocator</code> object. This is + * used to create buffers in memory up to a threshold size. If a + * buffer is required over the threshold size then the data is + * written to a file, where it can be retrieved at a later point. + */ + public FileAllocator() { + this(1048576); + } + + /** + * Constructor for the <code>FileAllocator</code> object. This is + * used to create buffers in memory up to a threshold size. If a + * buffer is required over the threshold size then the data is + * written to a file, where it can be retrieved at a later point. + * + * @param limit this is the maximum size for a heap buffer + */ + public FileAllocator(int limit) { + this(PREFIX, limit); + } + + /** + * Constructor for the <code>FileAllocator</code> object. This is + * used to create buffers in memory up to a threshold size. If a + * buffer is required over the threshold size then the data is + * written to a file, where it can be retrieved at a later point. + * + * @param prefix this is the file prefix for the file buffers + */ + public FileAllocator(String prefix) { + this(prefix, 1048576); + } + + /** + * Constructor for the <code>FileAllocator</code> object. This is + * used to create buffers in memory up to a threshold size. If a + * buffer is required over the threshold size then the data is + * written to a file, where it can be retrieved at a later point. + * + * @param prefix this is the file prefix for the file buffers + * @param limit this is the maximum size for a heap buffer + */ + public FileAllocator(String prefix, int limit) { + this.manager = new FileWatcher(prefix); + this.limit = limit; + } + + /** + * This will allocate a file buffer which will write data for the + * buffer to a file. Buffers allocated by this method can be of + * arbitrary size as data is appended directly to a temporary + * file. This ensures there is no upper limit for appended data. + * + * @return a buffer which will write to a temporary file + */ + public Buffer allocate() throws IOException { + File file = manager.create(); + + if(!file.exists()) { + throw new BufferException("Could not create file %s", file); + } + return new FileBuffer(file); + } + + /** + * This will allocate a file buffer which will write data for the + * buffer to a file. Buffers allocated by this method can be of + * arbitrary size as data is appended directly to a temporary + * file. This ensures there is no upper limit for appended data. + * If the size required is less than the limit then the buffer + * is an in memory array which provides optimal performance. + * + * @param size this is the size of the buffer to be created + * + * @return a buffer which will write to a created temporary file + */ + public Buffer allocate(long size) throws IOException { + int required = (int)size; + + if(size <= limit) { + return new ArrayBuffer(required); + } + return allocate(); + } +} 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 00000000..8fcea77e --- /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; + } + } + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileWatcher.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileWatcher.java new file mode 100644 index 00000000..6d1f3b27 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FileWatcher.java @@ -0,0 +1,179 @@ +/* + * FileWatcher.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.FileFilter; +import java.io.IOException; + +/** + * The <code>FileWatcher</code> object is used to create files that + * are to be used for file buffers. All files created by this are + * created in the <code>java.io.tmpdir</code> path. Temporary files + * created in this directory last for a configurable length of time + * before they are deleted. + * + * @author Niall Gallagher + */ +class FileWatcher implements FileFilter { + + /** + * This is the prefix for the temporary files created. + */ + private final String prefix; + + /** + * This is the duration the files created will exist for. + */ + private final long duration; + + /** + * Constructor for the <code>FileWatcher</code> object. This will + * allow temporary files to exist for five minutes. After this + * time the will be removed from the underlying directory. Any + * request for a new file will result in a sweep of the temporary + * directory for all matching files, if they have expired they + * will be deleted. + * + * @param prefix this is the file name prefix for the files + */ + public FileWatcher(String prefix) { + this(prefix, 300000); + } + + /** + * Constructor for the <code>FileWatcher</code> object. This will + * allow temporary files to exist for a configurable length of time. + * After this time the will be removed from the underlying directory. + * Any request for a new file will result in a sweep of the temporary + * directory for all matching files, if they have expired they + * will be deleted. + * + * @param prefix this is the file name prefix for the files + * @param duration this is the duration the files exist for + */ + public FileWatcher(String prefix, long duration) { + this.duration = duration; + this.prefix = prefix; + } + + /** + * This will create a temporary file which can be used as a buffer + * for <code>FileBuffer</code> objects. The file returned by this + * method will be created before it is returned, which ensures it + * can be used as a means to buffer bytes. All files are created + * in the <code>java.io.tmpdir</code> location, which represents + * the underlying file system temporary file destination. + * + * @return this returns a created temporary file for buffers + */ + public File create() throws IOException { + File path = create(prefix); + + if(!path.isDirectory()) { + File parent = path.getParentFile(); + + if(parent.isDirectory()) { + clean(parent); + } + } + return path; + } + + /** + * This will create a temporary file which can be used as a buffer + * for <code>FileBuffer</code> objects. The file returned by this + * method will be created before it is returned, which ensures it + * can be used as a means to buffer bytes. All files are created + * in the <code>java.io.tmpdir</code> location, which represents + * the underlying file system temporary file destination. + * + * @param prefix this is the prefix of the file to be created + * + * @return this returns a created temporary file for buffers + */ + private File create(String prefix) throws IOException { + File file = File.createTempFile(prefix, null); + + if(!file.exists()) { + file.createNewFile(); + } + return file; + } + + /** + * When this method is invoked the files that match the pattern + * of the temporary files are evaluated for deletion. Only those + * files that have not been modified in the duration period can + * be deleted. This ensures the file system is not exhausted. + * + * @param path this is the path of the file to be evaluated + */ + private void clean(File path) throws IOException { + File[] list = path.listFiles(this); + + for(File next : list) { + for(int i = 0; i < 3; i++) { + if(next.delete()) { + break; + } + } + } + } + + /** + * This determines if the file provided is an acceptable file for + * deletion. Acceptable files are those that match the pattern + * of files created by this file system object. If the file is + * a matching file then it is a candidate for deletion. + * + * @param file this is the file to evaluate for deletion + * + * @return this returns true if the file matches the pattern + */ + public boolean accept(File file) { + String name = file.getName(); + + if(file.isDirectory()) { + return false; + } + return accept(file, name); + } + + /** + * This determines if the file provided is an acceptable file for + * deletion. Acceptable files are those that match the pattern + * of files created by this file system object. If the file is + * a matching file then it is a candidate for deletion. + * + * @param file this is the file to evaluate for deletion + * @param name this is the name of the file to be evaluated + * + * @return this returns true if the file matches the pattern + */ + private boolean accept(File file, String name) { + long time = System.currentTimeMillis(); + long modified = file.lastModified(); + + if(modified + duration > time) { // not yet expired + return false; + } + return name.startsWith(prefix); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FilterAllocator.java b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FilterAllocator.java new file mode 100644 index 00000000..ab974235 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/buffer/FilterAllocator.java @@ -0,0 +1,123 @@ +/* + * FilterAllocator.java February 2001 + * + * Copyright (C) 2001, 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.IOException; + +/** + * The <code>FilterAllocator</code> object is used to provide a means + * to provide a general set of constraints around buffer allocation. + * It can ensure that a minimum capacity is used for default allocation + * and that an upper limit is used for allocation. In general this can + * be used in conjunction with another <code>Allocator</code> which may + * not have such constraints. It ensures that a set of requirements can + * be observed when allocating buffers. + * + * @author Niall Gallagher + */ +public class FilterAllocator implements Allocator { + + /** + * This is the allocator the underlying buffer is allocated with. + */ + protected Allocator source; + + /** + * This is the default initial minimum capacity of the buffer. + */ + protected long capacity; + + /** + * This is the maximum number of bytes that can be allocated. + */ + protected long limit; + + /** + * Constructor for the <code>FilterAllocator</code> object. This is + * used to instantiate the allocator with a default buffer size of + * half a kilobyte. This ensures that it can be used for general + * purpose byte storage and for minor I/O tasks. + * + * @param source this is where the underlying buffer is allocated + */ + public FilterAllocator(Allocator source) { + this(source, 512, 1048576); + } + + /** + * Constructor for the <code>FilterAllocator</code> object. This is + * used to instantiate the allocator with a specified buffer size. + * This is typically used when a very specific buffer capacity is + * required, for example a request body with a known length. + * + * @param source this is where the underlying buffer is allocated + * @param capacity the initial capacity of the allocated buffers + */ + public FilterAllocator(Allocator source, long capacity) { + this(source, capacity, 1048576); + } + + /** + * Constructor for the <code>FilterAllocator</code> object. This is + * used to instantiate the allocator with a specified buffer size. + * This is typically used when a very specific buffer capacity is + * required, for example a request body with a known length. + * + * @param source this is where the underlying buffer is allocated + * @param capacity the initial capacity of the allocated buffers + * @param limit this is the maximum buffer size created by this + */ + public FilterAllocator(Allocator source, long capacity, long limit) { + this.limit = Math.max(capacity, limit); + this.capacity = capacity; + this.source = source; + } + + /** + * This method is used to allocate a default buffer. This will + * allocate a buffer of predetermined size, allowing it to grow + * to an upper limit to accommodate extra data. If the buffer + * requested is larger than the limit an exception is thrown. + * + * @return this returns an allocated buffer with a default size + */ + public Buffer allocate() throws IOException { + return allocate(capacity); + } + + /** + * This method is used to allocate a default buffer. This will + * allocate a buffer of predetermined size, allowing it to grow + * to an upper limit to accommodate extra data. If the buffer + * requested is larger than the limit an exception is thrown. + * + * @param size the initial capacity of the allocated buffer + * + * @return this returns an allocated buffer with a default size + */ + public Buffer allocate(long size) throws IOException { + if(size > limit) { + throw new BufferException("Specified size %s beyond limit", size); + } + if(capacity > size) { + size = capacity; + } + return source.allocate(size); + } +}
\ No newline at end of file diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64Encoder.java b/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64Encoder.java new file mode 100644 index 00000000..54c820ae --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64Encoder.java @@ -0,0 +1,166 @@ +/* + * Base64Encoder.java February 2014 + * + * Copyright (C) 2014, 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.encode; + +/** + * The <code>Base64Encoder</code> is used to encode and decode base64 + * content. The implementation used here provides a reasonably fast + * memory efficient encoder for use with input and output streams. It + * is possible to achieve higher performance, however, ease of use + * and convenience are the priorities with this implementation. This + * can only decode complete blocks. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.encode.Base64OutputStream + * @see org.simpleframework.common.encode.Base64InputStream + */ +public class Base64Encoder { + + /** + * This maintains reference data used to fast decoding. + */ + private static final int[] REFERENCE = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0,}; + + /** + * This contains the base64 alphabet used for encoding. + */ + private static final char[] ALPHABET = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; + + /** + * This method is used to encode the specified byte array of binary + * data in to base64 data. The block is complete and must be decoded + * as a complete block. + * + * @param buf this is the binary data to be encoded + * + * @return this is the base64 encoded value of the data + */ + public static char[] encode(byte[] buf) { + return encode(buf, 0, buf.length); + } + + /** + * This method is used to encode the specified byte array of binary + * data in to base64 data. The block is complete and must be decoded + * as a complete block. + * + * @param buf this is the binary data to be encoded + * @param off this is the offset to read the binary data from + * @param len this is the length of data to encode from the array + * + * @return this is the base64 encoded value of the data + */ + public static char[] encode(byte[] buf, int off, int len) { + char[] text = new char[((len + 2) / 3) * 4]; + int last = off + len; + int a = 0; + int i = 0; + + while (i < last) { + byte one = buf[i++]; + byte two = (i < len) ? buf[i++] : 0; + byte three = (i < len) ? buf[i++] : 0; + + int mask = 0x3F; + text[a++] = ALPHABET[(one >> 2) & mask]; + text[a++] = ALPHABET[((one << 4) | ((two & 0xFF) >> 4)) & mask]; + text[a++] = ALPHABET[((two << 2) | ((three & 0xFF) >> 6)) & mask]; + text[a++] = ALPHABET[three & mask]; + } + switch (len % 3) { + case 1: + text[--a] = '='; + case 2: + text[--a] = '='; + } + return text; + } + + /** + * This is used to decode the provide base64 data back in to an + * array of binary data. The data provided here must be a full block + * of base 64 data in order to be decoded. + * + * @param text this is the base64 text to be decoded + * + * @return this returns the resulting byte array + */ + public static byte[] decode(char[] text) { + return decode(text, 0, text.length); + } + + /** + * This is used to decode the provide base64 data back in to an + * array of binary data. The data provided here must be a full block + * of base 64 data in order to be decoded. + * + * @param text this is the base64 text to be decoded + * @param off this is the offset to read the text data from + * @param len this is the length of data to decode from the text + * + * @return this returns the resulting byte array + */ + public static byte[] decode(char[] text, int off, int len) { + int delta = 0; + + if (text[off + len - 1] == '=') { + delta = text[off + len - 2] == '=' ? 2 : 1; + } + byte[] buf = new byte[len * 3 / 4 - delta]; + int mask = 0xff; + int index = 0; + + for (int i = 0; i < len; i += 4) { + int pos = off + i; + int one = REFERENCE[text[pos]]; + int two = REFERENCE[text[pos + 1]]; + + buf[index++] = (byte) (((one << 2) | (two >> 4)) & mask); + + if (index >= buf.length) { + return buf; + } + int three = REFERENCE[text[pos + 2]]; + + buf[index++] = (byte) (((two << 4) | (three >> 2)) & mask); + + if (index >= buf.length) { + return buf; + } + int four = REFERENCE[text[pos + 3]]; + buf[index++] = (byte) (((three << 6) | four) & mask); + } + return buf; + } + +}
\ No newline at end of file diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64InputStream.java b/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64InputStream.java new file mode 100644 index 00000000..8aa28af5 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64InputStream.java @@ -0,0 +1,123 @@ +/* + * Base64InputStream.java February 2014 + * + * Copyright (C) 2014, 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.encode; + +import java.io.IOException; +import java.io.InputStream; + +/** + * The <code>Base64InputStream</code> is used to read base64 text in + * the form of a string through a conventional input stream. This is + * provided for convenience so that it is possible to encode and + * decode binary data as base64 for implementations that would + * normally use a binary format. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.encode.Base64Encoder + */ +public class Base64InputStream extends InputStream { + + /** + * This is that original base64 text that is to be decoded. + */ + private char[] encoded; + + /** + * This is used to accumulate the decoded text as an array. + */ + private byte[] decoded; + + /** + * This is a temporary buffer used to read one byte at a time. + */ + private byte[] temp; + + /** + * This is the total number of bytes that have been read. + */ + private int count; + + /** + * Constructor for the <code>Base64InputStream</code> object. + * This takes an encoded string and reads it as binary data. + * + * @param source this string containing the encoded data + */ + public Base64InputStream(String source) { + this.encoded = source.toCharArray(); + this.temp = new byte[1]; + } + + /** + * This is used to read the next byte decoded from the text. If + * the data has been fully consumed then this will return the + * standard -1. + * + * @return this returns the next octet decoded + */ + @Override + public int read() throws IOException { + int count = read(temp); + + if (count == -1) { + return -1; + } + return temp[0] & 0xff; + } + + /** + * This is used to read the next byte decoded from the text. If + * the data has been fully consumed then this will return the + * standard -1. + * + * @param array this is the array to decode the text to + * @param offset this is the offset to decode in to the array + * @param this is the number of bytes available to decode to + * + * @return this returns the number of octets decoded + */ + @Override + public int read(byte[] array, int offset, int length) throws IOException { + if (decoded == null) { + decoded = Base64Encoder.decode(encoded); + } + if (count >= decoded.length) { + return -1; + } + int size = Math.min(length, decoded.length - count); + + if (size > 0) { + System.arraycopy(decoded, count, array, offset, size); + count += size; + } + return size; + } + + /** + * This returns the original base64 text that was encoded. This + * is useful for debugging purposes to see the source data. + * + * @return this returns the original base64 text to decode + */ + @Override + public String toString() { + return new String(encoded); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64OutputStream.java b/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64OutputStream.java new file mode 100644 index 00000000..a8f425e3 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/encode/Base64OutputStream.java @@ -0,0 +1,138 @@ +/* + * Base64OutputStream.java February 2014 + * + * Copyright (C) 2014, 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.encode; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * The <code>Base64OutputStream</code> is used to write base64 text + * in the form of a string through a conventional output stream. This + * is provided for convenience so that it is possible to encode and + * decode binary data as base64 for implementations that would + * normally use a binary format. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.encode.Base64Encoder + */ +public class Base64OutputStream extends OutputStream { + + private char[] encoded; + private byte[] buffer; + private byte[] temp; + private int count; + + /** + * Constructor for the <code>Base64OutputStream</code> object. A + * stream created with this constructor uses an initial capacity + * of one kilobyte, the capacity is increased as bytes are written. + */ + public Base64OutputStream() { + this(1024); + } + + /** + * Constructor for the <code>Base64OutputStream</code> object. A + * stream created with this constructor can have an initial capacity + * specified. Typically it is a good rule of thumb to use a capacity + * that is just over an additional third of the source binary data. + * + * @param capacity this is the initial capacity of the buffer + */ + public Base64OutputStream(int capacity) { + this.buffer = new byte[capacity]; + this.temp = new byte[1]; + } + + /** + * This method is used to write data as base64 to an internal buffer. + * The <code>toString</code> method can be used to acquire the text + * encoded from the written binary data. + * + * @param octet the octet to encode in to the internal buffer + */ + @Override + public void write(int octet) throws IOException { + temp[0] = (byte) octet; + write(temp); + } + + /** + * This method is used to write data as base64 to an internal buffer. + * The <code>toString</code> method can be used to acquire the text + * encoded from the written binary data. + * + * @param array the octets to encode to the internal buffer + * @param offset this is the offset in the array to encode from + * @param length this is the number of bytes to be encoded + */ + @Override + public void write(byte[] array, int offset, int length) throws IOException { + if (encoded != null) { + throw new IOException("Stream has been closed"); + } + if (count + length > buffer.length) { + expand(count + length); + } + System.arraycopy(array, offset, buffer, count, length); + count += length; + } + + /** + * This will expand the size of the internal buffer. To allow for + * a variable length number of bytes to be written the internal + * buffer can grow as demand exceeds space available. + * + * @param capacity this is the minimum capacity required + */ + private void expand(int capacity) throws IOException { + int length = Math.max(buffer.length * 2, capacity); + + if (buffer.length < capacity) { + buffer = Arrays.copyOf(buffer, length); + } + } + + /** + * This is used to close the stream and encode the buffered bytes + * to base64. Once this method is invoked no further data can be + * encoded with the stream. The <code>toString</code> method can + * be used to acquire the base64 encoded text. + */ + @Override + public void close() throws IOException { + if (encoded == null) { + encoded = Base64Encoder.encode(buffer, 0, count); + } + } + + /** + * This returns the base64 text encoded from the bytes written to + * the stream. This is the primary means for acquiring the base64 + * encoded text once the stream has been closed. + * + * @return this returns the base64 text encoded + */ + @Override + public String toString() { + return new String(encoded); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/Cleaner.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Cleaner.java new file mode 100644 index 00000000..08d7fb09 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Cleaner.java @@ -0,0 +1,44 @@ +/* + * Cleaner.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +/** + * The <code>Cleaner</code> represents an object that is used to + * clean up after the keyed resource. Typically this is used when + * a <code>Lease</code> referring a resource has expired meaning + * that any memory, file descriptors, or other such limited data + * should be released for the keyed resource. The resource keys + * used should be distinct over time to avoid conflicts. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.lease.Lease + */ +public interface Cleaner<T> { + + /** + * This method is used to clean up after a the keyed resource. + * To ensure that the leasing infrastructure operates properly + * this should not block releasing resources. If required this + * should spawn a thread to perform time consuming tasks. + * + * @param key this is the key for the resource to clean + */ + void clean(T key) throws Exception; +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/Contract.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Contract.java new file mode 100644 index 00000000..ac05682e --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Contract.java @@ -0,0 +1,77 @@ +/* + * Contract.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +/** + * A <code>Contract</code> is used to represent the contract a + * lease has been issued. This contains all relevant information + * regarding the lease, such as the keyed resource that has been + * leased and the duration of the lease. Delays for the contract + * can be measured in any <code>TimeUnit</code> for convinienct. + * + * @author Niall Gallagher + */ +interface Contract<T> extends Delayed { + + /** + * This returns the key for the resource this represents. + * This is used when the contract has expired to clean resources + * associated with the lease. It is passed in to the cleaner as + * an parameter to the callback. The cleaner is then responsible + * for cleaning any resources associated with the lease. + * + * @return returns the resource key that this represents + */ + T getKey(); + + /** + * This method will return the number of <code>TimeUnit</code> + * seconds that remain in the contract. If the value returned is + * less than or equal to zero then it should be assumed that the + * lease has expired, if greater than zero the lease is active. + * + * @return returns the duration in time unit remaining + */ + long getDelay(TimeUnit unit); + + /** + * This method is used to set the number of <code>TimeUnit</code> + * seconds that should remain within the contract. This is used + * when the contract is to be reissued. Once a new duration has + * been set the contract for the lease has been changed and the + * previous expiry time is ignores, so only one clean is called. + * + * @param delay this is the delay to be used for this contract + * @param unit this is the time unit measurment for the delay + */ + void setDelay(long delay, TimeUnit unit); + + /** + * This is used to provide a description of the contract that the + * instance represents. A description well contain the key owned + * by the contract as well as the expiry time expected for it. + * This is used to provide descriptive messages in the exceptions. + * + * @return a descriptive message describing the contract object + */ + String toString(); +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractController.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractController.java new file mode 100644 index 00000000..03536479 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractController.java @@ -0,0 +1,84 @@ +/* + * ContractController.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import org.simpleframework.common.lease.LeaseException; + +/** + * The <code>ContractController</code> forms the interface to the + * lease management system. There are two actions permitted for + * leased resources, these are lease issue and lease renewal. When + * the lease is first issued it is scheduled for the contract + * duration. Once issued the lease can be renewed with another + * duration, which can be less than the previous duration used. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.lease.ContractMaintainer + */ +interface ContractController<T> { + + /** + * This method will establish a contract for the given duration. + * If the contract duration expires before it is renewed then a + * notification is sent, typically to a <code>Cleaner</code> to + * to signify that the resource should be released. The contract + * can also be cancelled by providing a zero length duration. + * + * @param contract a contract representing a leased resource + * + * @exception Exception if the lease could not be done + */ + void issue(Contract<T> contract) throws LeaseException; + + /** + * This ensures that the contract is renewed for the duration on + * the contract, which may have changed since it was issued or + * last renewed. If the duration on the contract has changed this + * will insure the previous contract duration is revoked and the + * new duration is used to maintain the leased resource. + * + * @param contract a contract representing a leased resource + * + * @exception Exception if the lease could not be done + */ + void renew(Contract<T> contract) throws LeaseException; + + /** + * This will cancel the lease and release the resource. This + * has the same effect as the <code>renew</code> method with + * a zero length duration. Once this has been called the + * <code>Cleaner</code> used should be notified immediately. + * If the lease has already expired this throws an exception. + * + * @param contract a contract representing a leased resource + * + * @exception Exception if the expiry has been passed + */ + void cancel(Contract<T> contract) throws LeaseException; + + /** + * This method is used to cancel all outstanding leases and to + * close the controller. Closing the controller ensures that it + * can no longer be used to issue or renew leases. All resources + * occupied by the controller are released, including threads, + * memory, and all leased resources occupied by the instance. + */ + void close(); +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractLease.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractLease.java new file mode 100644 index 00000000..60cd4748 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractLease.java @@ -0,0 +1,119 @@ +/* + * ContractLease.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.TimeUnit; + +/** + * The <code>ContractLease</code> is used to maintain contracts by + * using a controller object. This will invoke the controller with + * the contract when a lease operation is performed. A lease is + * renewed by changing the contract duration and passing that to + * the controller which will reestablish the expiry time for it. + * + * @author Niall Gallagher + */ +class ContractLease<T> implements Lease<T> { + + /** + * This is the controller object used to handle contracts. + */ + private final ContractController<T> handler; + + /** + * This is the contract object representing the lease. + */ + private final Contract<T> contract; + + /** + * Constructor for the <code>ContractLease</code> object. This is + * used to create a lease which will maintain a contract using a + * controller object. Lease renewals are performed by changing the + * expiry duration on the contract and notifying the controller. + * + * @param handler this is used to manage the contract expiration + * @param contract this is the contract representing the lease + */ + public ContractLease(ContractController<T> handler, Contract<T> contract) { + this.handler = handler; + this.contract = contract; + } + + /** + * Determines the duration remaining before the lease expires. + * The expiry is given as the number of <code>TimeUnit</code> + * seconds remaining before the lease expires. If this value is + * negative it should be assumed that the lease has expired. + * + * @param unit this is the time unit used for the duration + * + * @return the duration remaining within this lease instance + * + * @exception LeaseException if the lease expiry has passed + */ + public long getExpiry(TimeUnit unit) throws LeaseException { + return contract.getDelay(unit); + } + + /** + * This ensures that the leased resource is maintained for the + * specified number of <code>TimeUnit</code> seconds. Allowing + * the duration unit to be specified enables the lease system + * to maintain a resource with a high degree of accuracy. The + * accuracy of the leasing system is dependant on how long it + * takes to clean the resource associated with the lease. + * + * @param duration this is the length of time to renew for + * @param unit this is the time unit used for the duration + * + * @exception LeaseException if the expiry has been passed + */ + public void renew(long duration, TimeUnit unit) throws LeaseException { + if(duration >= 0) { + contract.setDelay(duration, unit); + } + handler.renew(contract); + } + + /** + * This will cancel the lease and release the resource. This + * has the same effect as the <code>renew</code> method with + * a zero length duration. Once this has been called the + * <code>Cleaner</code> used should be notified immediately. + * If the lease has already expired this throws an exception. + * + * @exception LeaseException if the expiry has been passed + */ + public void cancel() throws LeaseException { + handler.cancel(contract); + } + + /** + * Provides the key for the resource that this lease represents. + * This can be used to identify the resource should the need + * arise. Also, this provides a convenient means of identifying + * leases when using or storing it as an <code>Object</code>. + * + * @return this returns the key for the resource represented + */ + public T getKey() { + return contract.getKey(); + } +} + diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractMaintainer.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractMaintainer.java new file mode 100644 index 00000000..3e650390 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractMaintainer.java @@ -0,0 +1,115 @@ +/* + * ContractMaintainer.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +/** + * The <code>ContractMaintainer</code> is used provide a controller + * uses a cleaner. This simple delegates to the cleaner queue when + * a renewal is required. Renewals are performed by revoking the + * contract and then reissuing it. This will ensure that the delay + * for expiry of the contract is reestablished within the queue. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.lease.LeaseCleaner + */ +class ContractMaintainer<T> implements ContractController<T> { + + /** + * The queue that is used to issue and revoke contracts. + */ + private final LeaseCleaner<T> queue; + + /** + * Constructor for the <code>ContractMaintainer</code> object. This + * is used to create a controller for contracts which will ensure + * that the lease expiry durations are met. All notifications of + * expiry will be delivered to the provided cleaner instance. + * + * @param cleaner this is used to receive expiry notifications + */ + public ContractMaintainer(Cleaner<T> cleaner) { + this.queue = new LeaseCleaner<T>(cleaner); + } + + /** + * This method will establish a contract for the given duration. + * If the contract duration expires before it is renewed then a + * notification is sent, typically to a <code>Cleaner</code> to + * to signify that the resource should be released. The contract + * can also be cancelled by providing a zero length duration. + * + * @param contract a contract representing a leased resource + */ + public synchronized void issue(Contract<T> contract) { + queue.issue(contract); + } + + /** + * This ensures that the contract is renewed for the duration on + * the contract, which may have changed since it was issued or + * last renewed. If the duration on the contract has changed this + * will insure the previous contract duration is revoked and the + * new duration is used to maintain the leased resource. + * + * @param contract a contract representing a leased resource + */ + public synchronized void renew(Contract<T> contract) { + boolean active = queue.revoke(contract); + + if(!active) { + throw new LeaseException("Lease has expired for " + contract); + } + queue.issue(contract); + } + + /** + * This will cancel the lease and release the resource. This + * has the same effect as the <code>renew</code> method with + * a zero length duration. Once this has been called the + * <code>Cleaner</code> used should be notified immediately. + * If the lease has already expired this throws an exception. + * + * @param contract a contract representing a leased resource + */ + public synchronized void cancel(Contract<T> contract) { + boolean active = queue.revoke(contract); + + if(!active) { + throw new LeaseException("Lease has expired for " + contract); + } + contract.setDelay(0, MILLISECONDS); + queue.issue(contract); + } + + /** + * This method is used to cancel all outstanding leases and to + * close the controller. Closing the controller ensures that it + * can no longer be used to issue or renew leases. All resources + * occupied by the controller are released, including threads, + * memory, and all leased resources occupied by the instance. + * + * @throws LeaseException if the controller can not be closed + */ + public synchronized void close() { + queue.close(); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractQueue.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractQueue.java new file mode 100644 index 00000000..df881dcc --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/ContractQueue.java @@ -0,0 +1,44 @@ +/* + * ContractQueue.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.DelayQueue; + +/** + * The <code>ContraceQueue</code> object is used to queue contracts + * between two asynchronous threads of execution. This allows the + * controller to schedule the lease contract for expiry. Taking the + * contracts from the queue is delayed for the contract duration. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.lease.Contract + */ +class ContractQueue<T> extends DelayQueue<Contract<T>> { + + /** + * Constructor for the <code>ContractQueue</code> object. This + * is used to create a queue for passing contracts between two + * asynchronous threads of execution. This is used by the + * lease controller to schedule the lease contract for expiry. + */ + public ContractQueue() { + super(); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/Expiration.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Expiration.java new file mode 100644 index 00000000..fca1c140 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Expiration.java @@ -0,0 +1,163 @@ +/* + * Expiration.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +/** + * A <code>Expiration</code> is used to represent the expiration + * for a lease. This contains all relevant information for the + * the lease, such as the keyed resource that has been leased and + * the duration of the lease. Durations for the contract can be + * measured in any <code>TimeUnit</code> for convenience. + * + * @author Niall Gallagher + */ +class Expiration<T> implements Contract<T> { + + /** + * This is the expiration time in nanoseconds for this. + */ + private volatile long time; + + /** + * This is the key representing the resource being lease. + */ + private T key; + + /** + * Constructor for the <code>Expiration</code> object. This is used + * to create a contract with an initial expiry period. Once this + * is created the time is taken and the contract can be issued. + * + * @param key this is the key that this contract represents + * @param lease this is the initial lease duration to be used + * @param scale this is the time unit scale that is to be used + */ + public Expiration(T key, long lease, TimeUnit scale) { + this.time = getTime() + scale.toNanos(lease); + this.key = key; + } + + /** + * This returns the key for the resource this represents. + * This is used when the contract has expired to clean resources + * associated with the lease. It is passed in to the cleaner as + * an parameter to the callback. The cleaner is then responsible + * for cleaning any resources associated with the lease. + * + * @return returns the resource key that this represents + */ + public T getKey() { + return key; + } + + /** + * This method will return the number of <code>TimeUnit</code> + * seconds that remain in the contract. If the value returned is + * less than or equal to zero then it should be assumed that the + * lease has expired, if greater than zero the lease is active. + * + * @return returns the duration in the time unit remaining + */ + public long getDelay(TimeUnit unit) { + return unit.convert(time - getTime(), NANOSECONDS); + } + + /** + * This method is used to set the number of <code>TimeUnit</code> + * seconds that should remain within the contract. This is used + * when the contract is to be reissued. Once a new duration has + * been set the contract for the lease has been changed and the + * previous expiry time is ignores, so only one clean is called. + * + * @param delay this is the delay to be used for this contract + * @param unit this is the time unit measurment for the delay + */ + public void setDelay(long delay, TimeUnit unit) { + this.time = getTime() + unit.toNanos(delay); + } + + /** + * This method returns the current time in nanoseconds. This is + * used to allow the duration of the lease to be calculated with + * any given time unit which allows flexibility in setting and + * getting the current delay for the contract. + * + * @return returns the current time in nanoseconds remaining + */ + private long getTime() { + return System.nanoTime(); + } + + /** + * This is used to compare the specified delay to this delay. The + * result of this operation is used to prioritize contracts in + * order of first to expire. Contracts that expire first reach + * the top of the contract queue and are taken off for cleaning. + * + * @param other this is the delay to be compared with this + * + * @return this returns zero if equal otherwise the difference + */ + public int compareTo(Delayed other) { + Expiration value = (Expiration) other; + + if(other == this) { + return 0; + } + return compareTo(value); + } + + /** + * This is used to compare the specified delay to this delay. The + * result of this operation is used to prioritize contracts in + * order of first to expire. Contracts that expire first reach + * the top of the contract queue and are taken off for cleaning. + * + * @param value this is the expiration to be compared with this + * + * @return this returns zero if equal otherwise the difference + */ + private int compareTo(Expiration value) { + long diff = time - value.time; + + if(diff < 0) { + return -1; + } else if(diff > 0) { + return 1; + } + return 0; + } + + /** + * This is used to provide a description of the contract that the + * instance represents. A description well contain the key owned + * by the contract as well as the expiry time expected for it. + * This is used to provide descriptive messages in the exceptions. + * + * @return a descriptive message describing the contract object + */ + public String toString() { + return String.format("contract %s", key); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/Lease.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Lease.java new file mode 100644 index 00000000..d2a97851 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/Lease.java @@ -0,0 +1,85 @@ +/* + * Lease.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.TimeUnit; + +/** + * The <code>Lease</code> object is used to keep a keyed resource + * active. This provides a very simple lease that can be used to + * track the activity of a resource or system. Keeping track of + * activity allows resources to be maintained until such time + * that they are no longer required, allowing the server to clean + * up any allocated memory, file descriptors, or other such data. + * + * @author Niall Gallagher + */ +public interface Lease<T> { + + /** + * Determines the duration remaining before the lease expires. + * The expiry is given as the number of <code>TimeUnit</code> + * seconds remaining before the lease expires. If this value is + * negative it should be assumed that the lease has expired. + * + * @param unit this is the time unit used for the duration + * + * @return the duration remaining within this lease instance + * + * @exception Exception if the expiry could not be acquired + */ + long getExpiry(TimeUnit unit) throws LeaseException; + + /** + * This ensures that the leased resource is maintained for the + * specified number of <code>TimeUnit</code> seconds. Allowing + * the duration unit to be specified enables the lease system + * to maintain a resource with a high degree of accuracy. The + * accuracy of the leasing system is dependent on how long it + * takes to clean the resource associated with the lease. + * + * @param duration this is the length of time to renew for + * @param unit this is the time unit used for the duration + * + * @exception Exception if the lease could not be renewed + */ + void renew(long duration, TimeUnit unit) throws LeaseException; + + /** + * This will cancel the lease and release the resource. This + * has the same effect as the <code>renew</code> method with + * a zero length duration. Once this has been called the + * <code>Cleaner</code> used should be notified immediately. + * If the lease has already expired this throws an exception. + * + * @exception Exception if the expiry has been passed + */ + void cancel() throws LeaseException; + + /** + * Provides the key for the resource that this lease represents. + * This can be used to identify the resource should the need + * arise. Also, this provides a convenient means of identifying + * leases when using or storing it as an <code>Object</code>. + * + * @return this returns the key for the resource represented + */ + T getKey(); + +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseCleaner.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseCleaner.java new file mode 100644 index 00000000..d1fe912d --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseCleaner.java @@ -0,0 +1,155 @@ +/* + * LeaseCleaner.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import org.simpleframework.common.thread.Daemon; + +/** + * The <code>LeaseCleaner</code> provides a means of providing + * callbacks to clean a leased resource once the contract duration + * has expired. This will acquire contracts from the queue and + * invoke the <code>Cleaner</code> notification method. This will + * wait until the current clean operation has completed before it + * attempts to clean the next contract. + * + * @author Niall Gallagher + */ +class LeaseCleaner<T> extends Daemon { + + /** + * This is used to queue contracts that are to be cleaned. + */ + private final ContractQueue<T> queue; + + /** + * This is the cleaner that is invoked to clean contracts. + */ + private final Cleaner<T> cleaner; + + /** + * Constructor for the <code>LeaseCleaner</code> object. This + * can be used to issue, update, and expire leases. When a lease + * expires notification is sent to the <code>Cleaner</code> + * object provided. This allows an implementation independent + * means to clean up once a specific lease has expired. + * + * @param cleaner this will receive expiration notifications + */ + public LeaseCleaner(Cleaner<T> cleaner) { + this.queue = new ContractQueue<T>(); + this.cleaner = cleaner; + this.start(); + } + + /** + * This revokes a contract that has previously been issued. This + * is used when the contract duration has changed so that it can + * be reissued again with a new duration. This returns true if + * the contract was still active and false if it did not exist. + * + * @param contract this is the contract that contains details + */ + public boolean revoke(Contract<T> contract) throws LeaseException { + if(!isActive()) { + throw new LeaseException("Lease can not be revoked"); + } + return queue.remove(contract); + } + + /** + * This method will establish a contract for a given resource. + * If the contract duration expires before it is renewed then + * a notification is sent, to the issued <code>Cleaner</code> + * implementation, to signify that the resource has expired. + * + * @param contract this is the contract that contains details + */ + public boolean issue(Contract<T> contract) throws LeaseException { + if(!isActive()) { + throw new LeaseException("Lease can not be issued"); + } + return queue.offer(contract); + } + + /** + * This acquires expired lease contracts from the queue once the + * expiry duration has passed. This will deliver notification to + * the <code>Cleaner</code> object once the contract has been + * taken from the queue. This allows the cleaner to clean up any + * resources associated with the lease before the next expiration. + */ + public void run() { + while(isActive()) { + try { + clean(); + } catch(Throwable e) { + continue; + } + } + purge(); + } + + /** + * This method is used to take the lease from the queue and give + * it to the cleaner for expiry. This effectively waits until the + * next contract expiry has passed, once it has passed the key + * for that contract is given to the cleaner to clean up resources. + */ + private void clean() throws Exception { + Contract<T> next = queue.take(); + T key = next.getKey(); + + if(key != null) { + cleaner.clean(key); + } + } + + /** + * Here all of the existing contracts are purged when the invoker + * is closed. This ensures that each leased resource has a chance + * to clean up after the lease manager has been closed. All of the + * contracts are given a zero delay and cleaned immediately such + * that once this method has finished the queue will be empty. + */ + private void purge() { + for(Contract<T> next : queue) { + T key = next.getKey(); + + try { + next.setDelay(0L, NANOSECONDS); + cleaner.clean(key); + } catch(Throwable e) { + continue; + } + } + } + + /** + * Here we shutdown the lease maintainer so that the thread will + * die. Shutting down the maintainer is done by interrupting the + * thread and setting the dead flag to true. Once this is invoked + * then the thread will no longer be running for this object. + */ + public void close() { + stop(); + interrupt(); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseException.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseException.java new file mode 100644 index 00000000..3a479493 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseException.java @@ -0,0 +1,52 @@ +/* + * LeaseException.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +/** + * The <code>LeaseException</code> is used to indicate that some + * operation failed when using the lease after the lease duration + * has expired. Typically this will be thrown when the lease is + * renewed after the expiry period has passed. + * + * @author Niall Gallagher + */ +public class LeaseException extends RuntimeException { + + /** + * This constructor is used if there is a description of the + * event that caused the exception required. This can be given + * a message used to describe the situation for the exception. + * + * @param message this is a description of the exception + */ + public LeaseException(String template) { + super(template); + } + + /** + * This constructor is used if there is a description of the + * event that caused the exception required. This can be given + * a message used to describe the situation for the exception. + * + * @param message this is a description of the exception + */ + public LeaseException(String template, Throwable cause) { + super(template, cause); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseManager.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseManager.java new file mode 100644 index 00000000..93459fdc --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseManager.java @@ -0,0 +1,93 @@ +/* + * LeaseManager.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.TimeUnit; + +/** + * The <code>LeaseManager</code> is used to issue a lease for a + * named resource. This is effectively used to issue a request + * for a keyed resource to be released when a lease has expired. + * The use of a <code>Lease</code> simplifies the interface to + * the notification and also enables other objects to manage the + * lease without any knowledge of the resource it represents. + * + * @author Niall Gallagher + */ +public class LeaseManager<T> implements LeaseProvider<T> { + + /** + * This is the controller used to handle lease operations. + */ + private ContractController<T> handler; + + /** + * Constructor for the <code>LeaseManager</code> object. This + * instance is created using a specified notification handler. + * The specified <code>Cleaner</code> will be notified when + * the lease for a named resource expires, which will allow + * the cleaner object to perform a clean up for that resource. + * + * @param cleaner the cleaner object receiving notifications + */ + public LeaseManager(Cleaner<T> cleaner) { + this.handler = new ContractMaintainer<T>(cleaner); + } + + /** + * This method will issue a <code>Lease</code> object that + * can be used to manage the release of a keyed resource. If + * the lease duration expires before it is renewed then the + * notification is sent, typically to a <code>Cleaner</code> + * implementation, to signify that the resource should be + * recovered. The issued lease can also be canceled. + * + * @param key this is the key for the leased resource + * @param duration this is the duration of the issued lease + * @param unit this is the time unit to issue the lease with + * + * @return a lease that can be used to manage notification + */ + public Lease<T> lease(T key, long duration, TimeUnit unit) { + Contract<T> contract = new Expiration<T>(key, duration, unit); + + try { + handler.issue(contract); + } catch(Exception e) { + throw new LeaseException("Could not issue lease", e); + } + return new ContractLease<T>(handler, contract); + } + + /** + * This is used to close the lease provider such that all of + * the outstanding leases are canceled. This also ensures the + * provider can no longer be used to issue new leases, such + * that further invocations of the <code>lease</code> method + * will result in null leases. Once the provider has been + * closes all threads and other such resources are released. + */ + public void close() { + try { + handler.close(); + } catch(Exception e) { + return; + } + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseMap.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseMap.java new file mode 100644 index 00000000..711a466b --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseMap.java @@ -0,0 +1,83 @@ +/* + * LeaseMap.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * The <code>LeaseMap</code> object is used to map lease keys to the + * lease objects managing those objects. This allows components that + * are using the leasing framework to associate an object with its + * lease and vice versa. Such a capability enables lease renewals to + * be performed without the need for a direct handle on the lease. + * + * @author Niall Gallagher + */ +public class LeaseMap<T> extends ConcurrentHashMap<T, Lease<T>> { + + /** + * Constructor for the <code>LeaseMap</code> object. This will + * create a map for mapping leased resource keys to the leases + * that manage them. Having such a map allows leases to be + * maintained without having a direct handle on the lease. + */ + public LeaseMap() { + super(); + } + + /** + * Constructor for the <code>LeaseMap</code> object. This will + * create a map for mapping leased resource keys to the leases + * that manage them. Having such a map allows leases to be + * maintained without having a direct handle on the lease. + * + * @param capacity this is the initial capacity of the map + */ + public LeaseMap(int capacity) { + super(capacity); + } + + /** + * This is used to acquire the <code>Lease</code> object that is + * mapped to the specified key. Overriding this method ensures + * that even without generic parameters a type safe method for + * acquiring the registered lease objects can be used. + * + * @param key this is the key used to acquire the lease object + * + * @return this is the lease that is associated with the key + */ + public Lease<T> get(Object key) { + return super.get(key); + } + + /** + * This is used to remove the <code>Lease</code> object that is + * mapped to the specified key. Overriding this method ensures + * that even without generic parameters a type safe method for + * removing the registered lease objects can be used. + * + * @param key this is the key used to remove the lease object + * + * @return this is the lease that is associated with the key + */ + public Lease<T> remove(Object key) { + return super.remove(key); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseProvider.java b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseProvider.java new file mode 100644 index 00000000..e5e7d8c9 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/lease/LeaseProvider.java @@ -0,0 +1,60 @@ +/* + * LeaseProvider.java May 2004 + * + * Copyright (C) 2004, 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.lease; + +import java.util.concurrent.TimeUnit; + +/** + * The <code>LeaseProvider</code> is used to issue a lease for a + * named resource. This is effectively used to issue a request + * for a keyed resource to be released when a lease has expired. + * The use of a <code>Lease</code> simplifies the interface to + * the notification and also enables other objects to manage the + * lease without any knowledge of the resource it represents. + * + * @author Niall Gallagher + */ +public interface LeaseProvider<T> { + + /** + * This method will issue a <code>Lease</code> object that + * can be used to manage the release of a keyed resource. If + * the lease duration expires before it is renewed then the + * notification is sent, typically to a <code>Cleaner</code> + * implementation, to signify that the resource should be + * recovered. The issued lease can also be canceled. + * + * @param key this is the key for the leased resource + * @param duration this is the duration of the issued lease + * @param unit this is the time unit to issue the lease with + * + * @return a lease that can be used to manage notification + */ + Lease<T> lease(T key, long duration, TimeUnit unit); + + /** + * This is used to close the lease provider such that all of + * the outstanding leases are canceled. This also ensures the + * provider can no longer be used to issue new leases, such + * that further invocations of the <code>lease</code> method + * will result in null leases. Once the provider has been + * closes all threads and other such resources are released. + */ + void close(); +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/parse/MapParser.java b/simple/simple-common/src/main/java/org/simpleframework/common/parse/MapParser.java new file mode 100644 index 00000000..d242937a --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/parse/MapParser.java @@ -0,0 +1,251 @@ +/* + * MapParser.java February 2005 + * + * Copyright (C) 2005, 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.parse; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The <code>MapParser</code> object represents a parser for name + * value pairs. Any parser extending this will typically be parsing + * name=value tokens or the like, and inserting these pairs into + * the internal map. This type of parser is useful as it exposes all + * pairs extracted using the <code>java.util.Map</code> interface + * and as such can be used with the Java collections framework. The + * internal map used by this is a <code>Hashtable</code>, however + * subclasses are free to assign a different type to the map used. + * + * @author Niall Gallagher + */ +public abstract class MapParser<T> extends Parser implements Map<T, T> { + + /** + * Represents all values inserted to the map as a list of values. + */ + protected Map<T, List<T>> all; + + /** + * Represents the last value inserted into this map instance. + */ + protected Map<T, T> map; + + /** + * Constructor for the <code>MapParser</code> object. This is + * used to create a new parser that makes use of a thread safe + * map implementation. The <code>HashMap</code> is used so + * that the resulting parser can be accessed in a concurrent + * environment with the fear of data corruption. + */ + protected MapParser(){ + this.all = new HashMap<T, List<T>>(); + this.map = new HashMap<T, T>(); + } + + /** + * This is used to determine whether a token representing the + * name of a pair has been inserted into the internal map. The + * object passed into this method should be a string, as all + * tokens stored within the map will be stored as strings. + * + * @param name this is the name of a pair within the map + * + * @return this returns true if the pair of that name exists + */ + public boolean containsKey(Object name) { + return map.containsKey(name); + } + + /** + * This method is used to determine whether any pair that has + * been inserted into the internal map had the presented value. + * If one or more pairs within the collected tokens contains + * the value provided then this method will return true. + * + * @param value this is the value that is to be searched for + * + * @return this returns true if any value is equal to this + */ + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + /** + * This method is used to acquire the name and value pairs that + * have currently been collected by this parser. This is used + * to determine which tokens have been extracted from the + * source. It is useful when the tokens have to be gathered. + * + * @return this set of token pairs that have been extracted + */ + public Set<Map.Entry<T, T>> entrySet() { + return map.entrySet(); + } + + /** + * The <code>get</code> method is used to acquire the value for + * a named pair. So if a pair of name=value has been parsed and + * inserted into the collection of tokens this will return the + * value given the name. The value returned will be a string. + * + * @param name this is a string used to search for the value + * + * @return this is the value, as a string, that has been found + */ + public T get(Object name) { + return map.get(name); + } + + /** + * This method is used to acquire a <code>List</code> for all of + * the values that have been put in to the map. The list allows + * all values associated with the specified key. This enables a + * parser to collect a number of associated tokens. + * + * @param key this is the key used to search for the value + * + * @return this is the list of values associated with the key + */ + public List<T> getAll(Object key) { + return all.get(key); + } + + /** + * This method is used to determine whether the parser has any + * tokens available. If the <code>size</code> is zero then the + * parser is empty and this returns true. The is acts as a + * proxy the the <code>isEmpty</code> of the internal map. + * + * @return this is true if there are no available tokens + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * This is used to acquire the names for all the tokens that + * have currently been collected by this parser. This is used + * to determine which tokens have been extracted from the + * source. It is useful when the tokens have to be gathered. + * + * @return the set of name tokens that have been extracted + */ + public Set<T> keySet() { + return map.keySet(); + } + + /** + * The <code>put</code> method is used to insert the name and + * value provided into the collection of tokens. Although it is + * up to the parser to decide what values will be inserted it + * is generally the case that the inserted tokens will be text. + * + * @param name this is the name token from a name=value pair + * @param value this is the value token from a name=value pair + * + * @return this returns the previous value if there was any + */ + public T put(T name, T value) { + List<T> list = all.get(name); + T first = map.get(name); + + if(list == null) { + list = new ArrayList<T>(); + all.put(name, list); + } + list.add(value); + + if(first == null) { + return map.put(name, value); + } + return null; + } + + /** + * This method is used to insert a collection of tokens into + * the parsers map. This is used when another source of tokens + * is required to populate the connection currently maintained + * within this parsers internal map. Any tokens that currently + * exist with similar names will be overwritten by this. + * + * @param data this is the collection of tokens to be added + */ + public void putAll(Map<? extends T, ? extends T> data) { + Set<? extends T> keySet = data.keySet(); + + for(T key : keySet) { + T value = data.get(key); + + if(value != null) { + put(key, value); + } + } + } + + /** + * The <code>remove</code> method is used to remove the named + * token pair from the collection of tokens. This acts like a + * take, in that it will get the token value and remove if + * from the collection of tokens the parser has stored. + * + * @param name this is a string used to search for the value + * + * @return this is the value, as a string, that is removed + */ + public T remove(Object name) { + return map.remove(name); + } + + /** + * This obviously enough provides the number of tokens that + * have been inserted into the internal map. This acts as + * a proxy method for the internal map <code>size</code>. + * + * @return this returns the number of tokens are available + */ + public int size() { + return map.size(); + } + + /** + * This method is used to acquire the value for all tokens that + * have currently been collected by this parser. This is used + * to determine which tokens have been extracted from the + * source. It is useful when the tokens have to be gathered. + * + * @return the list of value tokens that have been extracted + */ + public Collection<T> values() { + return map.values(); + } + + /** + * The <code>clear</code> method is used to wipe out all the + * currently existing tokens from the collection. This is used + * to recycle the parser so that it can be used to parse some + * other source of tokens without any lingering state. + */ + public void clear() { + all.clear(); + map.clear(); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/parse/ParseBuffer.java b/simple/simple-common/src/main/java/org/simpleframework/common/parse/ParseBuffer.java new file mode 100644 index 00000000..d680a08f --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/parse/ParseBuffer.java @@ -0,0 +1,247 @@ +/* + * ParseBuffer.java February 2001 + * + * Copyright (C) 2001, 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.parse; + +/** + * This is primarily used to replace the <code>StringBuffer</code> + * class, as a way for the <code>Parser</code> to store the char's + * for a specific region within the parse data that constitutes a + * desired value. The methods are not synchronized so it enables + * the <code>char</code>'s to be taken quicker than the + * <code>StringBuffer</code> class. + * + * @author Niall Gallagher + */ +public class ParseBuffer { + + /** + * This is used to quicken <code>toString</code>. + */ + protected String cache; + + /** + * The <code>char</code>'s this buffer accumulated. + */ + protected char[] buf; + + /** + * This is the number of <code>char</code>'s stored. + */ + protected int count; + + /** + * Constructor for <code>ParseBuffer</code>. The default + * <code>ParseBuffer</code> stores 16 <code>char</code>'s + * before a <code>resize</code> is needed to accommodate + * extra characters. + */ + public ParseBuffer(){ + this(16); + } + + /** + * This creates a <code>ParseBuffer</code> with a specific + * default size. The buffer will be created the with the + * length specified. The <code>ParseBuffer</code> can grow + * to accommodate a collection of <code>char</code>'s larger + * the the size specified. + * + * @param size initial size of this <code>ParseBuffer</code> + */ + public ParseBuffer(int size){ + this.buf = new char[size]; + } + + /** + * This will add a <code>char</code> to the end of the buffer. + * The buffer will not overflow with repeated uses of the + * <code>append</code>, it uses an <code>ensureCapacity</code> + * method which will allow the buffer to dynamically grow in + * size to accommodate more <code>char</code>'s. + * + * @param c the <code>char</code> to be appended + */ + public void append(char c){ + ensureCapacity(count+ 1); + buf[count++] = c; + } + + /** + * This will add a <code>String</code> to the end of the buffer. + * The buffer will not overflow with repeated uses of the + * <code>append</code>, it uses an <code>ensureCapacity</code> + * method which will allow the buffer to dynamically grow in + * size to accommodate large <code>String</code> objects. + * + * @param text the <code>String</code> to be appended to this + */ + public void append(String text){ + ensureCapacity(count+ text.length()); + text.getChars(0,text.length(),buf,count); + count += text.length(); + } + + /** + * This will reset the buffer in such a way that the buffer is + * cleared of all contents and then has the given string appended. + * This is used when a value is to be set into the buffer value. + * See the <code>append(String)</code> method for reference. + * + * @param text this is the text that is to be appended to this + */ + public void reset(String text) { + clear(); + append(text); + } + + /** + * This will add a <code>ParseBuffer</code> to the end of this. + * The buffer will not overflow with repeated uses of the + * <code>append</code>, it uses an <code>ensureCapacity</code> + * method which will allow the buffer to dynamically grow in + * size to accommodate large <code>ParseBuffer</code> objects. + * + * @param text the <code>ParseBuffer</code> to be appended + */ + public void append(ParseBuffer text){ + append(text.buf, 0, text.count); + } + + /** + * This will reset the buffer in such a way that the buffer is + * cleared of all contents and then has the given string appended. + * This is used when a value is to be set into the buffer value. + * See the <code>append(ParseBuffer)</code> method for reference. + * + * @param text this is the text that is to be appended to this + */ + public void reset(ParseBuffer text) { + clear(); + append(text); + } + /** + * This will add a <code>char</code> to the end of the buffer. + * The buffer will not overflow with repeated uses of the + * <code>append</code>, it uses an <code>ensureCapacity</code> + * method which will allow the buffer to dynamically grow in + * size to accommodate large <code>char</code> arrays. + * + * @param c the <code>char</code> array to be appended to this + * @param off the read offset for the array + * @param len the number of <code>char</code>'s to add + */ + public void append(char[] c, int off, int len){ + ensureCapacity(count+ len); + System.arraycopy(c,off,buf,count,len); + count+=len; + } + + /** + * This will add a <code>String</code> to the end of the buffer. + * The buffer will not overflow with repeated uses of the + * <code>append</code>, it uses an <code>ensureCapacity</code> + * method which will allow the buffer to dynamically grow in + * size to accommodate large <code>String</code> objects. + * + * @param str the <code>String</code> to be appended to this + * @param off the read offset for the <code>String</code> + * @param len the number of <code>char</code>'s to add + */ + public void append(String str, int off, int len){ + ensureCapacity(count+ len); + str.getChars(off,len,buf,count); + count += len; + } + + + /** + * This will add a <code>ParseBuffer</code> to the end of this. + * The buffer will not overflow with repeated uses of the + * <code>append</code>, it uses an <code>ensureCapacity</code> + * method which will allow the buffer to dynamically grow in + * size to accommodate large <code>ParseBuffer</code> objects. + * + * @param text the <code>ParseBuffer</code> to be appended + * @param off the read offset for the <code>ParseBuffer</code> + * @param len the number of <code>char</code>'s to add + */ + public void append(ParseBuffer text, int off, int len){ + append(text.buf, off, len); + } + + /** + * This ensure that there is enough space in the buffer to + * allow for more <code>char</code>'s to be added. If + * the buffer is already larger than min then the buffer + * will not be expanded at all. + * + * @param min the minimum size needed + */ + protected void ensureCapacity(int min) { + if(buf.length < min) { + int size = buf.length * 2; + int max = Math.max(min, size); + char[] temp = new char[max]; + System.arraycopy(buf, 0, temp, 0, count); + buf = temp; + } + } + + /** + * This will empty the <code>ParseBuffer</code> so that the + * <code>toString</code> parameter will return <code>null</code>. + * This is used so that the same <code>ParseBuffer</code> can be + * recycled for different tokens. + */ + public void clear(){ + cache = null; + count = 0; + } + + /** + * This will return the number of bytes that have been appended + * to the <code>ParseBuffer</code>. This will return zero after + * the clear method has been invoked. + * + * @return the number of <code>char</code>'s within the buffer + */ + public int length(){ + return count; + } + + /** + * This will return the characters that have been appended to the + * <code>ParseBuffer</code> as a <code>String</code> object. + * If the <code>String</code> object has been created before then + * a cached <code>String</code> object will be returned. This + * method will return <code>null</code> after clear is invoked. + * + * @return the <code>char</code>'s appended as a <code>String</code> + */ + public String toString(){ + if(count <= 0) { + return null; + } + if(cache != null) { + return cache; + } + cache = new String(buf,0,count); + return cache; + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/parse/Parser.java b/simple/simple-common/src/main/java/org/simpleframework/common/parse/Parser.java new file mode 100644 index 00000000..5ba7b522 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/parse/Parser.java @@ -0,0 +1,197 @@ +/* + * Parser.java February 2001 + * + * Copyright (C) 2001, 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.parse; + +/** + * This <code>Parser</code> object is to be used as a simple template + * for parsing uncomplicated expressions. This object is used to parse + * a <code>String</code>. This provides a few methods that can be used + * to store and track the reading of data from a buffer. There are two + * abstract methods provided to allow this to be subclassed to create + * a <code>Parser</code> for a given <code>String</code>. + * + * @author Niall Gallagher + */ +public abstract class Parser { + + /** + * This is the buffer that is being parsed. + */ + protected char[] buf; + + /** + * This represents the current read offset. + */ + protected int off; + + /** + * This represents the length of the buffer. + */ + protected int count; + + /** + * This is a no argument constructor for the <code>Parser</code>. + * This will be invoked by each subclass of this object. It will + * set the buffer to a zero length buffer so that when the + * <code>ensureCapacity</code> method is used the buf's + * length can be checked. + */ + protected Parser(){ + this.buf = new char[0]; + } + + /** + * This is used to parse the <code>String</code> given to it. This + * will ensure that the <code>char</code> buffer has enough space + * to contain the characters from the <code>String</code>. This + * will firstly ensure that the buffer is resized if nessecary. The + * second step in this <code>parse</code> method is to initialize + * the <code>Parser</code> object so that multiple parse invocations + * can be made. The <code>init</code> method will reset this to an + * prepared state. Then finally the <code>parse</code> method is + * called to parse the <code>char</code> buffer. + * + * @param text the <code>String</code> to be parsed with this + * <code>Parser</code> + */ + public void parse(String text){ + if(text != null){ + ensureCapacity(text.length()); + count = text.length(); + text.getChars(0, count, buf,0); + init(); + parse(); + } + } + + /** + * This ensure that there is enough space in the buffer to allow + * for more <code>char</code>'s to be added. If the buffer is + * already larger than min then the buffer will not be expanded + * at all. + * + * @param min the minimum size needed to accommodate the characters + */ + protected void ensureCapacity(int min) { + if(buf.length < min) { + int size = buf.length * 2; + int max = Math.max(min, size); + char[] temp = new char[max]; + buf = temp; + } + } + + /** + * This is used to determine if a given ISO-8859-1 character is + * a space character. That is a whitespace character this sees + * the, space, carriage return and line feed characters as + * whitespace characters. + * + * @param c the character that is being determined by this + * + * @return true if the character given it is a space character + */ + protected boolean space(char c) { + switch(c){ + case ' ': case '\t': + case '\n': case '\r': + return true; + default: + return false; + } + } + + /** + * This is used to determine weather or not a given character is + * a digit character. It assumes iso-8859-1 encoding to compare. + * + * @param c the character being determined by this method + * + * @return true if the character given is a digit character + */ + protected boolean digit(char c){ + return c <= '9' && '0' <= c; + } + + /** + * This takes a unicode character and assumes an encoding of + * ISO-8859-1. This then checks to see if the given character + * is uppercase if it is it converts it into is ISO-8859-1 + * lowercase char. + * + * @param c the <code>char</code> to be converted to lowercase + * + * @return the lowercase ISO-8859-1 of the given character + */ + protected char toLower(char c) { + if(c >= 'A' && c <= 'Z') { + return (char)((c - 'A') + 'a'); + } + return c; + } + + /** This is used to skip an arbitrary <code>String</code> within the + * <code>char</code> buf. It checks the length of the <code>String</code> + * first to ensure that it will not go out of bounds. A comparison + * is then made with the buffers contents and the <code>String</code> + * if the reigon in the buffer matched the <code>String</code> then the + * offset within the buffer is increased by the <code>String</code>'s + * length so that it has effectively skipped it. + * + * @param text this is the <code>String</code> value to be skipped + * + * @return true if the <code>String</code> was skipped + */ + protected boolean skip(String text){ + int size = text.length(); + int read = 0; + + if(off + size > count){ + return false; + } + while(read < size){ + char a = text.charAt(read); + char b = buf[off + read++]; + + if(toLower(a) != toLower(b)){ + return false; + } + } + off += size; + return true; + } + + /** + * This will initialize the <code>Parser</code> when it is ready + * to parse a new <code>String</code>. This will reset the + * <code>Parser</code> to a ready state. The <code>init</code> + * method is invoked by the <code>Parser</code> when the + * <code>parse</code> method is invoked. + */ + protected abstract void init(); + + /** + * This is the method that should be implemented to read + * the buf. This method should attempt to extract tokens + * from the buffer so that thes tokens may some how be + * used to determine the semantics. This method is invoked + * after the <code>init</code> method is invoked. + */ + protected abstract void parse(); +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/ConcurrentExecutor.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/ConcurrentExecutor.java new file mode 100644 index 00000000..9f99025e --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/ConcurrentExecutor.java @@ -0,0 +1,109 @@ +/* + * ConcurrentExecutor.java February 2007 + * + * Copyright (C) 2007, 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.thread; + +import java.util.concurrent.Executor; + +/** + * The <code>ConcurrentExecutor</code> object is used to execute tasks + * in a thread pool. This creates a thread pool with an unbounded list + * of outstanding tasks, which ensures that any system requesting + * a task to be executed will not block when handing it over. + * + * @author Niall Gallagher + */ +public class ConcurrentExecutor implements Executor { + + /** + * This is the queue used to enqueue the tasks for execution. + */ + private final ExecutorQueue queue; + + /** + * Constructor for the <code>ConcurrentExecutor</code> object. This + * is used to create a pool of threads that can be used to execute + * arbitrary <code>Runnable</code> tasks. If the threads are + * busy this will simply enqueue the tasks and return. + * + * @param type this is the type of runnable that this accepts + */ + public ConcurrentExecutor(Class type) { + this(type, 10); + } + + /** + * Constructor for the <code>ConcurrentExecutor</code> object. This + * is used to create a pool of threads that can be used to execute + * arbitrary <code>Runnable</code> tasks. If the threads are + * busy this will simply enqueue the tasks and return. + * + * @param type this is the type of runnable that this accepts + * @param size this is the number of threads to use in the pool + */ + public ConcurrentExecutor(Class type, int size) { + this(type, size, size); + } + + /** + * Constructor for the <code>ConcurrentExecutor</code> object. This + * is used to create a pool of threads that can be used to execute + * arbitrary <code>Runnable</code> tasks. If the threads are + * busy this will simply enqueue the tasks and return. + * + * @param type this is the type of runnable that this accepts + * @param rest this is the number of threads to use in the pool + * @param active this is the maximum size the pool can grow to + */ + public ConcurrentExecutor(Class type, int rest, int active) { + this.queue = new ExecutorQueue(type, rest, active); + } + + /** + * The <code>execute</code> method is used to queue the task for + * execution. If all threads are busy the provided task is queued + * and waits until all current and outstanding tasks are finished. + * + * @param task this is the task to be queued for execution + */ + public void execute(Runnable task) { + queue.execute(task); + } + + /** + * This is used to stop the executor by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + */ + public void stop() { + stop(60000); + } + + /** + * This is used to stop the executor by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + * + * @param wait the number of milliseconds to wait for it to stop + */ + public void stop(long wait) { + queue.stop(wait); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/ConcurrentScheduler.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/ConcurrentScheduler.java new file mode 100644 index 00000000..bb4a1174 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/ConcurrentScheduler.java @@ -0,0 +1,122 @@ +/* + * ConcurrentScheduler.java February 2007 + * + * Copyright (C) 2007, 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.thread; + +import java.util.concurrent.TimeUnit; + +/** + * The <code>ConcurrentScheduler</code> object is used to schedule tasks + * for execution. This queues the task for the requested period of + * time before it is executed. It ensures that the delay is adhered + * to such that tasks can be timed for execution in an accurate way. + * + * @author Niall Gallagher + */ +public class ConcurrentScheduler implements Scheduler { + + /** + * This is the scheduler queue used to enque tasks to execute. + */ + private final SchedulerQueue queue; + + /** + * Constructor for the <code>ConcurrentScheduler</code> object. + * This will create a scheduler with a fixed number of threads to + * use before execution. Depending on the types of task that are + * to be executed this should be increased for accuracy. + * + * @param type this is the type of the worker threads + */ + public ConcurrentScheduler(Class type) { + this(type, 10); + } + + /** + * Constructor for the <code>ConcurrentScheduler</code> object. + * This will create a scheduler with a fixed number of threads to + * use before execution. Depending on the types of task that are + * to be executed this should be increased for accuracy. + * + * @param type this is the type of the worker threads + * @param size this is the number of threads for the scheduler + */ + public ConcurrentScheduler(Class type, int size) { + this.queue = new SchedulerQueue(type, size); + } + + /** + * This will execute the task within the executor immediately + * as it uses a delay duration of zero milliseconds. This can + * be used if the scheduler is to be used as a thread pool. + * + * @param task this is the task to schedule for execution + */ + public void execute(Runnable task) { + queue.execute(task); + } + + /** + * This will execute the task within the executor after the time + * specified has expired. If the time specified is zero then it + * will be executed immediately. Once the scheduler has been + * stopped then this method will no longer accept runnable tasks. + * + * @param task this is the task to schedule for execution + * @param delay the time in milliseconds to wait for execution + */ + public void execute(Runnable task, long delay) { + execute(task, delay, TimeUnit.MILLISECONDS); + } + + /** + * This will execute the task within the executor after the time + * specified has expired. If the time specified is zero then it + * will be executed immediately. Once the scheduler has been + * stopped then this method will no longer accept runnable tasks. + * + * @param task this is the task to schedule for execution + * @param delay this is the delay to wait before execution + * @param unit this is the duration time unit to wait for + */ + public void execute(Runnable task, long delay, TimeUnit unit) { + queue.execute(task, delay, unit); + } + + /** + * This is used to stop the scheduler by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return immediately once it has been stopped, and not further + * tasks will be accepted by this pool for execution. + */ + public void stop() { + stop(60000); + } + + /** + * This is used to stop the scheduler by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + * + * @param wait the number of milliseconds to wait for it to stop + */ + public void stop(long wait) { + queue.stop(wait); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/Daemon.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/Daemon.java new file mode 100644 index 00000000..3b7b5bf9 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/Daemon.java @@ -0,0 +1,164 @@ +/* + * Daemon.java February 2009 + * + * Copyright (C) 2009, 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.thread; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * The <code>Daemon</code> object provides a named thread which will + * execute the <code>run</code> method when started. This offers + * some convenience in that it hides the normal thread methods and + * also allows the object extending this to provide the name of the + * internal thread, which is given an incrementing sequence number + * appended to the name provided. + * + * @author Niall Gallagher + */ +public abstract class Daemon implements Runnable { + + /** + * This is the current thread executing this service. + */ + private final AtomicReference<Thread> reference; + + /** + * This is the internal thread used by this daemon instance. + */ + private final DaemonFactory factory; + + /** + * This is used to determine if the daemon is active. + */ + private final AtomicBoolean active; + + /** + * This is the internal thread that is executed by this. + */ + private final Runnable delegate; + + /** + * Constructor for the <code>Daemon</code> object. This will + * create the internal thread and ensure it is a daemon. When it + * is started the name of the internal thread is set using the + * name of the instance as taken from <code>getName</code>. If + * the name provided is null then no name is set for the thread. + */ + protected Daemon() { + this.reference = new AtomicReference<Thread>(); + this.delegate = new RunnableDelegate(this); + this.factory = new DaemonFactory(); + this.active = new AtomicBoolean(); + } + + /** + * This is used to determine if the runner is active. If it is not + * active then it is assumed that no thread is executing. Also, if + * this is extended then any executing thread to stop as soon as + * this method returns false. + * + * @return this returns true if the runner is active + */ + public boolean isActive() { + return active.get(); + } + + /** + * This is used to start the internal thread. Once started the + * internal thread will execute the <code>run</code> method of + * this instance. Aside from starting the thread this will also + * ensure the internal thread has a unique name. + */ + public void start() { + Class type = getClass(); + + if (!active.get()) { + Thread thread = factory.newThread(delegate, type); + + reference.set(thread); + active.set(true); + thread.start(); + } + } + + /** + * This is used to interrupt the internal thread. This is used + * when there is a need to wake the thread from a sleeping or + * waiting state so that some other operation can be performed. + * Typically this is required when killing the thread. + */ + public void interrupt() { + Thread thread = reference.get(); + + if(thread != null) { + thread.interrupt(); + } + } + + /** + * This method is used to stop the thread without forcing it to + * stop. It acts as a means to deactivate it. It is up to the + * implementor to ensure that the <code>isActive</code> method + * is checked to determine whether it should continue to run. + */ + public void stop() { + active.set(false); + } + + /** + * The <code>RunnableDelegate</code> object is used to actually + * invoke the <code>run</code> method. A delegate is used to ensure + * that once the task has finished it is inactive so that it can + * be started again with a new thread. + */ + private class RunnableDelegate implements Runnable { + + /** + * This is the runnable that is to be executed. + */ + private final Runnable task; + + /** + * Constructor for the <code>RunnableDelegate</code> object. The + * delegate requires the actual runnable that is to be executed. + * As soon as the task has finished the runner becomes inactive. + * + * @param task this is the task to be executed + */ + public RunnableDelegate(Runnable task) { + this.task = task; + } + + /** + * This is used to execute the task. Once the task has finished + * the runner becomes inactive and any reference to the internal + * thread is set to null. This ensures the runner can be started + * again at a later time if desired. + */ + public void run() { + try { + task.run(); + } finally { + reference.set(null); + active.set(false); + } + } + + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/DaemonFactory.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/DaemonFactory.java new file mode 100644 index 00000000..d5da16a2 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/DaemonFactory.java @@ -0,0 +1,147 @@ +/* + * DaemonFactory.java February 2009 + * + * Copyright (C) 2009, 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.thread; + +import java.util.concurrent.ThreadFactory; + +/** + * The <code>DaemonFactory</code> object is used to build threads + * and prefix the thread with a type name. Prefixing the threads with + * the type that it represents allows the purpose of the thread to + * be determined and also provides better debug information. + * + * @author Niall Gallagher + */ +public class DaemonFactory implements ThreadFactory { + + /** + * This is the type of the task this pool will execute. + */ + private final Class type; + + /** + * Constructor for the <code>DaemonFactory</code> object. This + * will provide a thread factory that names the threads based + * on the type of <code>Runnable</code> the pool executes. + */ + public DaemonFactory() { + this(null); + } + + /** + * Constructor for the <code>DaemonFactory</code> object. This + * will provide a thread factory that names the threads based + * on the type of <code>Runnable</code> the pool executes. Each + * of the threads is given a unique sequence number. + * + * @param type this is the type of runnable this will execute + */ + public DaemonFactory(Class type) { + this.type = type; + } + + /** + * This is used to create a thread from the provided runnable. The + * thread created will contain a unique name which is prefixed with + * the type of task it has been created to execute. This provides + * some detail as to what the thread should be doing. + * + * @param task this is the task that the thread is to execute + * + * @return this returns a thread that will executed the given task + */ + public Thread newThread(Runnable task) { + Thread thread = newThread(task, type); + String name = createName(task, thread); + + if(!thread.isAlive()) { + thread.setName(name); + } + return thread; + } + + /** + * This is used to create a thread from the provided runnable. The + * thread created will contain a unique name which is prefixed with + * the type of task it has been created to execute. This provides + * some detail as to what the thread should be doing. + * + * @param task this is the task that the thread is to execute + * @param type this is the type of object the thread is to execute + * + * @return this returns a thread that will executed the given task + */ + public Thread newThread(Runnable task, Class type) { + Thread thread = createThread(task); + String name = createName(type, thread); + + if(!thread.isAlive()) { + thread.setName(name); + } + return thread; + } + + /** + * This will create a thread name that is unique. The thread name + * is a combination of the original thread name with a prefix + * of the type of the object that will be running within it. + * + * @param task this is the task to be run within the thread + * @param thread this is the thread containing the original name + * + * @return this will return the new name of the thread + */ + private String createName(Runnable task, Thread thread) { + Class type = task.getClass(); + String prefix = type.getSimpleName(); + String name = thread.getName(); + + return String.format("%s: %s", prefix, name); + } + + /** + * This will create a thread name that is unique. The thread name + * is a combination of the original thread name with a prefix + * of the type of the object that will be running within it. + * + * @param type this is the type of object to be executed + * @param thread this is the thread containing the original name + * + * @return this will return the new name of the thread + */ + private String createName(Class type, Thread thread) { + String prefix = type.getSimpleName(); + String name = thread.getName(); + + return String.format("%s: %s", prefix, name); + } + + /** + * This is used to create the thread that will be used to execute + * the provided task. The created thread will be renamed after + * it has been created and before it has been started. + * + * @param task this is the task that is to be executed + * + * @return this returns a thread to execute the given task + */ + private Thread createThread(Runnable task) { + return new Thread(task); + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/ExecutorQueue.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/ExecutorQueue.java new file mode 100644 index 00000000..99e8fb21 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/ExecutorQueue.java @@ -0,0 +1,128 @@ +/* + * ExecutorQueue.java February 2007 + * + * Copyright (C) 2007, 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.thread; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * The <code>ExecutorQueue</code> object is used to queue tasks in + * a thread pool. This creates a thread pool with no limit to the + * number of tasks that can be enqueued, which ensures that any + * system requesting a task to be executed will not block when + * handing it over, it also means the user must use caution. + * + * @author Niall Gallagher + * + * @see org.simpleframework.common.thread.ConcurrentExecutor + */ +class ExecutorQueue { + + /** + * This is the task queue that contains tasks due to execute. + */ + private final BlockingQueue<Runnable> queue; + + /** + * This is the actual thread pool implementation used. + */ + private final ThreadPoolExecutor executor; + + /** + * This is used to create the pool worker threads. + */ + private final ThreadFactory factory; + + /** + * Constructor for the <code>ExecutorQueue</code> object. This is + * used to create a pool of threads that can be used to execute + * arbitrary <code>Runnable</code> tasks. If the threads are + * busy this will simply enqueue the tasks and return. + * + * @param type this is the type of runnable that this accepts + * @param rest this is the number of threads to use in the pool + * @param active this is the maximum size the pool can grow to + */ + public ExecutorQueue(Class type, int rest, int active) { + this(type, rest, active, 120, TimeUnit.SECONDS); + } + + /** + * Constructor for the <code>ExecutorQueue</code> object. This is + * used to create a pool of threads that can be used to execute + * arbitrary <code>Runnable</code> tasks. If the threads are + * busy this will simply enqueue the tasks and return. + * + * @param type this is the type of runnable that this accepts + * @param rest this is the number of threads to use in the pool + * @param active this is the maximum size the pool can grow to + * @param duration the duration active threads remain idle for + * @param unit this is the time unit used for the duration + */ + public ExecutorQueue(Class type, int rest, int active, long duration, TimeUnit unit) { + this.queue = new LinkedBlockingQueue<Runnable>(); + this.factory = new DaemonFactory(type); + this.executor = new ThreadPoolExecutor(rest, active, duration, unit, queue, factory); + } + + /** + * The <code>execute</code> method is used to queue the task for + * execution. If all threads are busy the provided task is queued + * and waits until all current and outstanding tasks are finished. + * + * @param task this is the task to be queued for execution + */ + public void execute(Runnable task) { + executor.execute(task); + } + + /** + * This is used to stop the executor by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + */ + public void stop() { + stop(60000); + } + + /** + * This is used to stop the executor by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + * + * @param wait the number of milliseconds to wait for it to stop + */ + public void stop(long wait) { + if(!executor.isTerminated()) { + try { + executor.shutdown(); + executor.awaitTermination(wait, MILLISECONDS); + } catch(Exception e) { + throw new IllegalStateException("Could not stop pool", e); + } + } + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/Scheduler.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/Scheduler.java new file mode 100644 index 00000000..d24fa17a --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/Scheduler.java @@ -0,0 +1,57 @@ +/* + * Scheduler.java October 2014 + * + * Copyright (C) 2014, 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.thread; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * The <code>Scheduler</code> interface represents a means to execute + * a task immediately or after a specified delay. This queues the + * task for the requested period of time before it is executed, if a + * delay is specified. How the task is executed is dependent on the + * implementation, however it will normally use a thread pool. + * + * @author Niall Gallagher + */ +public interface Scheduler extends Executor { + + /** + * This will execute the task within the executor after the time + * specified has expired. If the time specified is zero then it + * will be executed immediately. Once the scheduler has been + * stopped then this method will no longer accept runnable tasks. + * + * @param task this is the task to schedule for execution + * @param delay the time in milliseconds to wait for execution + */ + void execute(Runnable task, long delay); + + /** + * This will execute the task within the executor after the time + * specified has expired. If the time specified is zero then it + * will be executed immediately. Once the scheduler has been + * stopped then this method will no longer accept runnable tasks. + * + * @param task this is the task to schedule for execution + * @param delay this is the delay to wait before execution + * @param unit this is the duration time unit to wait for + */ + void execute(Runnable task, long delay, TimeUnit unit); +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/SchedulerQueue.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/SchedulerQueue.java new file mode 100644 index 00000000..67385ed3 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/SchedulerQueue.java @@ -0,0 +1,127 @@ +/* + * SchedulerQueue.java February 2007 + * + * Copyright (C) 2007, 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.thread; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * The <code>SchedulerQueue</code> object is used to schedule tasks + * for execution. This queues the task for the requested period of + * time before it is executed. It ensures that the delay is adhered + * to such that tasks can be timed for execution in an accurate way. + * + * @author Niall Gallagher + */ +class SchedulerQueue { + + /** + * This is the actual scheduler used to schedule the tasks. + */ + private final ScheduledThreadPoolExecutor executor; + + /** + * This is the factory used to create the worker threads. + */ + private final ThreadFactory factory; + + /** + * Constructor for the <code>SchedulerQueue</code> object. This + * will create a scheduler with a fixed number of threads to use + * before execution. Depending on the types of task that are + * to be executed this should be increased for accuracy. + * + * @param type this is the type of task to execute + * @param size this is the number of threads for the scheduler + */ + public SchedulerQueue(Class type, int size) { + this.factory = new DaemonFactory(type); + this.executor = new ScheduledThreadPoolExecutor(size, factory); + } + + /** + * The <code>execute</code> method is used to queue the task for + * execution. If all threads are busy the provided task is queued + * and waits until all current and outstanding tasks are finished. + * + * @param task this is the task to be queued for execution + */ + public void execute(Runnable task) { + executor.execute(task); + } + + /** + * This will execute the task within the executor after the time + * specified has expired. If the time specified is zero then it + * will be executed immediately. Once the scheduler has been + * stopped then this method will no longer accept runnable tasks. + * + * @param task this is the task to schedule for execution + * @param delay the time in milliseconds to wait for execution + */ + public void execute(Runnable task, long delay) { + execute(task, delay, TimeUnit.MILLISECONDS); + } + + /** + * This will execute the task within the executor after the time + * specified has expired. If the time specified is zero then it + * will be executed immediately. Once the scheduler has been + * stopped then this method will no longer accept runnable tasks. + * + * @param task this is the task to schedule for execution + * @param delay this is the delay to wait before execution + * @param unit this is the duration time unit to wait for + */ + public void execute(Runnable task, long delay, TimeUnit unit) { + executor.schedule(task, delay, unit); + } + + /** + * This is used to stop the executor by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + */ + public void stop() { + stop(60000); + } + + /** + * This is used to stop the executor by interrupting all running + * tasks and shutting down the threads within the pool. This will + * return once it has been stopped, and no further tasks will be + * accepted by this pool for execution. + * + * @param wait the number of milliseconds to wait for it to stop + */ + public void stop(long wait) { + if(!executor.isTerminated()) { + try { + executor.shutdown(); + executor.awaitTermination(wait, MILLISECONDS); + } catch(Exception e) { + throw new IllegalStateException("Could not stop pool", e); + } + } + } +} diff --git a/simple/simple-common/src/main/java/org/simpleframework/common/thread/SynchronousExecutor.java b/simple/simple-common/src/main/java/org/simpleframework/common/thread/SynchronousExecutor.java new file mode 100644 index 00000000..decd41d7 --- /dev/null +++ b/simple/simple-common/src/main/java/org/simpleframework/common/thread/SynchronousExecutor.java @@ -0,0 +1,43 @@ +/* + * SynchronousExecutor.java February 2007 + * + * Copyright (C) 2007, 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.thread; + +import java.util.concurrent.Executor; + +/** + * The <code>SynchronousExecutor</code> object is used for synchronous + * execution of tasks. This simple acts as an adapter for running + * a <code>Runnable</code> implementation and can be used wherever + * the executor interface is required. + * + * @author Niall Gallagher + */ +public class SynchronousExecutor implements Executor { + + /** + * This will execute the provided <code>Runnable</code> within + * the current thread. This implementation will simple invoke + * the run method of the task and wait for it to complete. + * + * @param task this is the task that is to be executed + */ + public void execute(Runnable task) { + task.run(); + } +}
\ No newline at end of file diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/KeyTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/KeyTest.java new file mode 100644 index 00000000..f9056fe5 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/KeyTest.java @@ -0,0 +1,195 @@ +package org.simpleframework.common; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +/** + * Test for fast case insensitive mapping for headers that have been taken + * from the request HTTP header or added to the response HTTP header. + * + * @author Niall Gallagher + */ +public class KeyTest extends TestCase { + + public class Index implements Name { + + private final String value; + + public Index(String value) { + this.value = value.toLowerCase(); + } + + public int hashCode() { + return value.hashCode(); + } + + public boolean equals(Object key) { + if(key instanceof Name) { + return key.equals(value); + } + if(key instanceof String) { + return key.equals(value); + } + return false; + } + } + + public interface Name { + + public int hashCode(); + public boolean equals(Object value); + } + + public class ArrayName implements Name { + + private String cache; + private byte[] array; + private int off; + private int size; + private int hash; + + public ArrayName(byte[] array) { + this(array, 0, array.length); + } + + public ArrayName(byte[] array, int off, int size) { + this.array = array; + this.size = size; + this.off = off; + } + + public boolean equals(Object value) { + if(value instanceof String) { + String text = value.toString(); + + return equals(text); + } + return false; + } + + public boolean equals(String value) { + int length = value.length(); + + if(length != size) { + return false; + } + for(int i = 0; i < size; i++) { + int left = value.charAt(i); + int right = array[off + i]; + + if(right >= 'A' && right <= 'Z') { + right = (right - 'A') + 'a'; + } + if(left != right) { + return false; + } + } + return true; + } + + public int hashCode() { + int code = hash; + + if(code == 0) { + int pos = off; + + for(int i = 0; i < size; i++) { + int next = array[pos++]; + + if(next >= 'A' && next <= 'Z') { + next = (next - 'A') + 'a'; + } + code = 31*code + next; + } + hash = code; + } + return code; + } + } + + public class StringName implements Name { + + private final String value; + private final String key; + + public StringName(String value) { + this.key = value.toLowerCase(); + this.value = value; + } + + public int hashCode() { + return key.hashCode(); + } + + public boolean equals(Object value) { + return value.equals(key); + } + } + + public class NameTable<T> { + + private final Map<Name, T> map; + + public NameTable() { + this.map = new HashMap<Name, T>(); + } + + public void put(Name key, T value) { + map.put(key, value); + } + + public void put(String text, T value) { + Name key = new StringName(text); + + map.put(key, value); + } + + public T get(String key) { + Index index = new Index(key); + + return map.get(index); + } + + public T remove(String key) { + Index index = new Index(key); + + return map.remove(index); + } + } + + public void testName() { + Name contentLength = new ArrayName("Content-Length".getBytes()); + Name contentType = new ArrayName("Content-Type".getBytes()); + Name transferEncoding = new ArrayName("Transfer-Encoding".getBytes()); + Name userAgent = new ArrayName("User-Agent".getBytes()); + NameTable<String> map = new NameTable<String>(); + + assertEquals(contentLength.hashCode(), "Content-Length".toLowerCase().hashCode()); + assertEquals(contentType.hashCode(), "Content-Type".toLowerCase().hashCode()); + assertEquals(transferEncoding.hashCode(), "Transfer-Encoding".toLowerCase().hashCode()); + assertEquals(userAgent.hashCode(), "User-Agent".toLowerCase().hashCode()); + + map.put(contentLength, "1024"); + map.put(contentType, "text/html"); + map.put(transferEncoding, "chunked"); + map.put(userAgent, "Mozilla/4.0"); + map.put("Date", "18/11/1977"); + map.put("Accept", "text/plain, text/html, image/gif"); + + assertEquals(map.get("Content-Length"), "1024"); + assertEquals(map.get("CONTENT-LENGTH"), "1024"); + assertEquals(map.get("content-length"), "1024"); + assertEquals(map.get("Content-length"), "1024"); + assertEquals(map.get("Content-Type"), "text/html"); + assertEquals(map.get("Transfer-Encoding"), "chunked"); + assertEquals(map.get("USER-AGENT"), "Mozilla/4.0"); + assertEquals(map.get("Accept"), "text/plain, text/html, image/gif"); + assertEquals(map.get("ACCEPT"), "text/plain, text/html, image/gif"); + assertEquals(map.get("accept"), "text/plain, text/html, image/gif"); + assertEquals(map.get("DATE"), "18/11/1977"); + assertEquals(map.get("Date"), "18/11/1977"); + assertEquals(map.get("date"), "18/11/1977"); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/ArrayBufferTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/ArrayBufferTest.java new file mode 100644 index 00000000..1f6f4943 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/ArrayBufferTest.java @@ -0,0 +1,54 @@ +package org.simpleframework.common.buffer; + +import org.simpleframework.common.buffer.ArrayBuffer; +import org.simpleframework.common.buffer.Buffer; + +import junit.framework.TestCase; + +public class ArrayBufferTest extends TestCase { + + public void testBuffer() throws Exception { + Buffer buffer = new ArrayBuffer(1, 2); + + buffer.append(new byte[]{'a'}).append(new byte[]{'b'}); + + assertEquals(buffer.encode(), "ab"); + assertEquals(buffer.encode("ISO-8859-1"), "ab"); + + boolean overflow = false; + + try { + buffer.append(new byte[]{'c'}); + } catch(Exception e) { + overflow = true; + } + assertTrue(overflow); + + buffer.clear(); + + assertEquals(buffer.encode(), ""); + assertEquals(buffer.encode("UTF-8"), ""); + + buffer = new ArrayBuffer(1024, 2048); + buffer.append("abcdefghijklmnopqrstuvwxyz".getBytes()); + + Buffer alphabet = buffer.allocate(); + alphabet.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes()); + + Buffer digits = buffer.allocate(); + digits.append("0123456789".getBytes()); + + assertEquals(alphabet.encode(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + assertEquals(digits.encode(), "0123456789"); + assertEquals(buffer.encode(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + + Buffer extra = digits.allocate(); + extra.append("#@?".getBytes()); + + assertEquals(extra.encode(), "#@?"); + assertEquals(digits.encode(), "0123456789#@?"); + assertEquals(buffer.encode(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#@?"); + assertEquals(buffer.length(), 65); + } + +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/BufferAllocatorTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/BufferAllocatorTest.java new file mode 100644 index 00000000..95840471 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/BufferAllocatorTest.java @@ -0,0 +1,79 @@ +package org.simpleframework.common.buffer; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.ArrayAllocator; +import org.simpleframework.common.buffer.Buffer; +import org.simpleframework.common.buffer.BufferAllocator; + +import junit.framework.TestCase; + +public class BufferAllocatorTest extends TestCase { + + public void testBuffer() throws Exception { + Allocator allocator = new ArrayAllocator(1, 2); + Buffer buffer = new BufferAllocator(allocator, 1, 2); + + buffer.append(new byte[]{'a'}).append(new byte[]{'b'}); + + assertEquals(buffer.encode(), "ab"); + assertEquals(buffer.encode("ISO-8859-1"), "ab"); + + boolean overflow = false; + + try { + buffer.append(new byte[]{'c'}); + } catch(Exception e) { + overflow = true; + } + assertTrue(overflow); + + buffer.clear(); + + assertEquals(buffer.encode(), ""); + assertEquals(buffer.encode("UTF-8"), ""); + + allocator = new ArrayAllocator(1024, 2048); + buffer = new BufferAllocator(allocator, 1024, 2048); + buffer.append("abcdefghijklmnopqrstuvwxyz".getBytes()); + + Buffer alphabet = buffer.allocate(); + alphabet.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes()); + + Buffer digits = buffer.allocate(); + digits.append("0123456789".getBytes()); + + assertEquals(alphabet.encode(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + assertEquals(digits.encode(), "0123456789"); + assertEquals(buffer.encode(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + + Buffer extra = digits.allocate(); + extra.append("#@?".getBytes()); + + assertEquals(extra.encode(), "#@?"); + assertEquals(extra.length(), 3); + assertEquals(digits.encode(), "0123456789#@?"); + assertEquals(digits.length(), 13); + assertEquals(buffer.encode(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#@?"); + assertEquals(buffer.length(), 65); + } + + public void testCascadingBufferAllocator() throws Exception { + Allocator allocator = new ArrayAllocator(1024, 2048); + allocator = new BufferAllocator(allocator, 1024, 2048); + allocator = new BufferAllocator(allocator, 1024, 2048); + allocator = new BufferAllocator(allocator, 1024, 2048); + allocator = new BufferAllocator(allocator, 1024, 2048); + + Buffer buffer = allocator.allocate(1024); + + buffer.append("abcdefghijklmnopqrstuvwxyz".getBytes()); + + assertEquals(buffer.encode(), "abcdefghijklmnopqrstuvwxyz"); + + buffer.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes()); + + assertEquals(buffer.encode(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + assertEquals(buffer.length(), 52); + } + +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileBufferTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileBufferTest.java new file mode 100644 index 00000000..54cb142b --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileBufferTest.java @@ -0,0 +1,45 @@ +package org.simpleframework.common.buffer; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.simpleframework.common.buffer.Buffer; +import org.simpleframework.common.buffer.FileBuffer; + +import junit.framework.TestCase; + +public class FileBufferTest extends TestCase { + + public void testFileBuffer() throws Exception { + File tempFile = File.createTempFile(FileBufferTest.class.getSimpleName(), null); + Buffer buffer = new FileBuffer(tempFile); + buffer.append("abcdefghijklmnopqrstuvwxyz".getBytes()); + + Buffer alphabet = buffer.allocate(); + alphabet.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes()); + + Buffer digits = buffer.allocate(); + digits.append("0123456789".getBytes()); + + expect(buffer, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes()); + expect(alphabet, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes()); + expect(digits, "0123456789".getBytes()); + } + + private void expect(Buffer buffer, byte[] expect) throws IOException { + InputStream result = buffer.open(); + + for(int i =0; i < expect.length; i++) { + byte octet = expect[i]; + int value = result.read(); + + if(value < 0) { + throw new IOException("Buffer exhausted too early"); + } + assertEquals(octet, (byte)value); + } + assertEquals(-1, result.read()); + } + +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileByteQueue.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileByteQueue.java new file mode 100644 index 00000000..b4045629 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileByteQueue.java @@ -0,0 +1,109 @@ +package org.simpleframework.common.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; +import org.simpleframework.common.buffer.BufferAllocator; + +public class FileByteQueue { + + private BlockingQueue<Block> blocks; + private BlockAllocator allocator; + private Block source; + + public FileByteQueue(Allocator allocator) throws IOException { + this.blocks = new LinkedBlockingQueue<Block>(); + this.allocator = new BlockAllocator(allocator); + } + + public int read(byte[] array, int off, int size) throws Exception { + int left = blocks.size(); + int mark = size; + + for(int i = 0; source != null || i < left; i++) { + if(source == null) { + source = blocks.take(); + } + int remain = source.remaining(); + int read = Math.min(remain, size); + + if(read > 0) { + source.read(array, off, size); + size -= read; + off += read; + } + if(remain == 0) { + source.close(); // clear up file handles + source = null; + } + if(size <= 0) { + return mark; + } + } + return mark - size; + } + + public void write(byte[] array, int off, int size) throws Exception { + Block buffer = allocator.allocate(array, off, size); + + if(size > 0) { + blocks.offer(buffer); + } + } + + private class BlockAllocator { + + private Allocator allocator; + + public BlockAllocator(Allocator allocator) { + this.allocator = new BufferAllocator(allocator); + } + + public Block allocate(byte[] array, int off, int size) throws IOException { + Buffer buffer = allocator.allocate(); + + if(size > 0) { + buffer.append(array, off, size); + } + return new Block(buffer, size); + } + } + + private class Block { + + private InputStream source; + private int remaining; + private int size; + + public Block(Buffer buffer, int size) throws IOException { + this.source = buffer.open(); + this.remaining = size; + this.size = size; + } + + public int read(byte[] array, int off, int size) throws IOException { + int count = source.read(array, off, size); + + if(count > 0) { + remaining -= size; + } + return count; + } + + public void close() throws IOException { + source.close(); + } + + public int remaining() { + return remaining; + } + + public int size() { + return size; + } + } +}
\ No newline at end of file diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileByteQueueTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileByteQueueTest.java new file mode 100644 index 00000000..96994543 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/FileByteQueueTest.java @@ -0,0 +1,22 @@ +package org.simpleframework.common.buffer; + +import junit.framework.TestCase; + +public class FileByteQueueTest extends TestCase { + + public void testQueue() throws Exception { + /* Allocator allocator = new FileAllocator(); + FileByteQueue queue = new FileByteQueue(allocator); + for(int i = 0; i < 26; i++) { + queue.write(new byte[]{(byte)(i+'a')}, 0, 1); + System.err.println("WRITE>>"+(char)(i+'a')); + } + for(int i = 0; i < 26; i++) { + byte[] buffer = new byte[1]; + assertEquals(queue.read(buffer, 0, 1), 1); + System.err.println("READ>>"+((char)buffer[0])); + assertEquals(buffer[0], (byte)(i+'a')); + }*/ + } + +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ArrayByteQueue.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ArrayByteQueue.java new file mode 100644 index 00000000..893ae802 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ArrayByteQueue.java @@ -0,0 +1,100 @@ +package org.simpleframework.common.buffer.queue; + +import java.io.IOException; + +import org.simpleframework.common.buffer.BufferException; + +public class ArrayByteQueue implements ByteQueue { + + private byte[] buffer; + private int limit; + private int count; + private int seek; + private boolean closed; + + public ArrayByteQueue(int limit) { + this.buffer = new byte[16]; + this.limit = limit; + } + + public synchronized void write(byte[] array) throws IOException { + write(array, 0, array.length); + } + + public synchronized void write(byte[] array, int off, int size) throws IOException { + if(closed) { + throw new BufferException("Queue has been closed"); + } + if (size + count > buffer.length) { + expand(count + size); + } + int fragment = buffer.length - seek; // from read pos to end + int space = fragment - count; // space at end + + if(space >= size) { + System.arraycopy(array, off, buffer, seek + count, size); + } else { + int chunk = Math.min(fragment, count); + + System.arraycopy(buffer, seek, buffer, 0, chunk); // adjust downward + System.arraycopy(array, off, buffer, chunk, size); + seek = 0; + } + notify(); + count += size; + } + + public synchronized int read(byte[] array) throws IOException { + return read(array, 0, array.length); + } + + public synchronized int read(byte[] array, int off, int size) throws IOException { + while(count == 0) { + try { + if(closed) { + return -1; + } + wait(); + } catch(Exception e) { + throw new BufferException("Thread interrupted", e); + } + } + int chunk = Math.min(size, count); + + if(chunk > 0) { + System.arraycopy(buffer, seek, array, off, chunk); + seek += chunk; + count -= chunk; + } + return chunk; + } + + private synchronized void expand(int capacity) throws IOException { + if (capacity > limit) { + throw new BufferException("Capacity limit %s exceeded", limit); + } + int resize = buffer.length * 2; + int size = Math.max(capacity, resize); + byte[] temp = new byte[size]; + + System.arraycopy(buffer, seek, temp, 0, count); + buffer = temp; + seek = 0; + } + + public synchronized void reset() throws IOException { + if(closed) { + throw new BufferException("Queue has been closed"); + } + seek = 0; + count = 0; + } + + public synchronized int available() { + return count; + } + + public synchronized void close() { + closed = true; + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ArrayByteQueueTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ArrayByteQueueTest.java new file mode 100644 index 00000000..7d361f37 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ArrayByteQueueTest.java @@ -0,0 +1,119 @@ +package org.simpleframework.common.buffer.queue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import junit.framework.TestCase; + +public class ArrayByteQueueTest extends TestCase { + + public void testArrayByteQueue() throws Exception { + ArrayByteQueue queue = new ArrayByteQueue(10); + + for(int i = 0; i < 9; i++) { + queue.write(new byte[]{(byte)('A'+i)}); + } + for(int i = 0; i < 9; i++) { + byte[] b = new byte[1]; + queue.read(b); + System.err.write(b); + System.err.println(); + } + for(int i = 9; i < 19; i++) { + queue.write(new byte[]{(byte)('A'+i)}); + } + for(int i = 0; i < 9; i++) { + byte[] b = new byte[1]; + queue.read(b); + System.err.write(b); + System.err.println(); + } + } + + public void testRandomReadWrite() throws Exception { + ArrayByteQueue queue = new ArrayByteQueue(1024 * 10); + + for(int i = 0; i < 100; i++) { + String text = "Test: "+i; + queue.write(text.getBytes()); + } + for(int i = 0; i < 100; i++) { + String text = "Test: "+i; + byte[] buffer = new byte[256]; + int size = queue.read(buffer, 0, text.length()); + String result = new String(buffer, 0, size); + System.err.println(result); + assertEquals(result, text); + } + } + /* + public void testStream() throws Exception { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + final ArrayByteQueue queue = new ArrayByteQueue(1024 * 10); + final Thread reader = new Thread(new Runnable() { + public void run() { + try { + for(int i = 0; i < 100; i++) { + byte[] chunk = new byte[(int)Math.round((Math.random() * 100))]; + int size = queue.read(chunk); + output.write(chunk, 0, size); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + }); + final Thread writer = new Thread(new Runnable() { + public void run() { + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ObjectOutputStream objectOutput = new ObjectOutputStream(buffer); + + for(int i = 0; i < 100; i++) { + try { + TestMessage message = new TestMessage(i, "Test Message: " +i); + objectOutput.writeObject(message); + objectOutput.flush(); + byte[] messageBytes = buffer.toByteArray(); + queue.write(messageBytes); + buffer.reset(); // clear out the buffer so toByteArray picks up changes only + } catch(Exception e) { + e.printStackTrace(); + } + } + }catch(Exception e){ + e.printStackTrace(); + } + } + }); + writer.start(); + reader.start(); + writer.join(); + Thread.sleep(5000); + reader.interrupt(); + reader.join(); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + ObjectInputStream objectInput = new ObjectInputStream(input); + + for(int i = 0; i < 100; i++) { + TestMessage message = (TestMessage)objectInput.readObject(); + assertEquals(message.count, i); + assertEquals(message.text, "Test Message: "+i); + } + } +*/ + private static class TestMessage implements Serializable { + + public final int count; + public final String text; + + public TestMessage(int count, String text) { + this.count = count; + this.text = text; + } + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/BufferQueue.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/BufferQueue.java new file mode 100644 index 00000000..5f3e97f8 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/BufferQueue.java @@ -0,0 +1,67 @@ +package org.simpleframework.common.buffer.queue; + +import java.io.IOException; +import java.io.InputStream; + +import org.simpleframework.common.buffer.ArrayBuffer; +import org.simpleframework.common.buffer.Buffer; + +public class BufferQueue implements Buffer { + + private final ByteQueue queue; + private final Buffer buffer; + + public BufferQueue(ByteQueue queue) { + this.buffer = new ArrayBuffer(); + this.queue = queue; + } + + public InputStream open() throws IOException { + return new ByteQueueStream(queue); + } + + public Buffer allocate() throws IOException { + return new BufferQueue(queue); + } + + public String encode() throws IOException { + return encode("UTF-8"); + } + + public String encode(String charset) throws IOException { + InputStream source = open(); + byte[] chunk = new byte[512]; + int count = 0; + + while((count = source.read(chunk)) != -1) { + buffer.append(chunk, 0, count); + } + return buffer.encode(charset); + } + + public Buffer append(byte[] array) throws IOException { + if(array.length > 0) { + queue.write(array); + } + return this; + } + + public Buffer append(byte[] array, int off, int len) throws IOException { + if(len > 0) { + queue.write(array, off, len); + } + return this; + } + + public void clear() throws IOException { + queue.reset(); + } + + public void close() throws IOException { + queue.close(); + } + + public long length() { + return buffer.length(); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/BufferQueueTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/BufferQueueTest.java new file mode 100644 index 00000000..22eaba7c --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/BufferQueueTest.java @@ -0,0 +1,44 @@ +package org.simpleframework.common.buffer.queue; + +import java.io.InputStream; + +import junit.framework.TestCase; + +public class BufferQueueTest extends TestCase { + + public void testBufferQueue() throws Exception { + final ByteQueue queue = new ArrayByteQueue(1024 * 1000); + final BufferQueue buffer = new BufferQueue(queue); + + Thread reader = new Thread(new Runnable() { + public void run() { + try { + InputStream source = buffer.open(); + for(int i = 0; i < 1000; i++) { + int octet = source.read(); + System.err.write(octet); + System.err.flush(); + } + }catch(Exception e) { + e.printStackTrace(); + } + } + }); + Thread writer = new Thread(new Runnable() { + public void run() { + try { + for(int i = 0; i < 1000; i++) { + buffer.append(("Test message: "+i+"\n").getBytes()); + } + }catch(Exception e) { + e.printStackTrace(); + } + } + }); + reader.start(); + writer.start(); + reader.join(); + writer.join(); + } + +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ByteQueue.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ByteQueue.java new file mode 100644 index 00000000..dc567e91 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ByteQueue.java @@ -0,0 +1,13 @@ +package org.simpleframework.common.buffer.queue; + +import java.io.IOException; + +public interface ByteQueue { + void write(byte[] array) throws IOException; + void write(byte[] array, int off, int size) throws IOException; + int read(byte[] array) throws IOException; + int read(byte[] array, int off, int size) throws IOException; + int available() throws IOException; + void reset() throws IOException; + void close() throws IOException; +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ByteQueueStream.java b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ByteQueueStream.java new file mode 100644 index 00000000..dbf73e18 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/buffer/queue/ByteQueueStream.java @@ -0,0 +1,40 @@ +package org.simpleframework.common.buffer.queue; + +import java.io.IOException; +import java.io.InputStream; + +public class ByteQueueStream extends InputStream { + + private final ByteQueue queue; + + public ByteQueueStream(ByteQueue queue) { + this.queue = queue; + } + + @Override + public int read() throws IOException { + byte[] array = new byte[1]; + int count = read(array) ; + + if(count != -1) { + return array[0] & 0xff; + } + return -1; + } + + public int read(byte[] buffer) throws IOException { + return queue.read(buffer, 0, buffer.length); + } + + public int read(byte[] buffer, int off, int size) throws IOException { + return queue.read(buffer, off, size); + } + + public int available() throws IOException { + return queue.available(); + } + + public void close() throws IOException { + queue.close(); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/lease/ContractQueueTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/lease/ContractQueueTest.java new file mode 100644 index 00000000..6fa55c18 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/lease/ContractQueueTest.java @@ -0,0 +1,57 @@ +package org.simpleframework.common.lease; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.simpleframework.common.lease.Contract; +import org.simpleframework.common.lease.ContractQueue; +import org.simpleframework.common.lease.Expiration; + +public class ContractQueueTest extends TimeTestCase { + + public void testTimeUnits() throws Exception { + ContractQueue<Long> queue = new ContractQueue<Long>(); + List<String> complete = new ArrayList<String>(); + + for(long i = 0; i < 10000; i++) { + long random = (long)(Math.random() * 1000); + Contract<Long> contract = new Expiration(random, random, TimeUnit.NANOSECONDS); + + queue.offer(contract); + } + for(int i = 0; i < 10000; i++) { + Contract<Long> contract = queue.take(); + + assertGreaterThanOrEqual(contract.getDelay(TimeUnit.NANOSECONDS), contract.getDelay(TimeUnit.NANOSECONDS)); + assertGreaterThanOrEqual(contract.getDelay(TimeUnit.MILLISECONDS), contract.getDelay(TimeUnit.MILLISECONDS)); + assertGreaterThanOrEqual(contract.getDelay(TimeUnit.SECONDS), contract.getDelay(TimeUnit.SECONDS)); + + long nanoseconds = contract.getDelay(TimeUnit.NANOSECONDS); + long milliseconds = contract.getDelay(TimeUnit.MILLISECONDS); + + complete.add(String.format("index=[%s] nano=[%s] milli=[%s]", i, nanoseconds, milliseconds)); + } + for(int i = 0; i < 10000; i++) { + System.err.println("expiry=[" + complete.get(i)+ "]"); + } + } + + public void testAccuracy() throws Exception { + ContractQueue<Long> queue = new ContractQueue<Long>(); + + for(long i = 0; i < 10000; i++) { + long random = (long)(Math.random() * 1000); + Contract<Long> contract = new Expiration(random, random, TimeUnit.NANOSECONDS); + + queue.offer(contract); + } + for(int i = 0; i < 10000; i++) { + Contract<Long> contract = queue.take(); + + assertLessThanOrEqual(-2000, contract.getDelay(TimeUnit.MILLISECONDS)); + } + } + +} + diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/lease/ContractTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/lease/ContractTest.java new file mode 100644 index 00000000..1cc40af4 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/lease/ContractTest.java @@ -0,0 +1,40 @@ +package org.simpleframework.common.lease; + +import java.util.concurrent.TimeUnit; + +import org.simpleframework.common.lease.Contract; +import org.simpleframework.common.lease.Expiration; + +public class ContractTest extends TimeTestCase { + + public void testContract() throws Exception { + Contract ten = new Expiration(this, 10, TimeUnit.MILLISECONDS); + Contract twenty = new Expiration(this, 20, TimeUnit.MILLISECONDS); + Contract thirty= new Expiration(this, 30, TimeUnit.MILLISECONDS); + + assertGreaterThanOrEqual(twenty.getDelay(TimeUnit.NANOSECONDS), ten.getDelay(TimeUnit.NANOSECONDS)); + assertGreaterThanOrEqual(thirty.getDelay(TimeUnit.NANOSECONDS), twenty.getDelay(TimeUnit.NANOSECONDS)); + + assertGreaterThanOrEqual(twenty.getDelay(TimeUnit.MILLISECONDS), ten.getDelay(TimeUnit.MILLISECONDS)); + assertGreaterThanOrEqual(thirty.getDelay(TimeUnit.MILLISECONDS), twenty.getDelay(TimeUnit.MILLISECONDS)); + + ten.setDelay(0, TimeUnit.MILLISECONDS); + twenty.setDelay(0, TimeUnit.MILLISECONDS); + + assertLessThanOrEqual(ten.getDelay(TimeUnit.MILLISECONDS), 0); + assertLessThanOrEqual(twenty.getDelay(TimeUnit.MILLISECONDS), 0); + + ten.setDelay(10, TimeUnit.MILLISECONDS); + twenty.setDelay(20, TimeUnit.MILLISECONDS); + thirty.setDelay(30, TimeUnit.MILLISECONDS); + + assertGreaterThanOrEqual(twenty.getDelay(TimeUnit.NANOSECONDS), ten.getDelay(TimeUnit.NANOSECONDS)); + assertGreaterThanOrEqual(thirty.getDelay(TimeUnit.NANOSECONDS), twenty.getDelay(TimeUnit.NANOSECONDS)); + + assertGreaterThanOrEqual(twenty.getDelay(TimeUnit.MILLISECONDS), ten.getDelay(TimeUnit.MILLISECONDS)); + assertGreaterThanOrEqual(thirty.getDelay(TimeUnit.MILLISECONDS), twenty.getDelay(TimeUnit.MILLISECONDS)); + + ten.setDelay(0, TimeUnit.MILLISECONDS); + twenty.setDelay(0, TimeUnit.MILLISECONDS); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/lease/LeaseManagerTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/lease/LeaseManagerTest.java new file mode 100644 index 00000000..9ce2b938 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/lease/LeaseManagerTest.java @@ -0,0 +1,227 @@ +package org.simpleframework.common.lease; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.simpleframework.common.lease.Cleaner; +import org.simpleframework.common.lease.Lease; +import org.simpleframework.common.lease.LeaseManager; + +public class LeaseManagerTest extends TimeTestCase { + + private static int ITERATIONS = 1000; + private static int MAXIMUM = 20000; + + static { + String value = System.getProperty("iterations"); + + if (value != null) { + ITERATIONS = Integer.parseInt(value); + } + } + + public void testClock() { + List<Long> timeList = new ArrayList<Long>(); + + for(int i = 0; i < ITERATIONS; i++) { + long time = System.nanoTime(); + long milliseconds = TimeUnit.MILLISECONDS.convert(time, TimeUnit.MILLISECONDS); + + timeList.add(milliseconds); + } + for(int i = 1; i < ITERATIONS; i++) { + assertLessThanOrEqual(timeList.get(i - 1), timeList.get(i)); + } + } + + public void testRandom() { + for(int i = 0; i < ITERATIONS; i++) { + long randomTime = getRandomTime(MAXIMUM); + + assertGreaterThanOrEqual(MAXIMUM, randomTime); + assertGreaterThanOrEqual(randomTime, 0); + } + } + + public void testOrder() throws Exception { + final BlockingQueue<Integer> clean = new LinkedBlockingQueue<Integer>(); + final ConcurrentHashMap<Integer, Long> record = new ConcurrentHashMap<Integer, Long>(); + + Cleaner<Integer> cleaner = new Cleaner<Integer>() { + + long start = System.currentTimeMillis(); + + public void clean(Integer key) { + record.put(key, start - System.currentTimeMillis()); + clean.offer(key); + + } + }; + LeaseManager<Integer> manager = new LeaseManager<Integer>(cleaner); + List<Lease<Integer>> list = new ArrayList<Lease<Integer>>(); + + long start = System.currentTimeMillis(); + + for(int i = 0; i < ITERATIONS; i++) { + long randomTime = getRandomTime(MAXIMUM) + MAXIMUM + i * 50; + + System.err.printf("leasing [%s] for [%s] @ %s%n", i, randomTime, System.currentTimeMillis() - start); + + Lease<Integer> lease = manager.lease(i, randomTime, TimeUnit.MILLISECONDS); + + list.add(lease); + } + start = System.currentTimeMillis(); + + for(int i = 0; i < ITERATIONS; i++) { + try { + System.err.printf("renewing [%s] for [%s] expires [%s] @ %s expired [%s] %n", i, i, list.get(i).getExpiry(TimeUnit.MILLISECONDS), System.currentTimeMillis() - start, record.get(i)); + list.get(i).renew(i, TimeUnit.MILLISECONDS); + }catch(Exception e) { + System.err.printf("Lease %s in error: ", i); + e.printStackTrace(System.err); + } + } + int variation = 20; + int cleaned = 0; + + for(int i = 0; i < ITERATIONS; i++) { + int value = clean.take(); + cleaned++; + + System.err.printf("index=[%s] clean=[%s] expiry[%s]=%s expiry[%s]=%s%n ", i, value, i, record.get(i), value, record.get(value)); + assertLessThanOrEqual(i - variation, value); + } + assertEquals(cleaned, ITERATIONS); + } + + public void testLease() throws Exception { + final BlockingQueue<Expectation> clean = new LinkedBlockingQueue<Expectation>(); + + Cleaner<Expectation> cleaner = new Cleaner<Expectation>() { + public void clean(Expectation key) { + clean.offer(key); + } + }; + final BlockingQueue<Lease<Expectation>> renewalQueue = new LinkedBlockingQueue<Lease<Expectation>>(); + final BlockingQueue<Lease<Expectation>> expiryQueue = new LinkedBlockingQueue<Lease<Expectation>>(); + final CountDownLatch ready = new CountDownLatch(21); + final AtomicInteger renewCount = new AtomicInteger(ITERATIONS); + + for(int i = 0; i < 20; i++) { + new Thread(new Runnable() { + public void run() { + while(renewCount.getAndDecrement() > 0) { + long randomTime = getRandomTime(MAXIMUM); + + try { + ready.countDown(); + ready.await(); + + Lease<Expectation> lease = renewalQueue.take(); + + try { + lease.renew(randomTime, TimeUnit.MILLISECONDS); + lease.getKey().setExpectation(randomTime, TimeUnit.MILLISECONDS); + + assertGreaterThanOrEqual(randomTime, 0); + assertGreaterThanOrEqual(randomTime, lease.getExpiry(TimeUnit.MILLISECONDS)); + } catch(Exception e) { + expiryQueue.offer(lease); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + } + }).start(); + } + final LeaseManager<Expectation> manager = new LeaseManager<Expectation>(cleaner); + final CountDownLatch latch = new CountDownLatch(21); + final AtomicInteger leaseCount = new AtomicInteger(ITERATIONS); + + for(int i = 0; i < 20; i++) { + new Thread(new Runnable() { + public void run() { + while(leaseCount.getAndDecrement() > 0) { + long randomTime = getRandomTime(MAXIMUM); + Expectation expectation = new Expectation(randomTime, TimeUnit.MILLISECONDS); + + try { + latch.countDown(); + latch.await(); + } catch(InterruptedException e) { + e.printStackTrace(); + } + assertGreaterThanOrEqual(randomTime, 0); + + Lease<Expectation> lease = manager.lease(expectation, randomTime, TimeUnit.MILLISECONDS); + renewalQueue.offer(lease); + } + } + }).start(); + } + ready.countDown(); + latch.countDown(); + + for (int i = 0; i < ITERATIONS; i++) { + Expectation expectation = clean.poll(MAXIMUM, TimeUnit.MILLISECONDS); + + if(expectation != null) { + long accuracy = System.nanoTime() - expectation.getExpectation(TimeUnit.NANOSECONDS); + long milliseconds = TimeUnit.MILLISECONDS.convert(accuracy, TimeUnit.NANOSECONDS); + + System.err.printf("index=[%s] accuracy=[%s] queue=[%s]%n", i, milliseconds, clean.size()); + } else { + System.err.printf("index=[%s] queue=[%s]%n", i, clean.size()); + } + + } + System.err.printf("waiting=[%s]%n", clean.size()); + } + + + public static class Expectation { + + private long time; + + public Expectation(long duration, TimeUnit unit) { + setExpectation(duration, unit); + } + + public void setExpectation(long duration, TimeUnit unit) { + long nano = TimeUnit.NANOSECONDS.convert(duration, unit); + long expect = nano + System.nanoTime(); + + this.time = expect; + } + + public long getExpectation(TimeUnit unit) { + return unit.convert(time, TimeUnit.NANOSECONDS); + } + } + + + public static long getRandomTime(long maximum) { + long random = new Random().nextLong() % maximum; + + if(random < 0) { + random *= -1; + } + return random; + } + + public static void main(String[] list) throws Exception { + new LeaseManagerTest().testClock(); + new LeaseManagerTest().testRandom(); + new LeaseManagerTest().testOrder(); + new LeaseManagerTest().testLease(); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/lease/LeaseTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/lease/LeaseTest.java new file mode 100644 index 00000000..d4b73d7e --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/lease/LeaseTest.java @@ -0,0 +1,87 @@ +package org.simpleframework.common.lease; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.simpleframework.common.lease.Cleaner; +import org.simpleframework.common.lease.Contract; +import org.simpleframework.common.lease.ContractController; +import org.simpleframework.common.lease.ContractLease; +import org.simpleframework.common.lease.ContractMaintainer; +import org.simpleframework.common.lease.Expiration; +import org.simpleframework.common.lease.Lease; + +public class LeaseTest extends TimeTestCase { + + private static int ITERATIONS = 10000; + + static { + String value = System.getProperty("iterations"); + + if (value != null) { + ITERATIONS = Integer.parseInt(value); + } + } + + public void testLease() throws Exception { + final BlockingQueue<Integer> clean = new LinkedBlockingQueue<Integer>(); + + Cleaner<Integer> cleaner = new Cleaner<Integer>() { + public void clean(Integer key) { + clean.offer(key); + } + }; + Map<Integer, Contract> table = new ConcurrentHashMap<Integer, Contract>(); + List<Lease> list = new ArrayList<Lease>(); + ContractController controller = new ContractMaintainer(cleaner); + + for (int i = 0; i < ITERATIONS; i++) { + long random = (long) (Math.random() * 1000) + 1000L; + Contract<Integer> contract = new Expiration(i, random, TimeUnit.MILLISECONDS); + Lease lease = new ContractLease(controller, contract); + + table.put(i, contract); + list.add(lease); + controller.issue(contract); + } + for (int i = 0; i < ITERATIONS; i++) { + long random = (long) (Math.random() * 1000); + + try { + list.get(i).renew(random, TimeUnit.MILLISECONDS); + } catch (Exception e) { + continue; + // e.printStackTrace(); + } + } + for (int i = 0; i < ITERATIONS; i++) { + try { + System.err.println("delay: " + + list.get(i).getExpiry(TimeUnit.MILLISECONDS)); + } catch (Exception e) { + continue; + // e.printStackTrace(); + } + } + System.err.println("clean: " + clean.size()); + + for (int i = 0; i < ITERATIONS; i++) { + Integer index = clean.take(); + Contract contract = table.get(index); + + // assertLessThanOrEqual(-4000, + // contract.getDelay(TimeUnit.MILLISECONDS)); + System.err.println(String.format("index=[%s] delay=[%s]", index, + contract.getDelay(TimeUnit.MILLISECONDS))); + } + } + + public static void main(String[] list) throws Exception { + new LeaseTest().testLease(); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/lease/TimeTestCase.java b/simple/simple-common/src/test/java/org/simpleframework/common/lease/TimeTestCase.java new file mode 100644 index 00000000..294cc9b3 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/lease/TimeTestCase.java @@ -0,0 +1,25 @@ +package org.simpleframework.common.lease; + +import junit.framework.TestCase; + +public class TimeTestCase extends TestCase { + + public void testTime() { + } + + public static void assertLessThan(long a, long b) { + assertTrue(String.format("Value %s is not less than %s", a, b), a < b); + } + + public static void assertLessThanOrEqual(long a, long b) { + assertTrue(String.format("Value %s is not less than or equal to %s", a, b), a <= b); + } + + public static void assertGreaterThan(long a, long b) { + assertTrue(String.format("Value %s is not greater than %s", a, b), a > b); + } + + public static void assertGreaterThanOrEqual(long a, long b) { + assertTrue(String.format("Value %s is not greater than or equal to %s", a, b), a >= b); + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/thread/SchedulerTest.java b/simple/simple-common/src/test/java/org/simpleframework/common/thread/SchedulerTest.java new file mode 100644 index 00000000..78fe8026 --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/thread/SchedulerTest.java @@ -0,0 +1,65 @@ +package org.simpleframework.common.thread; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.simpleframework.common.thread.ConcurrentScheduler; + +import junit.framework.TestCase; + +public class SchedulerTest extends TestCase { + + private static final int ITERATIONS = 10000; + + public void testScheduler() throws Exception { + ConcurrentScheduler queue = new ConcurrentScheduler(Runnable.class, 10); + LinkedBlockingQueue<Timer> list = new LinkedBlockingQueue<Timer>(); + + for(int i = 0; i < ITERATIONS; i++) { + queue.execute(new Task(list, new Timer(i)), i, TimeUnit.MILLISECONDS); + } + for(Timer timer = list.take(); timer.getValue() < ITERATIONS - 10; timer = list.take()) { + System.err.println("value=["+timer.getValue()+"] delay=["+timer.getDelay()+"] expect=["+timer.getExpectation()+"]"); + } + } + + public class Timer { + + private Integer value; + + private long time; + + public Timer(Integer value) { + this.time = System.currentTimeMillis(); + this.value = value; + } + + public Integer getValue() { + return value; + } + + public long getDelay() { + return System.currentTimeMillis() - time; + } + + public int getExpectation() { + return value.intValue(); + } + } + + public class Task implements Runnable { + + private LinkedBlockingQueue<Timer> queue; + + private Timer timer; + + public Task(LinkedBlockingQueue<Timer> queue, Timer timer) { + this.queue = queue; + this.timer = timer; + } + + public void run() { + queue.offer(timer); + } + } +} diff --git a/simple/simple-common/src/test/java/org/simpleframework/common/thread/TransientApplication.java b/simple/simple-common/src/test/java/org/simpleframework/common/thread/TransientApplication.java new file mode 100644 index 00000000..69941b2e --- /dev/null +++ b/simple/simple-common/src/test/java/org/simpleframework/common/thread/TransientApplication.java @@ -0,0 +1,54 @@ +package org.simpleframework.common.thread; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.simpleframework.common.thread.ConcurrentExecutor; + +public class TransientApplication { + + public static void main(String[] list) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue(); + ConcurrentExecutor pool = new ConcurrentExecutor(TerminateTask.class, 10); + + for(int i = 0; i < 50; i++) { + pool.execute(new LongTask(queue, String.valueOf(i))); + } + pool.execute(new TerminateTask(pool)); + } + + private static class TerminateTask implements Runnable { + + private ConcurrentExecutor pool; + + public TerminateTask(ConcurrentExecutor pool) { + this.pool = pool; + } + + public void run() { + pool.stop(); + } + } + + private static class LongTask implements Runnable { + + private BlockingQueue queue; + + private String name; + + public LongTask(BlockingQueue queue, String name) { + this.queue = queue; + this.name = name; + } + + public void run() { + try { + Thread.sleep(1000); + } catch(Exception e) { + e.printStackTrace(); + } + System.err.println(name); + queue.offer(name); + } + } +} |