/*
* @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;
}
}