package com.zillabyte.motherbrain.flow.graph;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.javatuples.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.monitoring.runtime.instrumentation.common.com.google.common.collect.Maps;
import com.zillabyte.motherbrain.flow.Flow;
import com.zillabyte.motherbrain.flow.FlowCompilationException;
import com.zillabyte.motherbrain.flow.operations.Operation;
import com.zillabyte.motherbrain.flow.operations.Sink;
import com.zillabyte.motherbrain.flow.operations.Source;
@NonNullByDefault
@SuppressWarnings("unchecked")
public final class FlowGraph implements Serializable {
private static final long serialVersionUID = -1340686782939393035L;
private Collection<Connection> _arcs;
private Map<String, Operation> _idMap;
/****
*
* @param flow
*/
public FlowGraph() {
_arcs = new ArrayList<>();
_idMap = new HashMap<>();
}
/***
*
* @param source
*/
public boolean contains(String source, String dest) {
for(Connection c : _arcs) {
if (c.sourceId().equals(source) && c.destId().equals(dest)) {
return true;
}
}
return false;
}
public boolean contains(Operation source, Operation dest) {
return contains(source.namespaceName(), dest.namespaceName());
}
public boolean isLoopBack(String source, String dest) {
for(Connection c : _arcs) {
if (c.sourceId().equals(source) && c.destId().equals(dest) && c.loopBack()) {
return true;
}
}
return false;
}
public boolean isLoopBack(String stream) {
for(Connection c : _arcs) {
if(c.streamName().equals(stream) && c.loopBack()) return true;
}
return false;
}
/***
*
* @param source
* @param dest
* @return
*/
public Connection connect(Operation source, Operation dest, String name, Boolean loopBack, Integer maxIter) throws FlowCompilationException {
if (contains(source, dest)) throw (FlowCompilationException) new FlowCompilationException().setAllMessages("Connection already exists: " + source.operationId() + " -> " + dest.operationId());
if (source.namespaceName().equals(dest.namespaceName()) && !loopBack) throw (FlowCompilationException) new FlowCompilationException().setAllMessages("Cannot connect operation to itself unless it is a loopback: " + source + " -> " + dest + ".");
Connection c = new Connection(this, source, dest, name, loopBack, maxIter);
_arcs.add(c);
_idMap.put(source.namespaceName(), source);
_idMap.put(dest.namespaceName(), dest);
return c;
}
public Connection connect(Operation source, Operation dest, String name) throws FlowCompilationException {
return connect(source, dest, name, false, 0);
}
public Set<String> operationsBetween(final Operation source, final Operation dest) {
return operationsBetween(source.namespaceName(), dest.namespaceName());
}
public Set<String> operationsBetween(final String source, final String dest) {
// This queue will track the operation as well as all previous seen operations in the path in the Set
ConcurrentLinkedQueue<Pair<String, Set<String>>> queue = new ConcurrentLinkedQueue<Pair<String, Set<String>>>();
queue.add(new Pair<String, Set<String>>(source, new HashSet<String>()));
Set<String> opsBetween = Sets.newHashSet();
Map<String, LinkedList<Set<String>>> searchEndOperation = Maps.newHashMap();
while(!queue.isEmpty()) {
Pair<String, Set<String>> opPair = queue.remove();
String op = opPair.getValue0();
Set<String> opList = opPair.getValue1();
if(opList.contains(op)) {
// If the previously seen operations already contains this operation, then we're going to start going down the same path we
// previous went down.
if(source.equals(dest)) {
// In the case where source == dest, we need to add all of the seen operations to the final list before we move on (the
// source is put in the list first, so the next time we see it, it should be in the list already).
opsBetween.addAll(opList);
} else {
// If the source != dest, we need to account for the possibility that the end-operation is somehow connected to the dest
// somewhere further down. We add the current seen operations to a linked list and store the end operation-linked list
// pair in a hashmap (note that the linked list is just a list of the parents of the end-operation). Later we see if any
// of the end-operations in the hashmap are linked to the dest.
LinkedList<Set<String>> otherEndLists;
if(searchEndOperation.containsKey(op)) {
otherEndLists = searchEndOperation.get(op);
} else {
otherEndLists = Lists.newLinkedList();
}
otherEndLists.add(opList);
searchEndOperation.put(op, Lists.newLinkedList(otherEndLists));
}
continue;
} else if (op.equals(dest) && !source.equals(dest)) {
// If we have not already seen the operation and the source and dest are different, then we have also arrived at an end-point
// and should add all of the seen operations to the final list. Note that the condition source != dest is necessary because on
// the first iteration in the source == dest case, the first condition is satisfied, but we do not want to move to the next
// element in this case (we want to explore the graph!).
opsBetween.addAll(opList);
continue;
} else {
// Otherwise, we add the operation to the seen list and move on.
opList.add(op);
}
// Add next connections to the queue.
for(Connection c : connectionsFrom(op)) {
queue.add(new Pair<String, Set<String>>(c.dest().namespaceName(), Sets.newHashSet(opList)));
}
}
// Check to see if any of the end-operations are somehow linked to the dest. If they are, we need to also add all operations in
// the parents of the end-operation to opsBetween.
for(String op : searchEndOperation.keySet()) {
if(opsBetween.contains(op)) {
for(Set<String> so : searchEndOperation.get(op)) {
opsBetween.addAll(so);
}
}
}
// Don't include the destination itself.
if(source.equals(dest)) opsBetween.remove(source);
return opsBetween;
}
/***
*
* @param source
*/
public List<Connection> loopConnectionsFrom(final String source) {
List<Connection> list = Lists.newLinkedList();
for(Connection c : this._arcs) {
if (c.source().namespaceName().equals(source) && c.loopBack()) {
list.add(c);
}
}
return list;
}
/***
*
* @param source
*/
public List<Connection> nonLoopConnectionsFrom(final String source) {
List<Connection> list = Lists.newLinkedList();
for(Connection c : this._arcs) {
if (c.source().namespaceName().equals(source) && !c.loopBack()) {
list.add(c);
}
}
return list;
}
public List<Connection> nonLoopConnectionsFrom(final Operation source) {
return nonLoopConnectionsFrom(source.namespaceName());
}
/***
*
* @param dest
*/
public List<Connection> nonLoopConnectionsTo(final String dest) {
List<Connection> list = Lists.newLinkedList();
for(Connection c : this._arcs) {
if (c.dest().namespaceName().equals(dest) && !c.loopBack()) {
list.add(c);
}
}
return list;
}
public List<Connection> nonLoopConnectionsTo(final Operation dest) {
return nonLoopConnectionsTo(dest.namespaceName());
}
public List<Connection> connectionsTo(final Operation dest) {
return connectionsTo(dest.namespaceName());
}
public List<Connection> connectionsTo(final String dest) {
List<Connection> list = Lists.newLinkedList();
for(Connection c : this._arcs){
if(c.dest().namespaceName().equals(dest)) {
list.add(c);
}
}
return list;
}
public List<Connection> connectionsFrom(final Operation source) {
return connectionsFrom(source.namespaceName());
}
public List<Connection> connectionsFrom(final String source) {
List<Connection> list = Lists.newLinkedList();
for(Connection c : this._arcs){
if(c.source().namespaceName().equals(source)) {
list.add(c);
}
}
return list;
}
public Collection<Operation> allOperations() {
final Collection<Operation> operations = this._idMap.values();
return operations == null ? Collections.EMPTY_SET : operations;
}
public Operation getById(String id) {
return this._idMap.get(id);
}
public Operation getById(Operation o) {
return getById(o.namespaceName());
}
public <T extends Operation> Collection<T> getByTypeAndContainer(Class<T> klass, Flow container) {
Set<T> ret = new HashSet<>();
for(Operation o : this._idMap.values()) {
if (o.getContainerFlow() == container) {
if (klass.isInstance(o)) {
ret.add((T)o);
}
}
}
return ret;
}
public <T extends Operation> Collection<T> getByType(Class<T> klass) {
Set<T> ret = new HashSet<>();
for(Operation o : this._idMap.values()) {
if (klass.isInstance(o)) {
ret.add((T)o);
}
}
return ret;
}
public Set<Operation> operationsTo(Operation op) {
Set<Operation> set = new HashSet<>();
for(Connection p : this.connectionsTo(op)) {
set.add(this.getById(p.sourceId()));
}
return set;
}
public Set<Operation> operationsFrom(Operation op) {
Set<Operation> set = new HashSet<>();
for(Connection p : this.connectionsFrom(op)) {
set.add(this.getById(p.destId()));
}
return set;
}
public Set<Operation> nonLoopOperationsTo(Operation op) {
Set<Operation> set = new HashSet<>();
for(Connection p : this.nonLoopConnectionsTo(op)) {
set.add(this.getById(p.sourceId()));
}
return set;
}
public Set<Operation> nonLoopOperationsFrom(Operation op) {
Set<Operation> set = new HashSet<>();
for(Connection p : this.nonLoopConnectionsFrom(op)) {
set.add(this.getById(p.destId()));
}
return set;
}
public List<String> streamsFrom(String opId) {
List<String> list = Lists.newLinkedList();
for(Connection c : this._arcs) {
if (c.source().namespaceName().equals(opId)) {
list.add(c.streamName());
}
}
return list;
}
public List<String> streamsTo(String opId) {
List<String> list = Lists.newLinkedList();
for(Connection c : this._arcs) {
if (c.dest().namespaceName().equals(opId)) {
list.add(c.streamName());
}
}
return list;
}
public List<String> streamsFrom(Operation op) {
return streamsFrom(op.namespaceName());
}
public List<String> streamsTo(Operation op) {
return streamsTo(op.namespaceName());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for(Connection c : _arcs) {
sb.append(c.sourceId() + " -> " + c.destId() + " (" + c.streamName() + ")\n");
}
final String string = sb.toString();
assert (string != null);
return string;
}
public void debug() {
System.err.println(this.toString());
}
public void inject(String prefix, FlowGraph graph) throws FlowCompilationException {
for(Operation o : graph._idMap.values()) {
o.addNamespacePrefix(prefix);
}
for(Connection c : graph._arcs) {
// Init
Operation s = c.source();
Operation d = c.dest();
String name = c.streamName();
Boolean lb = c.loopBack();
Integer mi = c.maxIterations();
name = prefix + "." + name;
// Connect
c = connect(s, d, name, lb, mi);
}
}
public Collection<Source> sources() {
List<Source> list = Lists.newLinkedList();
for(Operation o : this._idMap.values()) {
if (o instanceof Source) {
list.add((Source) o);
}
}
return list;
}
public Collection<Sink> sinks() {
List<Sink> list = Lists.newLinkedList();
for(Operation o : this._idMap.values()) {
if (o instanceof Sink) {
list.add((Sink) o);
}
}
return list;
}
/***
* Does the graph have any cycles/loopbacks?
*/
public boolean hasLoopbacks() {
for(Connection c : this._arcs) {
if (c.loopBack()) {
return true;
}
}
return false;
}
public boolean contains(Operation op) {
return this.allOperations().contains(op);
}
public boolean containsAny(Collection<Operation> allOperations) {
for(Operation o : allOperations) {
if (contains(o)) return true;
}
return false;
}
/***
* Removes an operation and connects the remaining arcs together...
*/
public void pluck(Operation o) {
// Init
List<Connection> originalIncoming = this.connectionsTo(o);
List<Connection> originalOutgoing = this.connectionsFrom(o);
// PLUCK
String id = getIdByOperation(o);
this._idMap.remove(id);
for(Connection c : Lists.newArrayList(this._arcs)) {
if (c.destId().equals(id) || c.sourceId().equals(id)) {
_arcs.remove(c);
}
}
// Cross connect
for(Connection s : originalIncoming) {
for(Connection d : originalOutgoing) {
Connection n = new Connection(this, s.source(), d.dest(), s.streamName(), d.loopBack(), d.maxIterations());
_arcs.add(n);
}
}
}
private String getIdByOperation(Operation o) {
for(Entry<String, Operation> e : this._idMap.entrySet()) {
if (e.getValue() == o) {
return e.getKey();
}
}
return null;
}
public List<Connection> getConnectionByStream(String streamName) {
List<Connection> connections = Lists.newArrayList();
for(Connection c : this._arcs) {
if (c.streamName().equals(streamName)) {
connections.add(c);
}
}
return connections;
}
}