package com.revolsys.parallel.channel;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import com.revolsys.parallel.ThreadInterruptedException;
import com.revolsys.parallel.ThreadUtil;
public class NamedChannelBundle<T> {
/** Flag indicating if the channel has been closed. */
private boolean closed = false;
/** The monitor reads must synchronize on */
private final Object monitor = new Object();
/** The name of the channel. */
private String name;
/** Number of readers connected to the channel. */
private int numReaders = 0;
/** Number of writers connected to the channel. */
private int numWriters = 0;
private int readerNotifyCount = 0;
/** The monitor reads must synchronize on */
private final Object readMonitor = new Object();
private AtomicLong sequence = new AtomicLong();
private Map<String, Queue<Long>> sequenceQueueByName = new HashMap<>();
/** The ChannelValueStore used to store the valueQueueByName for the Channel */
protected Map<String, Queue<T>> valueQueueByName = new HashMap<>();
/** Flag indicating if the channel is closed for writing. */
private boolean writeClosed;
/** The monitor writes must synchronize on */
private final Object writeMonitor = new Object();
public NamedChannelBundle() {
}
public NamedChannelBundle(final String name) {
this.name = name;
}
public void close() {
this.closed = true;
synchronized (this.monitor) {
this.valueQueueByName = null;
this.sequence = null;
this.sequenceQueueByName = null;
this.monitor.notifyAll();
}
}
public String getName() {
return this.name;
}
private Queue<T> getNextValueQueue(Collection<String> names) {
String selectedName = null;
long lowestSequence = Long.MAX_VALUE;
if (names == null) {
names = this.sequenceQueueByName.keySet();
}
for (final String name : names) {
final Queue<Long> sequenceQueue = this.sequenceQueueByName.get(name);
if (sequenceQueue != null && !sequenceQueue.isEmpty()) {
final long sequence = sequenceQueue.peek();
if (sequence < lowestSequence) {
lowestSequence = sequence;
selectedName = name;
}
}
}
if (selectedName == null) {
return null;
} else {
final Queue<Long> sequenceQueue = this.sequenceQueueByName.get(selectedName);
sequenceQueue.remove();
return getValueQueue(selectedName);
}
}
private Queue<Long> getSequenceQueue(final String name) {
Queue<Long> queue = this.sequenceQueueByName.get(name);
if (queue == null) {
queue = new LinkedList<>();
this.sequenceQueueByName.put(name, queue);
}
return queue;
}
private Queue<T> getValueQueue(final String name) {
Queue<T> queue = this.valueQueueByName.get(name);
if (queue == null) {
queue = new LinkedList<>();
this.valueQueueByName.put(name, queue);
}
return queue;
}
public boolean isClosed() {
if (!this.closed) {
if (this.writeClosed) {
boolean empty = true;
synchronized (this.monitor) {
for (final Queue<T> queue : this.valueQueueByName.values()) {
if (!queue.isEmpty()) {
empty = false;
}
}
if (empty) {
close();
}
}
}
}
return this.closed;
}
public void notifyReaders() {
synchronized (this.monitor) {
this.readerNotifyCount++;
this.monitor.notifyAll();
}
}
/**
* Reads an Object from the Channel. This method also ensures only one of the
* readers can actually be reading at any time. All other readers are blocked
* until it completes the read.
*
* @return The object returned from the Channel.
*/
public T read() {
return read(0, Collections.<String> emptyList());
}
public T read(final Collection<String> names) {
return read(0, names);
}
/**
* Reads an Object from the Channel. This method also ensures only one of the
* readers can actually be reading at any time. All other readers are blocked
* until it completes the read. If no data is available to be read after the
* timeout the method will return null.
*
* @param timeout The maximum time to wait in milliseconds.
* @return The object returned from the Channel.
*/
public T read(final long timeout) {
return read(timeout, Collections.<String> emptyList());
}
public T read(final long timeout, final Collection<String> names) {
synchronized (this.readMonitor) {
synchronized (this.monitor) {
final int readerNotifyCount = this.readerNotifyCount;
try {
long maxTime = 0;
if (timeout > 0) {
maxTime = System.currentTimeMillis() + timeout;
}
if (isClosed()) {
throw new ClosedException();
}
Queue<T> queue = getNextValueQueue(names);
if (timeout == 0) {
while (queue == null && readerNotifyCount == this.readerNotifyCount) {
ThreadUtil.pause(this.monitor);
if (isClosed()) {
throw new ClosedException();
}
queue = getNextValueQueue(names);
}
} else if (timeout > 0) {
long waitTime = maxTime - System.currentTimeMillis();
while (queue == null && waitTime > 0 && readerNotifyCount == this.readerNotifyCount) {
ThreadUtil.pause(this.monitor, waitTime);
if (isClosed()) {
throw new ClosedException();
}
queue = getNextValueQueue(names);
waitTime = maxTime - System.currentTimeMillis();
}
} else {
queue = getNextValueQueue(names);
}
if (queue == null) {
return null;
} else {
final T value = queue.remove();
this.monitor.notifyAll();
return value;
}
} catch (final ThreadInterruptedException e) {
close();
this.monitor.notifyAll();
throw new ClosedException();
}
}
}
}
public T read(final long timeout, final String... names) {
return read(timeout, Arrays.asList(names));
}
public T read(final String... names) {
return read(0, Arrays.asList(names));
}
public void readConnect() {
synchronized (this.monitor) {
if (isClosed()) {
throw new IllegalStateException("Cannot connect to a closed channel");
} else {
this.numReaders++;
}
}
}
public void readDisconnect() {
synchronized (this.monitor) {
if (!this.closed) {
this.numReaders--;
if (this.numReaders <= 0) {
close();
this.monitor.notifyAll();
}
}
}
}
public Collection<T> remove(final String name) {
synchronized (this.monitor) {
this.sequenceQueueByName.remove(name);
final Queue<T> values = this.valueQueueByName.remove(name);
this.monitor.notifyAll();
return values;
}
}
/**
* Writes a named Object to the Channel. This method also ensures only one of
* the writers can actually be writing at any time. All other writers are
* blocked until it completes the write. The channel can never be full so it
* does not block on write.
*
* @param value The object to write to the Channel.
*/
public void write(final String name, final T value) {
synchronized (this.writeMonitor) {
synchronized (this.monitor) {
if (this.closed) {
this.monitor.notifyAll();
throw new ClosedException();
} else {
final Long sequence = this.sequence.getAndIncrement();
final Queue<Long> sequenceQueue = getSequenceQueue(name);
sequenceQueue.add(sequence);
final Queue<T> queue = getValueQueue(name);
queue.add(value);
this.monitor.notifyAll();
}
}
}
}
public void writeConnect() {
synchronized (this.monitor) {
if (this.writeClosed) {
throw new IllegalStateException("Cannot connect to a closed channel");
} else {
this.numWriters++;
}
}
}
public void writeDisconnect() {
synchronized (this.monitor) {
if (!this.writeClosed) {
this.numWriters--;
if (this.numWriters <= 0) {
this.writeClosed = true;
this.monitor.notifyAll();
}
}
}
}
}