package javaflow.network.impl;
import javaflow.network.api.*;
import javaflow.network.utils.CombinedPacketListener;
import javaflow.network.utils.ComponentStateListener;
import javaflow.network.utils.PacketCountInComponent;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javaflow.components.api.Component;
import javaflow.components.api.InputPort;
import javaflow.components.api.InputPorts;
import javaflow.components.api.OutputPort;
import javaflow.components.api.OutputPorts;
final class NetworkImpl implements Network {
int nextComponentId = 0;
Map<String, InputPortImpl> inputPorts = new HashMap<>();
Map<String, OutputPortImpl> outputPorts = new HashMap<>();
Map<String, InputPortsImpl> inputArrayPorts = new HashMap<>();
Map<String, OutputPortsImpl> outputArrayPorts = new HashMap<>();
ArrayList<AbstractRunnableComponent> components = new ArrayList<>();
AtomicLong alivePacketsCount = new AtomicLong();
ExecutorService executor;
AtomicBoolean networkRunning = new AtomicBoolean(false);
ComponentStateListenerImpl componentStates = new ComponentStateListenerImpl();
private final CombinedPacketListener packetListeners = new CombinedPacketListener();
private final FailListener failListener;
public NetworkImpl() {
addPacketListener(new PacketListener() {
@Override
public void packetCreatedByComponent(long packetId, int componentId) {
alivePacketsCount.incrementAndGet();
}
@Override
public void packetArrivedAtComponent(long packetId, int componentId) {
}
@Override
public void packetDroppedByComponent(long packetId, int componentId) {
alivePacketsCount.decrementAndGet();
}
@Override
public void packetLeftComponent(long packetId, int fromComponentId, int towardsComponentId) {
if (fromComponentId<0){
alivePacketsCount.incrementAndGet();
}
if (towardsComponentId<0) {
alivePacketsCount.decrementAndGet();
} else {
ensureRuns(components.get(towardsComponentId));
}
}
});
failListener = new FailListener() {
@Override
public void componentRaisedError(AbstractRunnableComponent abstractRunnableComponent, Throwable t) {
NetworkImpl.this.componentRaisedError( abstractRunnableComponent, t);
}
};
}
private void componentRaisedError(AbstractRunnableComponent abstractRunnableComponent, Throwable t) {
System.err.println("Network encountered error from component "+abstractRunnableComponent.componentName());
t.printStackTrace(System.err);
shutdownComponents();
}
@Override
public void runInCurrentThread() {
if (networkRunning.compareAndSet(false, true)) {
try {
executor = Executors.newCachedThreadPool();
closeAllUnconnectedInputs();
final int componentsCount = components.size();
PacketCountInComponent packetCounter = new PacketCountInComponent(componentsCount);
addPacketListener(packetCounter);
for (AbstractRunnableComponent r : components) {
if (shouldRun(r)) {
startComponent(r);
}
}
componentStates.waitForComponentsStop();
if (alivePacketsCount.get() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("There are " + alivePacketsCount.get() + " packets left and no components are running.\n");
builder.append("Components failed due error:\n");
for (int i = 0; i < componentsCount; i++) {
final AbstractRunnableComponent component = components.get(i);
if (component.getError() != null){
Throwable t = component.getError();
builder.append(" ");
builder.append(component.componentName() + ": ");
ByteArrayOutputStream bout = new ByteArrayOutputStream();
PrintStream print = new PrintStream(bout);
t.printStackTrace(print);
builder.append(new String(bout.toByteArray()));
builder.append("\n");
}
}
builder.append("Components suspended or messages inside:\n");
for (int i = 0; i < componentsCount; i++) {
final AbstractRunnableComponent component = components.get(i);
if (component.state() == 0 && packetCounter.getCount(i) == 0) {
continue;
}
String state = component.getStateString();
final int count = packetCounter.getCount(i);
builder.append(" ");
builder.append(component.componentName() + " (" + state + ")" + ": " + count + " in component\n");
}
builder.append("\nPorts with messages:\n");
for (int i = 0; i < componentsCount; i++) {
final AbstractRunnableComponent component = components.get(i);
if (!component.hasInputPacketsWaiting()) {
continue;
}
for (Port p : component.inputs()) {
builder.append(" ");
builder.append(p.toString(4));
builder.append("\n");
}
}
throw new Error(builder.toString());
}
// } catch (InterruptedException ex) {
// throw new Error(ex);
} finally {
shutdownComponents();
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor = null;
networkRunning.set(false);
}
}
}
void reopenAllPorts() {
for(OutputPortsImpl ports : this.outputArrayPorts.values()){
ports.reopen();
};
for(OutputPortImpl port : this.outputPorts.values()){
port.reopen();
};
}
private void shutdownComponents() {
for (AbstractRunnableComponent component : components) {
component.onNetworkShutdown();
}
}
void addComponent(AbstractRunnableComponent component) {
component.addListener(getPacketListeners());
component.addListener(getComponentStateListener());
component.setComponentId(components.size());
component.setFailListener(failListener);
components.add(component);
}
void addComponent(String name, Component component) {
try {
List<Port> iPorts = new ArrayList<>();
List<Port> oPorts = new ArrayList<>();
Class componentClass = component.getClass();
while (componentClass != null) {
for (Field field : componentClass.getDeclaredFields()) {
if (field.getType().equals(InputPort.class)) {
PortReference portDef = createReference(name, field);
InputPortImpl in = new InputPortImpl(portDef);
addInputPort(portDef.toString(), in);
field.setAccessible(true);
field.set(component, in);
iPorts.add(in);
}
if (field.getType().equals(OutputPort.class)) {
PortReference portDef = createReference(name, field);
OutputPortImpl out = new OutputPortImpl(portDef);
addOutputPort(portDef.toString(), out);
field.setAccessible(true);
field.set(component, out);
oPorts.add(out);
}
if (field.getType().equals(InputPorts.class)) {
PortReference portDef = createReference(name, field);
InputPortsImpl ins = new InputPortsImpl(portDef);
inputArrayPorts.put(portDef.toString(), ins);
field.setAccessible(true);
field.set(component, ins);
iPorts.add(ins);
}
if (field.getType().equals(OutputPorts.class)) {
PortReference portDef = createReference(name, field);
OutputPortsImpl outs = new OutputPortsImpl(portDef);
outputArrayPorts.put(portDef.toString(), outs);
field.setAccessible(true);
field.set(component, outs);
oPorts.add(outs);
}
}
componentClass = componentClass.getSuperclass();
}
addComponent(new RunnableComponent(name, component, iPorts, oPorts));
} catch (Exception e) {
throw new Error(e);
}
}
private PortReference createReference(String name, Field field) {
return new PortReference(name, NetworkApiUtil.portNameForField(field));
}
void addOutputPort(String name, OutputPortImpl out) {
outputPorts.put(name, out);
}
void addInputPort(String name, InputPortImpl in) {
inputPorts.put(name, in);
}
PacketListener getPacketListeners(){
return packetListeners;
}
PacketImpl createPacket(final Object content, final OutputPortImpl creatorPort) {
try {
return new PacketImpl(content, creatorPort);
} finally {
alivePacketsCount.incrementAndGet();
}
}
InputPortImpl getInput(String portName) {
InputPortImpl port = inputPorts.get(portName);
if (port == null) {
PortReference ref = PortReference.parse(portName);
if (ref.isArrayPort()) {
port = createArrayInputPort(ref);
} else {
throw new Error("Could not find input port " + portName + ". These port are available: " + inputPorts
.keySet().toString());
}
}
return port;
}
OutputPortImpl getOutput(String portName) {
OutputPortImpl port = outputPorts.get(portName);
if (port == null) {
PortReference ref = PortReference.parse(portName);
if (ref.isArrayPort()) {
port = createArrayOutputPort(ref);
} else {
throw new Error("Could not find output port " + portName + ". These port are available: " + outputPorts
.keySet().toString());
}
}
return port;
}
void ensureRuns(AbstractRunnableComponent component) {
if (networkRunning.get()) {
startComponent(component);
}
}
private void startComponent(AbstractRunnableComponent r) {
if (r.signalStarting()) {
componentStates.componentStarting(r.componentId());
executor.execute(r);
}
}
ComponentStateListener getComponentStateListener(){
return componentStates;
}
private OutputPortImpl createArrayOutputPort(PortReference ref) {
OutputPortsImpl outputArray = outputArrayPorts.get(ref.componentAndPortName());
if (outputArray == null) {
throw new Error(
"Could not find array output port for " + ref + ". These are available " + outputArrayPorts
.keySet());
}
OutputPortImpl out = outputArray.get(ref.portIndex());
return out;
}
private InputPortImpl createArrayInputPort(PortReference ref) {
int i = ref.portIndex();
InputPortsImpl inputArray = inputArrayPorts.get(ref.componentAndPortName());
if (inputArray == null) {
throw new Error(
"Could not find array input port for " + ref + ". These are available " + inputArrayPorts
.keySet());
}
InputPortImpl in = inputArray.get(ref.portIndex());
return in;
}
private void closeAllUnconnectedInputs() {
for (InputPortImpl in : inputPorts.values()) {
if (!in.hasConnections()) {
in.close();
}
}
}
PacketImpl createPacket(Object content) {
return createPacket(content, null);
}
@Override
public final void addPacketListener(PacketListener listener) {
packetListeners.add(listener);
}
@Override
public Collection<NetworkComponent> components() {
return (Collection) Collections.unmodifiableCollection(components);
}
private boolean shouldRun(AbstractRunnableComponent r) {
return r.hasInputPacketsWaiting() || r.hasNoInputs() || r.mustRun();
}
@Override
public void close() {
for (OutputPort<?> port : outputPorts.values()) {
port.close();
}
for (InputPort<?> port : inputPorts.values()) {
port.close();
}
for (InputPorts<?> port : inputArrayPorts.values()) {
port.closeAll();
}
for (OutputPorts<?> port : outputArrayPorts.values()) {
port.closeAll();
}
}
}