org.daisy.common.priority.UpdatablePriorityBlockingQueue Maven / Gradle / Ivy
The newest version!
package org.daisy.common.priority;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ForwardingBlockingQueue;
import com.google.common.util.concurrent.Monitor;
/**
* This class adds a layer to a forwarded {@link java.util.concurrent.PriorityBlockingQueue} that allows to update
* the order based on a priority or via the provided methods.
*
* Updating the priorities
*
* - {@link #update(Function
function)}: Updates the priorities by applying the given function sequentially to the different priorities. The function has update the {@link PrioritizableRunnable} by reference. This function is threadsafe
* - {@link #swap(PrioritizableRunnable runnable)} allows to swap two elements in the queue
*
*
*
* The internal design might seem a bit over-engineered but there are some reasons for that. ThreadPoolExecutorSevice needs a queue which has to be
* of type Runnable. Our needs are a bit more complex than that, so we have to "hide" the actual internal type of our
* objects a give a Runnable view to the superclass. This forces some unchecked type conversions that are safe ONLY when interacting with PrioritizableRunnables.
*
* Also we have to cope with the possibility of swapping elements in the queue. This is done more or less elegantly by the tuple SwappingPriority and WrappingPriorityQueue, which allow to hide the element wrapping process and return priorities of other elements as if they were its own.
* This class is threadsafe
* @version 1.0
*/
public class UpdatablePriorityBlockingQueue extends ForwardingBlockingQueue {
/**
* The forwarded priority queue
*/
private WrappingPriorityQueue,SwappingPriority> delegate;
/**
* Monitor that controls {@link #take()} and other blocking aspects of this class
*/
private Monitor monitor = new Monitor();
/**
* Condition when take stops blocking. There is something in the queue
* and there is no updating taking place.
*/
private Monitor.Guard canTake = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
return delegate.size() > 0 && !updating.get();
}
};
/**
* Condition for adding elements to this queue. Just makes sure
* that no updating processing is being carried out.
*/
private Monitor.Guard canAdd = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
return !updating.get();
}
};
/**
* Unwraps the SwappingPriority by returning the delegate
*/
private Function, PrioritizableRunnable> unwrapFunction = new Function, PrioritizableRunnable>() {
@Override
public PrioritizableRunnable apply(SwappingPriority arg) {
return arg.getDelegate();
}
};
/**
* Wraps the PrioritizableRunnable into a SwappingPriority
*/
private Function,SwappingPriority > wrapFunction = new Function,SwappingPriority>() {
@Override
public SwappingPriority apply(PrioritizableRunnable arg) {
return new SwappingPriority(arg);
}
};
/**
* flag controlling the updating processes
*/
AtomicBoolean updating = new AtomicBoolean(false);
/**
* Creates a new UpdatablePriorityBlockingQueue, it has infiniteinfiniteinfiniteinfiniteinfinite
* capacity.
*/
public UpdatablePriorityBlockingQueue() {
this.delegate = this.buildQueue();
}
/**
* @return a brand new queue, it does not set it as delegate
*/
private WrappingPriorityQueue,SwappingPriority> buildQueue() {
PriorityBlockingQueue> queue= new PriorityBlockingQueue>(20,
new PrioritizableComparator());
return new WrappingPriorityQueue,SwappingPriority>(queue,wrapFunction,unwrapFunction);
}
/**
* Updates the concurrent mechanisms when
* a updating process is starting.
*/
private void enterUpdate() {
this.monitor.enter();
this.updating.set(true);
}
/**
* Updates the concurrent mechanisms when
* a updating process is finishing and re-inserts all
* the elements in the queue.
*/
private void leaveUpdate() {
this.doUpdate();
this.updating.set(false);
this.monitor.leave();
}
/**
* Copies the delegate, cleans it and reinserts all the elements.
* This has to be done in order to reorder the runnables according
* to their priority.
*/
private void doUpdate() {
//re-add the elements of the queue to re-sort them
Collection> aux = Lists.newLinkedList(this.delegate.wrapped());
this.delegate.clear();
this.delegate.addAllBypass(aux);
}
/**
* Tries to find the runnable among the elements in the queue.
*/
protected Optional> tryFind(final PrioritizableRunnable runnable){
return Iterables.tryFind(this.delegate.wrapped(), new Predicate>() {
@Override
public boolean apply(SwappingPriority e) {
return e.getDelegate().equals(runnable);
}});
}
/**
* Swap the priorities of both PrioritizableRunnable objects to change the
* order in the queue
*/
public synchronized void swap(PrioritizableRunnable runnable1,PrioritizableRunnable runnable2) {
this.enterUpdate();//in monitor
Optional> node1=this.tryFind(runnable1);
Optional> node2=this.tryFind(runnable2);
//one of them doesn't exist, get out
if (!node1.isPresent() || !node2.isPresent()){
this.leaveUpdate();//out monitor
return;
}
//swap the overriders
node1.get().swapWith(node2.get());
this.leaveUpdate();//out monitor
}
/**
* Applies the function to all the elements in the queue. The function
* must change the objects by reference. Once the function has been applied
* the queue is reordered.
* This function is threadsafe
* @param function
*/
public synchronized void update(Function, Void> function) {
this.enterUpdate();
for (PrioritizableRunnable runnable : this.delegate) {
function.apply(runnable);
}
this.leaveUpdate();
}
/**
* Returns the runnables as an immutable {@link java.util.Collection} maintaining the order given by the priority
*/
public Collection> asOrderedCollection() {
List> list= Lists.newLinkedList(this.delegate.wrapped());
Collections.sort(list, new PrioritizableComparator());
return ImmutableList.copyOf(Collections2.transform(list,this.unwrapFunction));
}
/**
* Returns the runnables as an immutable {@link java.util.Collection} without maintaining the order given by the priority
*/
public Collection> asCollection() {
return ImmutableList.copyOf(this.delegate.unwrap());
}
/**
* Returns this forwarding class delegate
* @return
*/
@Override
@SuppressWarnings({ "unchecked" }) //controlled environment not (terribly) dangerous
protected BlockingQueue delegate() {
return (BlockingQueue)
(BlockingQueue extends Runnable>)
this.delegate;
}
/**
* See {@link java.util.concurrent.BlockingQueue#offer()}, This method may block if the queue
* is being updated.
*/
@SuppressWarnings("unchecked")
@Override
public synchronized boolean offer(Runnable o) {
boolean res;
try {
monitor.enterWhen(canAdd);
} catch (InterruptedException e) {
monitor.leave();
throw new RuntimeException(e);
}
//System.out.println("offer: entered monitor");
res = this.delegate.offer((PrioritizableRunnable) o);
//System.out.println("offer: left monitor");
monitor.leave();
return res;
}
/**
* See {@link java.util.concurrent.BlockingQueue#add()}, This method may block if the queue
* is being updated.
*/
@SuppressWarnings("unchecked")
@Override
public synchronized boolean add(Runnable element) {
boolean res;
try {
monitor.enterWhen(canAdd);
} catch (InterruptedException e) {
monitor.leave();
throw new RuntimeException(e);
}
res=this.delegate.add((PrioritizableRunnable)element);
monitor.leave();
return res;
}
/**
* See {@link java.util.concurrent.BlockingQueue#take}, it also waits for any updating
* operations to finish.
*/
@Override
public Runnable take() throws InterruptedException {
//int num=this.takes.incrementAndGet();
//System.out.println("Take("+num+"): before mon");
monitor.enterWhen(canTake);
//System.out.println("Take("+num+"): entered monitor");
Runnable res=this.delegate.poll();
monitor.leave();
//System.out.println("Take("+num+"): left monitor");
return res;
}
}
/**
* This class allows to return the priority of another PrioritizableRunnable while forwarding the rest
* of the calls to another object.
*/
class SwappingPriority extends
ForwardingPrioritableRunnable {
PrioritizableRunnable overrider;
public SwappingPriority(PrioritizableRunnable delegate,PrioritizableRunnable overrider) {
super(delegate);
this.overrider=overrider;
}
public SwappingPriority(PrioritizableRunnable delegate) {
super(delegate);
this.overrider=delegate;
}
@Override
public double getPriority() {
return this.overrider.getPriority();
}
/**
* @return the overrider
*/
public PrioritizableRunnable getOverrider() {
return overrider;
}
/**
* @param overrider the overrider to set
*/
public void setOverrider(PrioritizableRunnable overrider) {
this.overrider = overrider;
}
/**
* Swap priorities
*/
public void swapWith(SwappingPriority other){
PrioritizableRunnable aux=this.getOverrider();
this.setOverrider(other.getOverrider());
other.setOverrider(aux);
}
}