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    }