package javaflow.network.impl;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javaflow.components.api.InputPort;
import javaflow.components.api.OutputPort;
import javaflow.components.api.Packet;
import javaflow.network.api.NetworkComponent;
import javaflow.network.api.PortReference;
class InputPortImpl implements InputPort, Port {
PacketImpl[] queue = new PacketImpl[10];
int currentRead = 0;
int currentWrite = 0;
int count = 0;
final CopyOnWriteArraySet<OutputPortImpl> connected = new CopyOnWriteArraySet<>();
private final PortReference portDef;
private AtomicBoolean closed = new AtomicBoolean(false);
AbstractRunnableComponent owner;
AtomicBoolean receiverWaiting = new AtomicBoolean(false);
Lock lock = new ReentrantLock(false);
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
InputPortImpl(PortReference portDef) {
this.portDef = portDef;
}
@Override
public Packet receive() {
PacketImpl packet = null;
lock.lock();
try {
waitWhilePortGetsClosedOrThereIsSomethingToBeRead();
if (count == 0) {
return null;
}
packet = popFromQueue();
} finally {
lock.unlock();
}
closeIfShouldClose();
packet.arrivedAtComponent(owner);
owner.signalPacketConsumed();
return packet;
}
void send(OutputPortImpl sender, PacketImpl p) {
lock.lock();
try {
owner.signalPacketQueued();
waitUntilThereIsRoomInQueue(sender);
pushToQueue(p);
} finally {
lock.unlock();
}
}
private int inc(int counter) {
counter++;
if (counter >= queue.length) {
counter = 0;
}
return counter;
}
void addConnection(OutputPortImpl out) {
connected.add(out);
signalOutputReconnect();
}
boolean hasConnections() {
for(OutputPort port: connected){
if (port.isOpen()){
return true;
}
}
return false;
}
@Override
public String toString() {
return portDef + ": " + count + " messages waiting, " + (hasBeenClosed() ? "port has been closed" : "port is open. Connected from "+ connected);
}
public PortReference port(){
return portDef;
}
@Override
public String toString(int indentation) {
return Utils.indent(indentation, toString());
}
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
lock.lock();
try {
if (owner == null) {
throw new Error(this + " does not have owner!");
}
owner.inputPortClosed();
receiverWaitEnded();
} finally {
lock.unlock();
}
}
}
@Override
public boolean isClosed() {
return closed.get() && !hasPacket();
}
protected void closeIfShouldClose() {
if (!hasConnections()) {
close();
}
}
@Override
public boolean hasPacket() {
lock.lock();
try {
return count > 0;
} finally {
lock.unlock();
}
}
@Override
public void setOwner(AbstractRunnableComponent component) {
this.owner = component;
}
@Override
public Object receiveContentAndDrop() {
Packet receive = receive();
if (receive == null) {
return null;
} else {
return receive.getContentAndDrop();
}
}
public boolean isOpen() {
return !isClosed();
}
@Override
public int getOpenPortCount() {
return isOpen() ? 1 : 0;
}
int ownerId() {
return owner.componentId();
}
private void receiverWaitEnded() {
if (receiverWaiting.compareAndSet(true, false)) {
owner.receiveReturned();
notEmpty.signal();
}
}
private void waitingForSomethingToReceive() {
if (receiverWaiting.compareAndSet(false, true)) {
owner.waitForReceive();
notEmpty.awaitUninterruptibly();
receiverWaitEnded();
}
}
private void markThatSendersAreNotWaiting() {
for (OutputPortImpl out : connected) {
out.sendWaitEnded();
}
}
private void markThatReceiverIsNotWaiting() {
receiverWaitEnded();
}
private void waitWhilePortGetsClosedOrThereIsSomethingToBeRead() {
while (queueSize() == 0 && !closed.get()) {
markThatSendersAreNotWaiting();
waitingForSomethingToReceive();
}
}
private PacketImpl popFromQueue() {
PacketImpl packet;
count--;
packet = queue[currentRead];
currentRead = inc(currentRead);
notFull.signalAll();
markThatSendersAreNotWaiting();
return packet;
}
private void pushToQueue(PacketImpl p) {
count++;
queue[currentWrite] = p;
currentWrite = inc(currentWrite);
notEmpty.signal();
receiverWaitEnded();
}
private void waitUntilThereIsRoomInQueue(OutputPortImpl sender) {
while (!(count < queue.length)) {
markThatReceiverIsNotWaiting();
if (sender != null) {
sender.waitForSend();
}
notFull.awaitUninterruptibly();
if (sender != null) {
sender.sendWaitEnded();
}
}
}
int queueSize() {
return count;
}
boolean hasBeenClosed() {
return closed.get();
}
@Override
public void reopen() {
for(OutputPortImpl out : connected){
out.reopen();
}
}
@Override
public NetworkComponent getOwner() {
return owner;
}
public AbstractRunnableComponent owner() {
return owner;
}
public void signalOutputClosed(OutputPortImpl outputPort) {
closeIfShouldClose();
}
public void signalOutputReconnect() {
if (closed.compareAndSet(true,false)){
owner.inputPortOpened();
}
}
}