001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.collections.buffer;
018
019 import java.io.PrintWriter;
020 import java.io.StringWriter;
021 import java.util.Collection;
022 import java.util.Iterator;
023
024 import org.apache.commons.collections.BoundedCollection;
025 import org.apache.commons.collections.Buffer;
026 import org.apache.commons.collections.BufferOverflowException;
027 import org.apache.commons.collections.BufferUnderflowException;
028 import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
029
030 /**
031 * Decorates another <code>Buffer</code> to ensure a fixed maximum size.
032 * <p>
033 * Note: This class should only be used if you need to add bounded
034 * behaviour to another buffer. If you just want a bounded buffer then
035 * you should use {@link BoundedFifoBuffer} or {@link CircularFifoBuffer}.
036 * <p>
037 * The decoration methods allow you to specify a timeout value.
038 * This alters the behaviour of the add methods when the buffer is full.
039 * Normally, when the buffer is full, the add method will throw an exception.
040 * With a timeout, the add methods will wait for up to the timeout period
041 * to try and add the elements.
042 *
043 * @author James Carman
044 * @author Stephen Colebourne
045 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
046 * @since Commons Collections 3.2
047 */
048 public class BoundedBuffer extends SynchronizedBuffer implements BoundedCollection {
049
050 /** The serialization version. */
051 private static final long serialVersionUID = 1536432911093974264L;
052
053 /** The maximum size. */
054 private final int maximumSize;
055 /** The timeout milliseconds. */
056 private final long timeout;
057
058 /**
059 * Factory method to create a bounded buffer.
060 * <p>
061 * When the buffer is full, it will immediately throw a
062 * <code>BufferOverflowException</code> on calling <code>add()</code>.
063 *
064 * @param buffer the buffer to decorate, must not be null
065 * @param maximumSize the maximum size, must be size one or greater
066 * @return a new bounded buffer
067 * @throws IllegalArgumentException if the buffer is null
068 * @throws IllegalArgumentException if the maximum size is zero or less
069 */
070 public static BoundedBuffer decorate(Buffer buffer, int maximumSize) {
071 return new BoundedBuffer(buffer, maximumSize, 0L);
072 }
073
074 /**
075 * Factory method to create a bounded buffer that blocks for a maximum
076 * amount of time.
077 *
078 * @param buffer the buffer to decorate, must not be null
079 * @param maximumSize the maximum size, must be size one or greater
080 * @param timeout the maximum amount of time to wait in milliseconds
081 * @return a new bounded buffer
082 * @throws IllegalArgumentException if the buffer is null
083 * @throws IllegalArgumentException if the maximum size is zero or less
084 */
085 public static BoundedBuffer decorate(Buffer buffer, int maximumSize, long timeout) {
086 return new BoundedBuffer(buffer, maximumSize, timeout);
087 }
088
089 //-----------------------------------------------------------------------
090 /**
091 * Constructor that wraps (not copies) another buffer, making it bounded
092 * waiting only up to a maximum amount of time.
093 *
094 * @param buffer the buffer to wrap, must not be null
095 * @param maximumSize the maximum size, must be size one or greater
096 * @param timeout the maximum amount of time to wait
097 * @throws IllegalArgumentException if the buffer is null
098 * @throws IllegalArgumentException if the maximum size is zero or less
099 */
100 protected BoundedBuffer(Buffer buffer, int maximumSize, long timeout) {
101 super(buffer);
102 if (maximumSize < 1) {
103 throw new IllegalArgumentException();
104 }
105 this.maximumSize = maximumSize;
106 this.timeout = timeout;
107 }
108
109 //-----------------------------------------------------------------------
110 public Object remove() {
111 synchronized (lock) {
112 Object returnValue = getBuffer().remove();
113 lock.notifyAll();
114 return returnValue;
115 }
116 }
117
118 public boolean add(Object o) {
119 synchronized (lock) {
120 timeoutWait(1);
121 return getBuffer().add(o);
122 }
123 }
124
125 public boolean addAll(final Collection c) {
126 synchronized (lock) {
127 timeoutWait(c.size());
128 return getBuffer().addAll(c);
129 }
130 }
131
132 public Iterator iterator() {
133 return new NotifyingIterator(collection.iterator());
134 }
135
136 private void timeoutWait(final int nAdditions) {
137 // method synchronized by callers
138 if (nAdditions > maximumSize) {
139 throw new BufferOverflowException(
140 "Buffer size cannot exceed " + maximumSize);
141 }
142 if (timeout <= 0) {
143 // no wait period (immediate timeout)
144 if (getBuffer().size() + nAdditions > maximumSize) {
145 throw new BufferOverflowException(
146 "Buffer size cannot exceed " + maximumSize);
147 }
148 return;
149 }
150 final long expiration = System.currentTimeMillis() + timeout;
151 long timeLeft = expiration - System.currentTimeMillis();
152 while (timeLeft > 0 && getBuffer().size() + nAdditions > maximumSize) {
153 try {
154 lock.wait(timeLeft);
155 timeLeft = expiration - System.currentTimeMillis();
156 } catch (InterruptedException ex) {
157 PrintWriter out = new PrintWriter(new StringWriter());
158 ex.printStackTrace(out);
159 throw new BufferUnderflowException(
160 "Caused by InterruptedException: " + out.toString());
161 }
162 }
163 if (getBuffer().size() + nAdditions > maximumSize) {
164 throw new BufferOverflowException("Timeout expired");
165 }
166 }
167
168 public boolean isFull() {
169 // size() is synchronized
170 return (size() == maxSize());
171 }
172
173 public int maxSize() {
174 return maximumSize;
175 }
176
177 //-----------------------------------------------------------------------
178 /**
179 * BoundedBuffer iterator.
180 */
181 private class NotifyingIterator extends AbstractIteratorDecorator {
182
183 public NotifyingIterator(Iterator it) {
184 super(it);
185 }
186
187 public void remove() {
188 synchronized (lock) {
189 iterator.remove();
190 lock.notifyAll();
191 }
192 }
193 }
194 }