/* $Id$ */
package ibis.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>Ticket</code> class provides a mechanism that enables a user to
* first obtain an identification number, give that identification number to
* someone else, and then wait until that someone connects an object to that
* identification number, and then collect that object. So, an "object" consumer
* first calls <code>get()</code>. This gives him an identification ("ticket").
* He gives this ticket to an "object" producer. This producer at some point
* produces an object and calls <code>put(ticket, object)</code>. Meanwhile,
* the consumer calls <code>collect(ticket)</code>, which will block until
* an object has been connected to the ticket, and then return that object.
*/
public class Ticket {
/**
* Initial number of tickets.
*/
private final static int INIT_SIZE = 16;
static Logger logger = LoggerFactory.getLogger(Ticket.class.getName());
/**
* Bucket associated with a ticket. It contains room for the object
* that is to be associated with the ticket, and some other administrative
* stuff. It also has methods to access those data.
*/
private static class Bucket {
/**
* Room for the object associated with a ticket.
*/
private Object data;
/**
* The number of threads waiting for something to happen on this bucket.
*/
private int waiters;
/**
* Flag indicating whether this bucket may currently be used (i.e., the
* corresponding ticket number has been given out).
*/
private boolean valid;
/**
* Flag indicating whether this bucket is currently initialized
* (i.e., a value has been put in it). A separate flag allows for
* null values as well.
*/
private boolean initialized;
/**
* Constructor.
*/
Bucket() {
data = null;
waiters = 0;
valid = false;
initialized = false;
}
/**
* Makes this bucket valid: its associated ticket number has been
* given out.
*/
synchronized void setValid() {
valid = true;
}
/**
* Gets and resets the object. It first blocks until a value is
* put(), and then grabs and destroys it.
* @return the object put into this bucket.
*/
synchronized Object get() {
Object result;
if (!valid) {
throw new RuntimeException("Invalid ticket");
}
while (!initialized) {
waiters++;
try {
wait();
} catch (InterruptedException e) {
// ignore
}
waiters--;
}
result = data;
initialized = false;
if (waiters != 0) {
notifyAll();
}
return result;
}
/**
* Gets and resets the object. It first blocks until a value is
* put(), and then grabs and destroys it. The difference with
* <code>get</code> is that <code>collect</code> also makes the
* bucket invalid, so that a new ticket is required.
* @return the object put into this bucket.
*/
synchronized Object collect() {
Object result;
if (!valid) {
throw new RuntimeException("Invalid ticket");
}
while (!initialized) {
waiters++;
try {
wait();
} catch (InterruptedException e) {
// ignore
}
waiters--;
}
result = data;
initialized = false;
valid = false;
return result;
}
/**
* Releases this bucket. After this call, a new ticket is required.
*/
synchronized void release() {
if (!valid) {
throw new RuntimeException("Invalid ticket");
}
initialized = false;
valid = false;
}
/**
* Gets the object. It first blocks until a value is put(),
* and then returns it.
* @return the object put into this bucket.
*/
synchronized Object peek() {
if (!valid) {
throw new RuntimeException("Invalid ticket");
}
while (!initialized) {
waiters++;
try {
wait();
} catch (InterruptedException e) {
// ignore
}
waiters--;
}
return data;
}
/**
* Puts an object in the bucket. It first waits until the
* bucket is free, then puts the data in it, and notifies
* waiters.
* @param o the object to be placed in the bucket.
*/
synchronized void put(Object o) {
if (!valid) {
throw new RuntimeException("Invalid ticket");
}
while (initialized) {
waiters++;
try {
wait();
} catch (InterruptedException e) {
// ignore
}
waiters--;
}
data = o;
initialized = true;
if (waiters != 0) {
notifyAll();
}
}
}
/**
* Number of tickets that we currently can handle.
*/
private int size;
/**
* A stack of free ticket numbers.
*/
private int[] tickets;
/**
* Top of the free ticket number stack. Ticket numbers are popped from
* this stack, and pushed again when they become available for reuse.
*/
private int top;
private Bucket[] buckets;
/**
* Creates the initial data structure for <code>INIT_SIZE</code> tickets.
*/
public Ticket() {
this(INIT_SIZE);
}
/**
* Creates the initial data structure for <code>initialSize</code> tickets.
*
* @param initialSize the initial number of tickets.
*/
public Ticket(int initialSize) {
buckets = new Bucket[initialSize];
tickets = new int[initialSize];
for (int i = 0; i < initialSize; i++) {
buckets[i] = new Bucket();
tickets[i] = i;
}
top = initialSize;
size = initialSize;
if (logger.isDebugEnabled()) {
logger.debug("Ticket(" + initialSize + ") done");
}
}
/**
* Returns a new ticket. If not available, the data structure is doubled
* in size.
* @return a new ticket number.
*/
public synchronized int get() {
if (logger.isDebugEnabled()) {
logger.debug("Ticket.get() starting");
}
if (top == 0) {
if (logger.isDebugEnabled()) {
logger.debug("Ticket.get() resizing from " + size + " to "
+ (size * 2));
}
// resize the lot.
int new_size = size * 2;
// "tickets" is empty, so we can realloc it directly.
tickets = new int[new_size];
// buckets contains data, so copy it.
Bucket[] new_buckets = new Bucket[new_size];
System.arraycopy(buckets, 0, new_buckets, 0, size);
for (int i = 0; i < size; i++) {
tickets[i] = size + i;
new_buckets[size + i] = new Bucket();
}
top = size;
size = new_size;
buckets = new_buckets;
}
top--;
int ticket = tickets[top];
buckets[ticket].setValid();
if (logger.isDebugEnabled()) {
logger.debug("Ticket.get() returning tickets[" + top + "] = " + ticket);
}
return ticket;
}
/**
* Associates <code>object</code> with <code>ticket</code> and notifies
* anyone waiting on the corresponding lock.
* If an object is already associated
* with this ticket, the method blocks until the ticket is made available
* (by means of a <code>get(ticket)</code> call.
*
* @param ticket the ticket number that gets an object associated with it
* @param object the object that gets associated
*/
public void put(int ticket, Object object) {
Bucket bucket;
if (logger.isDebugEnabled()) {
logger.debug("Ticket.put(" + ticket + ") starting");
}
synchronized (this) {
bucket = buckets[ticket];
}
if (logger.isDebugEnabled()) {
logger.debug("Ticket.put() got a bucket");
}
bucket.put(object);
if (logger.isDebugEnabled()) {
logger.debug("Ticket.put() done");
}
}
/**
* Returns the object that gets associated with <code>ticket</code>. The
* <code>ticket</code> is made available for reuse.
*
* @param ticket the ticket number for which an object is now requested.
* @return the object that got associated with <code>ticket</code>.
*/
public Object collect(int ticket) {
Bucket bucket;
Object result;
if (logger.isDebugEnabled()) {
logger.debug("Ticket.collect(" + ticket + ") starting");
}
synchronized (this) {
bucket = buckets[ticket];
}
if (logger.isDebugEnabled()) {
logger.debug("Ticket.collect() got a bucket");
}
result = bucket.collect();
if (logger.isDebugEnabled()) {
logger.debug("Ticket.collect() got a result");
}
synchronized (this) {
tickets[top++] = ticket;
}
if (logger.isDebugEnabled()) {
logger.debug("Ticket.collect() done");
}
return result;
}
/**
* Returns the object that gets associated with <code>ticket</code>.
* This version is non-destructive: it leaves the associated value intact.
*
* @param ticket the ticket number for which an object is now requested.
* @return the object that got associated with <code>ticket</code>.
*/
public Object peek(int ticket) {
Object result;
Bucket bucket;
if (logger.isDebugEnabled()) {
logger.debug("Ticket.peek(" + ticket + ") starting");
}
synchronized (this) {
bucket = buckets[ticket];
}
if (logger.isDebugEnabled()) {
logger.debug("Ticket.peek() got a bucket");
}
result = bucket.peek();
if (logger.isDebugEnabled()) {
logger.debug("Ticket.peek() done");
}
return result;
}
/**
* Returns the object that gets associated with <code>ticket</code>.
* This version is destructive (makes the ticket available for another
* <code>put</code>), but does not release the ticket.
* To release the ticket, <code>collect</code> must be used.
*
* @param ticket the ticket number for which an object is now requested.
* @return the object that got associated with <code>ticket</code>.
*/
public Object get(int ticket) {
Object result;
Bucket bucket;
if (logger.isDebugEnabled()) {
logger.debug("Ticket.get(" + ticket + ") starting");
}
synchronized (this) {
bucket = buckets[ticket];
}
if (logger.isDebugEnabled()) {
logger.debug("Ticket.get() got a bucket");
}
result = bucket.get();
if (logger.isDebugEnabled()) {
logger.debug("Ticket.get() done");
}
return result;
}
/**
* Releases <code>ticket</code>.
* This makes the ticket available for reuse.
* @param ticket the ticket number to be released.
*/
public void freeTicket(int ticket) {
Bucket bucket;
synchronized (this) {
bucket = buckets[ticket];
}
bucket.release();
synchronized (this) {
tickets[top++] = ticket;
}
}
}