/**
* Copyright (C) 2008 Hal Hildebrand. All rights reserved.
*
* This file is part of the Prime Mover Event Driven Simulation Framework.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.hellblazer.primeMover.runtime;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.hellblazer.primeMover.Blocking;
import com.hellblazer.primeMover.Entity;
import com.hellblazer.primeMover.SynchronousQueue;
/**
* The implementation of the CSP channel.
*
* @author <a href="mailto:hal.hildebrand@gmail.com">Hal Hildebrand</a>
*
* @param <E>
* - the type of the contents
*/
@Entity
public class SynchronousQueueImpl<E> implements SynchronousQueue<E> {
static class EmptyIterator<E> implements Iterator<E> {
@Override
public boolean hasNext() {
return false;
}
@Override
public E next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new IllegalStateException();
}
}
/**
* The simulated entity.
*
* @author <a href="mailto:hal.hildebrand@gmail.com">Hal Hildebrand</a>
*
*/
static class entity<E> extends SynchronousQueueImpl<E> implements
EntityReference {
private final static int OFFER_TIMEOUT = 0;
private final static int POLL_TIMEOUT = 1;
private final static int PUT = 2;
private final static int TAKE = 3;
entity(Devi controller) {
this.controller = controller;
}
@Override
public void __bindTo(Devi controller) {
this.controller = controller;
}
@SuppressWarnings("unchecked")
@Override
public Object __invoke(int event, Object[] arguments) throws Throwable {
switch (event) {
case OFFER_TIMEOUT: {
return super.offer((E) arguments[0], (Integer) arguments[1]);
}
case POLL_TIMEOUT: {
return super.poll((Integer) arguments[1]);
}
case PUT: {
super.put((E) arguments[0]);
return null;
}
case TAKE: {
return super.take();
}
default:
throw new IllegalArgumentException(
"Unknown event ordinal: "
+ event);
}
}
@Override
public String __signatureFor(int event) {
switch (event) {
case OFFER_TIMEOUT:
return "<com.hellblazer.primeMover.runtime.SynchronousQueueImpl: boolean offer(java.lang.Object, int, java.util.concurrent.TimeUnit";
case POLL_TIMEOUT:
return "<com.hellblazer.primeMover.runtime.SynchronousQueueImpl: java.lang.Object poll(int, java.util.concurrent.TimeUnit)";
case PUT:
return "<com.hellblazer.primeMover.runtime.SynchronousQueueImpl: void put(java.lang.Object)>";
case TAKE:
return "<com.hellblazer.primeMover.runtime.SynchronousQueueImpl: java.lang.Object take()>";
default:
throw new IllegalArgumentException(
"Unknown event ordinal: "
+ event);
}
}
@Override
public boolean offer(E e, long timeout) {
try {
return super.offer(e, timeout);
} catch (Throwable ex) {
throw new IllegalStateException("Exception invoking event", ex);
}
}
@SuppressWarnings("unchecked")
@Override
public E poll(long timeout) {
try {
return (E) controller.postContinuingEvent(this, POLL_TIMEOUT,
timeout);
} catch (Throwable e) {
throw new IllegalStateException("Exception invoking event", e);
}
}
@Override
public void put(Object data) {
try {
controller.postContinuingEvent(this, PUT, data);
} catch (Throwable e) {
throw new IllegalStateException("Exception invoking event", e);
}
}
@SuppressWarnings("unchecked")
@Override
public E take() {
try {
return (E) controller.postContinuingEvent(this, TAKE);
} catch (Throwable e) {
throw new IllegalStateException("Exception invoking event", e);
}
}
}
private class Node {
EventImpl consumer;
EventImpl producer;
E data;
boolean hasData() {
return data != null;
}
}
protected Devi controller;
private final Deque<Node> waitList = new ArrayDeque<SynchronousQueueImpl<E>.Node>();
/**
* Inserts the specified element into this queue if it is possible to do so
* immediately without violating capacity restrictions, returning
* <tt>true</tt> upon success and throwing an <tt>IllegalStateException</tt>
* if no space is currently available.
*
* <p>
* This implementation returns <tt>true</tt> if <tt>offer</tt> succeeds,
* else throws an <tt>IllegalStateException</tt>.
*
* @param e
* the element to add
* @return <tt>true</tt> (as specified by {@link Collection#add})
* @throws IllegalStateException
* if the element cannot be added at this time due to capacity
* restrictions
* @throws ClassCastException
* if the class of the specified element prevents it from being
* added to this queue
* @throws NullPointerException
* if the specified element is null and this queue does not
* permit null elements
* @throws IllegalArgumentException
* if some property of this element prevents it from being added
* to this queue
*/
@Override
public boolean add(E e) {
if (offer(e)) {
return true;
} else {
throw new IllegalStateException("Queue full");
}
}
/**
* Adds all of the elements in the specified collection to this queue.
* Attempts to addAll of a queue to itself result in
* <tt>IllegalArgumentException</tt>. Further, the behavior of this
* operation is undefined if the specified collection is modified while the
* operation is in progress.
*
* <p>
* This implementation iterates over the specified collection, and adds each
* element returned by the iterator to this queue, in turn. A runtime
* exception encountered while trying to add an element (including, in
* particular, a <tt>null</tt> element) may result in only some of the
* elements having been successfully added when the associated exception is
* thrown.
*
* @param c
* collection containing elements to be added to this queue
* @return <tt>true</tt> if this queue changed as a result of the call
* @throws ClassCastException
* if the class of an element of the specified collection
* prevents it from being added to this queue
* @throws NullPointerException
* if the specified collection contains a null element and this
* queue does not permit null elements, or if the specified
* collection is null
* @throws IllegalArgumentException
* if some property of an element of the specified collection
* prevents it from being added to this queue, or if the
* specified collection is this queue
* @throws IllegalStateException
* if not all the elements can be added at this time due to
* insertion restrictions
* @see #add(Object)
*/
@Override
public boolean addAll(Collection<? extends E> c) {
if (c == null) {
throw new NullPointerException();
}
if (c == this) {
throw new IllegalArgumentException();
}
boolean modified = false;
Iterator<? extends E> e = c.iterator();
while (e.hasNext()) {
if (add(e.next())) {
modified = true;
}
}
return modified;
}
private void addConsumer() {
Node node;
node = new Node();
node.consumer = controller.swapCaller(null);
waitList.add(node);
}
private void addProducer(E data) {
Node node;
node = new Node();
node.data = data;
node.producer = controller.swapCaller(null);
waitList.add(node);
}
/**
* Does nothing. A <tt>SynchronousQueue</tt> has no internal capacity.
*/
@Override
public void clear() {
}
/**
* Always returns <tt>false</tt>. A <tt>SynchronousQueue</tt> has no
* internal capacity.
*
* @param o
* the element
* @return <tt>false</tt>
*/
@Override
public boolean contains(Object o) {
return false;
}
/**
* Returns <tt>false</tt> unless the given collection is empty. A
* <tt>SynchronousQueue</tt> has no internal capacity.
*
* @param c
* the collection
* @return <tt>false</tt> unless given collection is empty
*/
@Override
public boolean containsAll(Collection<?> c) {
return c.isEmpty();
}
/**
* @throws UnsupportedOperationException
* {@inheritDoc}
* @throws ClassCastException
* {@inheritDoc}
* @throws NullPointerException
* {@inheritDoc}
* @throws IllegalArgumentException
* {@inheritDoc}
*/
@Override
@Blocking
public int drainTo(Collection<? super E> c) {
if (c == null) {
throw new NullPointerException();
}
if (c == this) {
throw new IllegalArgumentException();
}
int n = 0;
E e;
while ((e = poll()) != null) {
c.add(e);
++n;
}
return n;
}
/**
* @throws UnsupportedOperationException
* {@inheritDoc}
* @throws ClassCastException
* {@inheritDoc}
* @throws NullPointerException
* {@inheritDoc}
* @throws IllegalArgumentException
* {@inheritDoc}
*/
@Override
@Blocking
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null) {
throw new NullPointerException();
}
if (c == this) {
throw new IllegalArgumentException();
}
int n = 0;
E e;
while (n < maxElements && (e = poll()) != null) {
c.add(e);
++n;
}
return n;
}
/**
* Always returns <tt>null</tt>. A <tt>SynchronousQueue</tt> does not return
* elements unless actively waited on.
*
* @return <tt>null</tt>
*/
@Override
public E element() {
return null;
}
private void enqueue(E data) {
Node node;
if (waitList.isEmpty()) {
addProducer(data);
return;
} else {
node = waitList.getFirst();
if (node.hasData()) {
// iterate and find a slot for this producer
for (Node waiter : waitList) {
if (!node.hasData()) {
waiter.data = data;
waiter.producer = controller.swapCaller(null);
}
return;
}
Node node1;
node1 = new Node();
node1.data = data;
node1.producer = controller.swapCaller(null);
waitList.add(node1);
return;
}
}
node.data = data;
node.producer = controller.swapCaller(null);
if (node.consumer != null) {
// schedule receive callback with result
node.consumer.setTime(controller.getCurrentTime());
node.consumer.getContinuation().setReturnValue(node.data);
controller.post(node.consumer);
// return to sender
controller.swapCaller(node.producer);
waitList.removeFirst();
}
}
/**
* Always returns <tt>true</tt>. A <tt>SynchronousQueue</tt> has no internal
* capacity.
*
* @return <tt>true</tt>
*/
@Override
public boolean isEmpty() {
return true;
}
/**
* Returns an empty iterator in which <tt>hasNext</tt> always returns
* <tt>false</tt>.
*
* @return an empty iterator
*/
@Override
public Iterator<E> iterator() {
return new EmptyIterator<E>();
}
/**
* Inserts the specified element into this queue, if another thread is
* waiting to receive it.
*
* @param e
* the element to add
* @return <tt>true</tt> if the element was added to this queue, else
* <tt>false</tt>
* @throws NullPointerException
* if the specified element is null
*/
@Override
public boolean offer(E e) {
if (e == null) {
throw new NullPointerException();
}
if (waitList.isEmpty()) {
return false;
}
Node node = waitList.getFirst();
if (node.hasData()) {
return false;
}
if (node.consumer == null) {
return false; // is this even possible if data is null?
}
// schedule receive callback with result
node.consumer.setTime(controller.getCurrentTime());
node.consumer.getContinuation().setReturnValue(node.data);
controller.post(node.consumer);
waitList.removeFirst();
return true;
}
@Override
@Blocking
public boolean offer(E e, long timeout) {
throw new UnsupportedOperationException("Currently unsupported");
}
/**
* Always returns <tt>null</tt>. A <tt>SynchronousQueue</tt> does not return
* elements unless actively waited on.
*
* @return <tt>null</tt>
*/
@Override
public E peek() {
return null;
}
/**
* Retrieves and removes the head of this queue, if another thread is
* currently making an element available.
*
* @return the head of this queue, or <tt>null</tt> if no element is
* available.
*/
@Override
public E poll() {
if (waitList.isEmpty()) {
return null;
}
Node node = waitList.getFirst();
if (node.consumer == null && node.hasData()) {
// schedule send callback
if (node.producer != null) {
node.producer.setTime(controller.getCurrentTime());
controller.post(node.producer);
}
waitList.removeFirst();
return node.data;
}
return null;
}
@Override
@Blocking
public E poll(long timeout) {
throw new UnsupportedOperationException("Currently unsupported");
}
/**
* Adds the specified element to this queue, waiting if necessary for
* another thread to receive it.
*
* @throws InterruptedException
* {@inheritDoc}
* @throws NullPointerException
* {@inheritDoc}
*/
@Override
@Blocking
public void put(E data) {
enqueue(data);
}
/**
* Always returns zero. A <tt>SynchronousQueue</tt> has no internal
* capacity.
*
* @return zero.
*/
@Override
public int remainingCapacity() {
return 0;
}
/**
* Retrieves and removes the head of this queue. This method differs from
* {@link #poll poll} only in that it throws an exception if this queue is
* empty.
*
* <p>
* This implementation returns the result of <tt>poll</tt> unless the queue
* is empty.
*
* @return the head of this queue
* @throws NoSuchElementException
* if this queue is empty
*/
@Override
@Blocking
public E remove() {
E x = poll();
if (x != null) {
return x;
} else {
throw new NoSuchElementException();
}
}
/**
* Always returns <tt>false</tt>. A <tt>SynchronousQueue</tt> has no
* internal capacity.
*
* @param o
* the element to remove
* @return <tt>false</tt>
*/
@Override
public boolean remove(Object o) {
return false;
}
/**
* Always returns <tt>false</tt>. A <tt>SynchronousQueue</tt> has no
* internal capacity.
*
* @param c
* the collection
* @return <tt>false</tt>
*/
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
/**
* Always returns <tt>false</tt>. A <tt>SynchronousQueue</tt> has no
* internal capacity.
*
* @param c
* the collection
* @return <tt>false</tt>
*/
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
/**
* Always returns zero. A <tt>SynchronousQueue</tt> has no internal
* capacity.
*
* @return zero.
*/
@Override
public int size() {
return 0;
}
/**
* Retrieves and removes the head of this queue, waiting if necessary for
* another thread to insert it.
*
* @return the head of this queue
*/
@Override
@Blocking
public E take() {
Node node;
if (waitList.isEmpty()) {
addConsumer();
return null;
} else {
node = waitList.getFirst();
if (node.consumer != null) {
// Iterate and find a slot for this consumer
for (Node waiter : waitList) {
if (waiter.consumer == null) {
waiter.consumer = controller.swapCaller(null);
return null;
}
}
addConsumer();
return null;
}
}
node.consumer = controller.swapCaller(null);
if (node.hasData()) {
// schedule send callback
node.producer.setTime(controller.getCurrentTime());
controller.post(node.producer);
// return to receiver
controller.swapCaller(node.consumer);
waitList.removeFirst();
return node.data;
}
return null; // won't return anywhere
}
/**
* Returns a zero-length array.
*
* @return a zero-length array
*/
@Override
public Object[] toArray() {
return new Object[0];
}
/**
* Sets the zeroeth element of the specified array to <tt>null</tt> (if the
* array has non-zero length) and returns it.
*
* @param a
* the array
* @return the specified array
* @throws NullPointerException
* if the specified array is null
*/
@Override
public <T> T[] toArray(T[] a) {
if (a.length > 0) {
a[0] = null;
}
return a;
}
}