From 420a1017eb0e5eb8e0f1486e9f413c69f03d1b39 Mon Sep 17 00:00:00 2001 From: "James W. Carman" Date: Wed, 23 Nov 2005 13:16:43 +0000 Subject: [PATCH] 37473: Implement a BoundedBuffer class git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@348429 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.html | 1 + ...undedBuffer.emptyCollection.version3.2.obj | Bin 0 -> 410 bytes ...oundedBuffer.fullCollection.version3.2.obj | Bin 0 -> 1477 bytes .../commons/collections/BufferUtils.java | 28 +++ .../collections/buffer/BoundedBuffer.java | 161 ++++++++++++++++ .../collections/buffer/TestBoundedBuffer.java | 181 ++++++++++++++++++ 6 files changed, 371 insertions(+) create mode 100644 data/test/BoundedBuffer.emptyCollection.version3.2.obj create mode 100644 data/test/BoundedBuffer.fullCollection.version3.2.obj create mode 100644 src/java/org/apache/commons/collections/buffer/BoundedBuffer.java create mode 100644 src/test/org/apache/commons/collections/buffer/TestBoundedBuffer.java diff --git a/RELEASE-NOTES.html b/RELEASE-NOTES.html index e39bee820..02cede4f8 100644 --- a/RELEASE-NOTES.html +++ b/RELEASE-NOTES.html @@ -58,6 +58,7 @@ If this causes major headaches to anyone please contact commons-dev at jakarta.a
  • DefaultedMap - Returns a default value when the key is not found, without adding the default value to the map itself [30911]
  • GrowthList - Decorator that causes set and indexed add to expand the list rather than throw IndexOutOfBoundsException [34171]
  • LoopingListIterator - When the end of the list is reached the iteration continues from the start [30166]
  • +
  • BoundedBuffer - A new wrapper class which can make any buffer bounded [37473]
  • ENHANCEMENTS

    diff --git a/data/test/BoundedBuffer.emptyCollection.version3.2.obj b/data/test/BoundedBuffer.emptyCollection.version3.2.obj new file mode 100644 index 0000000000000000000000000000000000000000..947993aa4fc0b5bd4d66cdd96f452466f5dfc393 GIT binary patch literal 410 zcmZ4UmVvdnh`~6&C|xhHATc>3RWCU|H#a}87)a;jq$ZbSg4ju=X=$lNdQSPJc`2zW zPGFvBP~-G@Kac!iVqo%Q;Lc5~$jmLx4bH4e^x3)k8MQ8SV$g8pfjip4>`6*ZMGUA(WRei1}nC zmL=+!mSpDWBh*{_FtFt0Cuf&12tnj?67$ma{gbkQL1JA|01Pur0#1U4S!iAoB-Gq8 l)AAu9cYYNYJ9FBxP-dXl3d&g+fS{s~p^kwS$VmAQ1prnmnGFB{ literal 0 HcmV?d00001 diff --git a/data/test/BoundedBuffer.fullCollection.version3.2.obj b/data/test/BoundedBuffer.fullCollection.version3.2.obj new file mode 100644 index 0000000000000000000000000000000000000000..cdc3e8e499c8e12b4b0c391eb9fe41ee1b7ae5ff GIT binary patch literal 1477 zcma*nxo^{89LDiC7byux8)!KTwA_~il%pj;OA3XQ&_YAFFWYfaw?2Ynmw*sb7Z_MU zFd)={3H5(qK}<;e1xSdU8JNH((Kl?xi$9iMKmNUsWY2#Wv6>_LY^Ng0Rau?XB-J)e z+p76z!_ZV$53G3QvgSxx+p|hqDf{=Tb7J=8n_nM(hDA6hlBQhOP0uXo54BMdcXd;< zJ-6UMpW-P1nLg_%TBZ#I%N${*4-D4?T}U&ih=l*Pk{o z60PN~=*myYGjiHZDw>n}Huv_&?9=|R2<1d$9M#svjn5_rzn-<|Bl{D1xFWh8CeXYD0@r0wvKBv=l8v z%TYV(Kq<5Wtwf!u3#~${(HgWCtwZZkH`;(UqD`m=^`gya3)+gdq3vh~+KG0d-DnTm zizJjr`_O)L0QI4R=ny)Lj-Y-tfR3UJI);v;6KD_(p_3?!hS4c>8jYYc=q$>iQFIRF z(Rnn6#?b|I5lx^1x`c}8GP;7UqHE|nx`A$@Tj(~rgYF_3DM+mcUw16{Y5Z;;e*o+P BQt$u( literal 0 HcmV?d00001 diff --git a/src/java/org/apache/commons/collections/BufferUtils.java b/src/java/org/apache/commons/collections/BufferUtils.java index 77eba4610..79286ff8c 100644 --- a/src/java/org/apache/commons/collections/BufferUtils.java +++ b/src/java/org/apache/commons/collections/BufferUtils.java @@ -21,6 +21,7 @@ import org.apache.commons.collections.buffer.SynchronizedBuffer; import org.apache.commons.collections.buffer.TransformedBuffer; import org.apache.commons.collections.buffer.TypedBuffer; import org.apache.commons.collections.buffer.UnmodifiableBuffer; +import org.apache.commons.collections.buffer.BoundedBuffer; /** * Provides utility methods and decorators for {@link Buffer} instances. @@ -102,6 +103,33 @@ public class BufferUtils { return BlockingBuffer.decorate(buffer, timeout); } + /** + * Returns a synchronized buffer backed by the given buffer that will block on {@link Buffer#add(Object)} and + * {@link Buffer#addAll(java.util.Collection)} until enough object(s) are removed from the buffer to allow + * the object(s) to be added and still maintain the maximum size. + * @param buffer the buffer to make bounded + * @param maximumSize the maximum size + * @return a bounded buffer backed by the given buffer + * @throws IllegalArgumentException if the given buffer is null + */ + public static Buffer boundedBuffer( Buffer buffer, int maximumSize ) { + return BoundedBuffer.decorate( buffer, maximumSize ); + } + + /** + * Returns a synchronized buffer backed by the given buffer that will block on {@link Buffer#add(Object)} and + * {@link Buffer#addAll(java.util.Collection)} until enough object(s) are removed from the buffer to allow + * the object(s) to be added and still maintain the maximum size or the timeout expires. + * @param buffer the buffer to make bounded + * @param maximumSize the maximum size + * @param timeout the maximum time to wait + * @return a bounded buffer backed by the given buffer + * @throws IllegalArgumentException if the given buffer is null + */ + public static Buffer boundedBuffer( Buffer buffer, int maximumSize, long timeout ) { + return BoundedBuffer.decorate( buffer, maximumSize, timeout ); + } + /** * Returns an unmodifiable buffer backed by the given buffer. * diff --git a/src/java/org/apache/commons/collections/buffer/BoundedBuffer.java b/src/java/org/apache/commons/collections/buffer/BoundedBuffer.java new file mode 100644 index 000000000..f838f1bc4 --- /dev/null +++ b/src/java/org/apache/commons/collections/buffer/BoundedBuffer.java @@ -0,0 +1,161 @@ +/* + * Copyright 2001-2005 The Apache Software Foundation + * + * 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.apache.commons.collections.buffer; + +import org.apache.commons.collections.Buffer; +import org.apache.commons.collections.BufferOverflowException; +import org.apache.commons.collections.BufferUnderflowException; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collection; +import java.util.Iterator; + +/** + * A wrapper class for buffers which makes them bounded. + * @author James Carman + * @since 3.2 + */ +public class BoundedBuffer extends SynchronizedBuffer { + + private static final long serialVersionUID = 1536432911093974264L; + + private final int maximumSize; + private final long timeout; + + /** + * Factory method to create a bounded buffer. + * @param buffer the buffer to decorate, must not be null + * @param maximumSize the maximum size + * @return a new bounded buffer + * @throws IllegalArgumentException if the buffer is null + */ + public static Buffer decorate( Buffer buffer, int maximumSize ) { + return new BoundedBuffer( buffer, maximumSize ); + } + + /** + * Factory method to create a bounded buffer that blocks for a maximum + * amount of time. + * @param buffer the buffer to decorate, must not be null + * @param maximumSize the maximum size + * @param timeout the maximum amount of time to wait. + * @return a new bounded buffer + * @throws IllegalArgumentException if the buffer is null + */ + public static Buffer decorate( Buffer buffer, int maximumSize, long timeout ) { + return new BoundedBuffer( buffer, maximumSize, timeout ); + } + + /** + * Constructor that wraps (not copies) another buffer, making it bounded. + * @param buffer the buffer to wrap, must not be null + * @param maximumSize the maximum size of the buffer + * @throws IllegalArgumentException if the buffer is null + */ + protected BoundedBuffer( Buffer buffer, int maximumSize ) { + this( buffer, maximumSize, -1 ); + } + + /** + * Constructor that wraps (not copies) another buffer, making it bounded waiting only up to + * a maximum amount of time. + * @param buffer the buffer to wrap, must not be null + * @param maximumSize the maximum size of the buffer + * @param timeout the maximum amount of time to wait + * @throws IllegalArgumentException if the buffer is null + */ + protected BoundedBuffer( Buffer buffer, int maximumSize, long timeout ) { + super( buffer ); + this.maximumSize = maximumSize; + this.timeout = timeout; + } + + public Object remove() { + synchronized( lock ) { + Object returnValue = getBuffer().remove(); + lock.notifyAll(); + return returnValue; + } + } + + public boolean add( Object o ) { + synchronized( lock ) { + timeoutWait( 1 ); + return getBuffer().add( o ); + } + } + + public boolean addAll( final Collection c ) { + synchronized( lock ) { + timeoutWait( c.size() ); + return getBuffer().addAll( c ); + } + } + + public Iterator iterator() { + return new NotifyingIterator( collection.iterator() ); + } + + private void timeoutWait( final int nAdditions ) { + synchronized( lock ) { + if( timeout < 0 && getBuffer().size() + nAdditions > maximumSize ) { + throw new BufferOverflowException( "Buffer size cannot exceed " + maximumSize + "." ); + } + final long expiration = System.currentTimeMillis() + timeout; + long timeLeft = expiration - System.currentTimeMillis(); + while( timeLeft > 0 && getBuffer().size() + nAdditions > maximumSize ) { + try { + lock.wait( timeLeft ); + timeLeft = expiration - System.currentTimeMillis(); + } + catch( InterruptedException e ) { + PrintWriter out = new PrintWriter( new StringWriter() ); + e.printStackTrace( out ); + throw new BufferUnderflowException( "Caused by InterruptedException: " + out.toString() ); + } + } + if( getBuffer().size() + nAdditions > maximumSize ) { + throw new BufferOverflowException( "Timeout expired." ); + } + } + } + + private class NotifyingIterator implements Iterator { + + private final Iterator i; + + public NotifyingIterator( Iterator i ) { + this.i = i; + } + + public void remove() { + synchronized( lock ) { + i.remove(); + lock.notifyAll(); + } + } + + public boolean hasNext() { + return i.hasNext(); + } + + public Object next() { + return i.next(); + } + } +} + diff --git a/src/test/org/apache/commons/collections/buffer/TestBoundedBuffer.java b/src/test/org/apache/commons/collections/buffer/TestBoundedBuffer.java new file mode 100644 index 000000000..53382da9a --- /dev/null +++ b/src/test/org/apache/commons/collections/buffer/TestBoundedBuffer.java @@ -0,0 +1,181 @@ +/* + * Copyright 2001-2005 The Apache Software Foundation + * + * 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.apache.commons.collections.buffer; + +import org.apache.commons.collections.AbstractTestObject; +import org.apache.commons.collections.Buffer; +import org.apache.commons.collections.BufferOverflowException; + +import java.util.Iterator; +import java.util.Collections; +import java.util.Arrays; + +public class TestBoundedBuffer extends AbstractTestObject { + + public TestBoundedBuffer( String testName ) { + super( testName ); + } + + public String getCompatibilityVersion() { + return "3.2"; + } + + public boolean isEqualsCheckable() { + return false; + } + + public Object makeObject() { + return BoundedBuffer.decorate( new UnboundedFifoBuffer(), 1 ); + } + + public void testAddToFullBufferNoTimeout() { + final Buffer bounded = BoundedBuffer.decorate( new UnboundedFifoBuffer(), 1 ); + bounded.add( "Hello" ); + try { + bounded.add( "World" ); + fail(); + } + catch( BufferOverflowException e ) { + } + } + + public void testAddAllToFullBufferNoTimeout() { + final Buffer bounded = BoundedBuffer.decorate( new UnboundedFifoBuffer(), 1 ); + bounded.add( "Hello" ); + try { + bounded.addAll( Collections.singleton( "World" ) ); + fail(); + } + catch( BufferOverflowException e ) { + } + } + + public void testAddToFullBufferRemoveViaIterator() { + final Buffer bounded = BoundedBuffer.decorate( new UnboundedFifoBuffer(), 1, 500 ); + bounded.add( "Hello" ); + new DelayedIteratorRemove( bounded, 200 ).start(); + bounded.add( "World" ); + assertEquals( 1, bounded.size() ); + assertEquals( "World", bounded.get() ); + + } + + public void testAddAllToFullBufferRemoveViaIterator() { + final Buffer bounded = BoundedBuffer.decorate( new UnboundedFifoBuffer(), 2, 500 ); + bounded.add( "Hello" ); + bounded.add( "World" ); + new DelayedIteratorRemove( bounded, 200, 2 ).start(); + bounded.addAll( Arrays.asList( new String[] { "Foo", "Bar" } ) ); + assertEquals( 2, bounded.size() ); + assertEquals( "Foo", bounded.remove() ); + assertEquals( "Bar", bounded.remove() ); + } + + public void testAddToFullBufferWithTimeout() { + final Buffer bounded = BoundedBuffer.decorate( new UnboundedFifoBuffer(), 1, 500 ); + bounded.add( "Hello" ); + new DelayedRemove( bounded, 200 ).start(); + bounded.add( "World" ); + assertEquals( 1, bounded.size() ); + assertEquals( "World", bounded.get() ); + try { + bounded.add( "!" ); + fail(); + } + catch( BufferOverflowException e ) { + } + } + + public void testAddAllToFullBufferWithTimeout() { + final Buffer bounded = BoundedBuffer.decorate( new UnboundedFifoBuffer(), 2, 500 ); + bounded.add( "Hello" ); + bounded.add( "World" ); + new DelayedRemove( bounded, 200, 2 ).start(); + + bounded.addAll( Arrays.asList( new String[] { "Foo", "Bar" } ) ); + assertEquals( 2, bounded.size() ); + assertEquals( "Foo", bounded.get() ); + try { + bounded.add( "!" ); + fail(); + } + catch( BufferOverflowException e ) { + } + } + + private class DelayedIteratorRemove extends Thread { + + private final Buffer buffer; + + private final long delay; + + private final int nToRemove; + + public DelayedIteratorRemove( Buffer buffer, long delay, int nToRemove ) { + this.buffer = buffer; + this.delay = delay; + this.nToRemove = nToRemove; + } + + public DelayedIteratorRemove( Buffer buffer, long delay ) { + this( buffer, delay, 1 ); + } + + public void run() { + try { + Thread.sleep( delay ); + Iterator iter = buffer.iterator(); + for( int i = 0; i < nToRemove; ++i ) { + iter.next(); + iter.remove(); + } + + } + catch( InterruptedException e ) { + } + } + } + + private class DelayedRemove extends Thread { + + private final Buffer buffer; + + private final long delay; + + private final int nToRemove; + + public DelayedRemove( Buffer buffer, long delay, int nToRemove ) { + this.buffer = buffer; + this.delay = delay; + this.nToRemove = nToRemove; + } + + public DelayedRemove( Buffer buffer, long delay ) { + this( buffer, delay, 1 ); + } + + public void run() { + try { + Thread.sleep( delay ); + for( int i = 0; i < nToRemove; ++i ) { + buffer.remove(); + } + } + catch( InterruptedException e ) { + } + } + } +} \ No newline at end of file