/* * @copyright 2011 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Book Catalogue is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue.utils; import java.util.Stack; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * Based loosely on LinkedBlockingQueue. Ideally we would use BlockingDeque but that is only * available in Android 2.3. * * A much-simplified blocking stack that satisfies the need of this application. Should be * replaceable with BlockingDeque when we set the min. version requirement to 2.3! * * @author Philip Warner * @param <T> */ public class BlockingStack<T> { // Underlying stack object private Stack<T> mStack; // Lock help by pop and by push when an item was added to an empty stack. private final ReentrantLock mPopLock = new ReentrantLock(); // Signal for available items private final Condition mNotEmpty = mPopLock.newCondition(); // Lock held by push(). Probably not needed since we sync on mStack... private final ReentrantLock mPushLock = new ReentrantLock(); public BlockingStack() { mStack = new Stack<T>(); } /** * Get the size of the stack */ public int size() { return mStack.size(); } /** * Remove the passed element, if present. */ public boolean remove(T o) { synchronized(mStack) { return mStack.remove(o); } } /** * Return a copy of all elements for safe examination. Obviously this * collection will not reflect reality for very long, but is safe to * iterate etc. * * @return */ public Stack<T> getElements() { Stack<T> copy = new Stack<T>(); synchronized(mStack) { for(T o : mStack) { copy.add(o); } } return copy; } /** * Add an object to the stack and signal * * @param object Object to add * * @throws InterruptedException */ public void push(T object) throws InterruptedException { final ReentrantLock pushLock = this.mPushLock; // This will hold the original stack size, before push. int origSize; // Make sure we are the only 'pusher' here. pushLock.lockInterruptibly(); try { // Add the object and get the size of the current stack // we 'synchronize' because it is not at all clear that // push and pop can be done concurrently (unlike the // linked list versions of queues). synchronized(mStack) { origSize = mStack.size(); mStack.push(object); } } finally { pushLock.unlock(); } if (origSize == 0) { // It was an empty stack; signal that it has some objects now. // But we need to take the popLock because the pop code also // messes with this. final ReentrantLock popLock = mPopLock; popLock.lock(); try { mNotEmpty.signal(); } finally { popLock.unlock(); } } } /** * Remove an object from the stack, wait if none. * * @return * @throws InterruptedException */ public T pop(long waitMilliseconds) throws InterruptedException { final ReentrantLock popLock = mPopLock; T o; boolean noTimeLimit = (waitMilliseconds <= 0); // Make sure we are the only popper. popLock.lockInterruptibly(); try { o = poll(); // If none left, wait for another thread to signal. while (o == null) { // Wait for the notEmpty condition, or until timeout if one was specified if (noTimeLimit) mNotEmpty.await(); else { waitMilliseconds = mNotEmpty.awaitNanos(TimeUnit.MILLISECONDS.toNanos(waitMilliseconds)); if (waitMilliseconds <= 0) // Ran out of time break; } // Try getting an object o = poll(); }; } finally { popLock.unlock(); } return o; } /** * Return an object if available, otherwise null. * * @return Object * * @throws InterruptedException */ public T poll() throws InterruptedException { final ReentrantLock popLock = mPopLock; T o = null; // Make sure we are the only popper. popLock.lockInterruptibly(); try { int count; // Get the current size synchronized(mStack) { count = mStack.size(); // If any present, we know no-one will delete (we are the popper) so get it. if (count > 0) { // Pop an item o = mStack.pop(); } } // If, after popping, there would be more left, resignal. if (count > 1) mNotEmpty.signal(); } finally { popLock.unlock(); } return o; } }