package com.revolsys.parallel.channel; import java.util.Iterator; import com.revolsys.parallel.ThreadInterruptedException; import com.revolsys.parallel.ThreadUtil; import com.revolsys.parallel.channel.store.ZeroBuffer; public class Channel<T> implements SelectableChannelInput<T>, ChannelOutput<T> { /** The Alternative class which will control the selection */ protected MultiInputSelector alt; /** Flag indicating if the channel has been closed. */ private boolean closed = false; /** The ChannelValueStore used to store the data for the Channel */ protected ChannelValueStore<T> data; /** The monitor reads must synchronize on */ protected 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; /** The monitor reads must synchronize on */ protected Object readMonitor = new Object(); /** Flag indicating if the channel is closed for writing. */ private boolean writeClosed; /** The monitor writes must synchronize on */ protected Object writeMonitor = new Object(); /** * Constructs a new Channel<T> with a ZeroBuffer ChannelValueStore. */ public Channel() { this(new ZeroBuffer<T>()); } /** * Constructs a new Channel<T> with the specified ChannelValueStore. * * @param data The ChannelValueStore used to store the data for the Channel */ public Channel(final ChannelValueStore<T> data) { this.data = data; } public Channel(final String name) { this(); this.name = name; } public Channel(final String name, final ChannelValueStore<T> data) { this.name = name; this.data = data; } public void close() { this.closed = true; } @Override public boolean disable() { this.alt = null; return this.data.getState() != ChannelValueStore.EMPTY; } @Override public boolean enable(final MultiInputSelector alt) { synchronized (this.monitor) { if (this.data.getState() == ChannelValueStore.EMPTY) { this.alt = alt; return false; } else { return true; } } } public String getName() { return this.name; } @Override public boolean isClosed() { if (!this.closed) { if (this.writeClosed) { if (this.data.getState() == ChannelValueStore.EMPTY) { close(); } } } return this.closed; } @Override public Iterator<T> iterator() { return new ChannelInputIterator<>(this); } /** * 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. */ @Override public T read() { return read(0); } /** * 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. */ @Override public T read(final long timeout) { synchronized (this.readMonitor) { synchronized (this.monitor) { if (isClosed()) { throw new ClosedException(); } if (this.data.getState() == ChannelValueStore.EMPTY) { try { ThreadUtil.pause(this.monitor, timeout); if (isClosed()) { throw new ClosedException(); } } catch (final ThreadInterruptedException e) { close(); this.monitor.notifyAll(); throw new ClosedException(); } } if (this.data.getState() == ChannelValueStore.EMPTY) { return null; } else { final T value = this.data.get(); this.monitor.notifyAll(); return value; } } } } @Override public void readConnect() { synchronized (this.monitor) { if (isClosed()) { throw new IllegalStateException("Cannot connect to a closed channel"); } else { this.numReaders++; } } } @Override public void readDisconnect() { synchronized (this.monitor) { if (!this.closed) { this.numReaders--; if (this.numReaders <= 0) { close(); this.monitor.notifyAll(); } } } } @Override public String toString() { if (this.name == null) { return this.data.toString(); } else { return this.name; } } /** * Writes an 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. * * @param value The object to write to the Channel. */ @Override public void write(final T value) { synchronized (this.writeMonitor) { synchronized (this.monitor) { if (this.closed) { throw new ClosedException(); } final MultiInputSelector tempAlt = this.alt; this.data.put(value); if (tempAlt != null) { tempAlt.schedule(); } else { this.monitor.notifyAll(); } if (this.data.getState() == ChannelValueStore.FULL) { try { ThreadUtil.pause(this.monitor); if (this.closed) { throw new ClosedException(); } } catch (final ThreadInterruptedException e) { close(); this.monitor.notifyAll(); throw new ClosedException(e); } } } } } @Override public void writeConnect() { synchronized (this.monitor) { if (this.writeClosed) { throw new IllegalStateException("Cannot connect to a closed channel"); } else { this.numWriters++; } } } @Override public void writeDisconnect() { synchronized (this.monitor) { if (!this.writeClosed) { this.numWriters--; if (this.numWriters <= 0) { this.writeClosed = true; final MultiInputSelector tempAlt = this.alt; if (tempAlt != null) { tempAlt.closeChannel(); } else { this.monitor.notifyAll(); } } } } } }