package org.scribble.model.endpoint;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.scribble.main.ScribbleException;
import org.scribble.model.GraphBuilderUtil;
import org.scribble.model.endpoint.actions.EAction;
import org.scribble.model.global.actions.SAction;
import org.scribble.sesstype.Payload;
import org.scribble.sesstype.kind.Local;
import org.scribble.sesstype.name.Op;
import org.scribble.sesstype.name.RecVar;
import org.scribble.sesstype.name.Role;
// Helper class for EGraphBuilder -- can access the protected setters of EState (via superclass helper methods)
// Tailored to support graph building from syntactic local protocol choice and recursion
public class EGraphBuilderUtil extends GraphBuilderUtil<RecVar, EAction, EState, Local>
{
private final Map<RecVar, Deque<EState>> recvars = new HashMap<>();
//private final Map<RecVar, Deque<Set<EAction>>> enacting = new HashMap<>(); // First action(s) inside a rec scope ("enacting" means how to enact an unguarded choice-continue)
private final Map<RecVar, Deque<List<EAction>>> enacting = new HashMap<>();
// CHECKME: Set sufficient? or List? (consider non-determinism -- does it matter?)
//private final Map<EState, Map<RecVar, Set<EAction>>> enactingMap = new HashMap<>(); // Record enacting per-recvar, since a state could have multi-recvars
private final Map<EState, Map<RecVar, List<EAction>>> enactingMap = new HashMap<>();
private final Deque<List<EState>> pred = new LinkedList<>();
private final Deque<List<EAction>> prev = new LinkedList<>();
public EGraphBuilderUtil()
{
clear();
}
protected void clear()
{
this.recvars.clear();
this.enacting.clear();
this.pred.clear();
this.prev.clear();
this.pred.push(new LinkedList<>());
this.prev.push(new LinkedList<>());
this.enactingMap.clear();
}
@Override
public void reset()
{
clear();
super.reset();
}
@Override
public EState newState(Set<RecVar> labs)
{
return new EState(labs);
}
// Records 's' as predecessor state, and 'a' as previous action and the "enacting action" for "fresh" recursion scopes
@Override
public void addEdge(EState s, EAction a, EState succ)
{
addEdgeAux(s, a, succ);
//if (!this.pred.isEmpty())
{
this.pred.pop();
this.prev.pop();
}
this.pred.push(new LinkedList<>(Arrays.asList(s)));
this.prev.push(new LinkedList<>(Arrays.asList(a)));
//for (Deque<Set<EAction>> ens : this.enacting.values())
for (Deque<List<EAction>> ens : this.enacting.values())
{
if (!ens.isEmpty()) // Unnecessary?
{
//Set<EAction> tmp = ens.peek();
List<EAction> tmp = ens.peek();
if (tmp.isEmpty())
{
tmp.add(a);
}
}
}
}
/*
* Dealing with Choice contexts
*/
public void enterChoice()
{
this.pred.push(new LinkedList<>());
this.prev.push(new LinkedList<>());
for (RecVar rv : this.enacting.keySet())
{
////Deque<Set<EAction>> tmp = this.enacting.get(rv);
Deque<List<EAction>> tmp = this.enacting.get(rv);
////tmp.push(new HashSet<>(tmp.peek()));
//tmp.push(new HashSet<>()); // Initially empty to record nested enablings in choice blocks
tmp.push(new LinkedList<>()); // Initially empty to record nested enablings in choice blocks
}
}
public void leaveChoice()
{
List<EState> pred = this.pred.pop();
List<EAction> prev = this.prev.pop();
if (!pred.isEmpty())
{
this.pred.pop();
this.prev.pop();
this.pred.push(pred);
this.prev.push(prev);
}
for (RecVar rv : this.enacting.keySet())
{
/*Set<EAction> pop = this.enacting.get(rv).pop();
Set<EAction> peek = this.enacting.get(rv).peek();*/
List<EAction> pop = this.enacting.get(rv).pop();
List<EAction> peek = this.enacting.get(rv).peek();
if (peek.isEmpty()) // Cf. addEdge
{
peek.addAll(pop);
}
}
}
public void pushChoiceBlock()
{
this.pred.push(null); // Signifies following statement is "unguarded" in this choice block
this.prev.push(null);
for (RecVar rv : this.enacting.keySet())
{
//Deque<Set<EAction>> tmp = this.enacting.get(rv);
Deque<List<EAction>> tmp = this.enacting.get(rv);
//tmp.push(new HashSet<>()); // Must be empty for addEdge to record (nested) enabling
tmp.push(new LinkedList<>());
}
}
public void popChoiceBlock()
{
List<EState> pred = this.pred.pop();
List<EAction> prev = this.prev.pop();
if (pred != null) // Unguarded choice-continue?
{
List<EState> peek1 = this.pred.peek();
if (peek1 == null)
{
this.pred.pop();
peek1 = new LinkedList<>();
this.pred.push(peek1);
}
peek1.addAll(pred);
}
if (prev != null)
{
List<EAction> peek2 = this.prev.peek();
if (peek2 == null)
{
this.prev.pop();
peek2 = new LinkedList<>();
this.prev.push(peek2);
}
peek2.addAll(prev);
}
for (RecVar rv : this.enacting.keySet())
{
/*Set<EAction> pop = this.enacting.get(rv).pop();
Set<EAction> peek = this.enacting.get(rv).peek();*/
List<EAction> pop = this.enacting.get(rv).pop();
List<EAction> peek = this.enacting.get(rv).peek();
//if (peek.isEmpty())
{
peek.addAll(pop);
}
}
}
/*
* Dealing with Recursion contexts
*/
public void pushRecursionEntry(RecVar recvar, EState entry)
{
/*if (!isUnguardedInChoice()) // Don't record rec entry if it is an unguarded choice-rec
{
this.entry.addLabel(recvar);
}*/
//this.recvars.put(recvar, this.entry);
Deque<EState> tmp = this.recvars.get(recvar);
if (tmp == null)
{
tmp = new LinkedList<>();
this.recvars.put(recvar, tmp);
}
/*if (isUnguardedInChoice())
{
tmp.push(tmp.peek()); // Works because unguarded recs unfolded (including nested recvar shadowing -- if unguarded choice-rec, it will be unfolded and rec entry recorded for guarded unfolding)
}
else
{
tmp.push(this.entry);
}*/
tmp.push(entry);
//Deque<Set<EAction>> tmp2 = this.enacting.get(recvar);
Deque<List<EAction>> tmp2 = this.enacting.get(recvar);
if (tmp2 == null)
{
tmp2 = new LinkedList<>(); // New Stack for this recvar
this.enacting.put(recvar, tmp2);
}
//tmp2.push(new HashSet<>()); // Push new Set element onto stack
tmp2.push(new LinkedList<>()); // Push new Set element onto stack
}
public void popRecursionEntry(RecVar recvar)
{
this.recvars.get(recvar).pop(); // Pop the entry of this rec
//Set<EAction> pop = this.enacting.get(recvar).pop();
List<EAction> pop = this.enacting.get(recvar).pop();
if (this.enacting.get(recvar).isEmpty()) // All Sets popped from the stack of this recvar
{
this.enacting.remove(recvar);
}
EState curr = getEntry();
//Map<RecVar, Set<EAction>> tmp = this.enactingMap.get(curr);
Map<RecVar, List<EAction>> tmp = this.enactingMap.get(curr);
if (tmp == null)
{
tmp = new HashMap<>();
this.enactingMap.put(curr, tmp);
}
tmp.put(recvar, pop);
}
/*
* Edge construction for Continues
*/
public boolean isUnguardedInChoice()
//public boolean isUnguardedInChoice(RecVar rv)
{
return
////!this.entry.equals(this.root) && // Hacky? for protocols that start with unguarded choice-rec, e.g. choice at A { rec X { ... at root
//!this.pred.isEmpty() && // This and above fixed by initialising non-null pred/prev?
this.pred.peek() == null;
}
// Choice-unguarded continues -- fixed in finalise pass
public void addContinueEdge(EState s, RecVar rv)
{
/*this.contStates.add(s);
this.contRecVars.add(rv);*/
EState entry = getRecursionEntry(rv);
addEdgeAux(s, new IntermediateContinueEdge(rv), entry);
//addEdge(s, new IntermediateContinueEdge(rv), entry); // **FIXME: broken on purpose for testing
}
// Doesn't set predecessor, cf. addEdge (and cf. addEdgeAux)
// Choice-guarded continues (can be done in one pass)
public void addRecursionEdge(EState s, EAction a, EState succ) // Cf. LGraphBuilder.addContinueEdge, for choice-unguarded cases -- addRecursionEdge, for guarded cases, should also be in LGraphBuilder, but here for convenience (private state)
{
addEdgeAux(s, a, succ);
// Still needed here?
//for (Deque<Set<EAction>> ens : this.enacting.values())
for (Deque<List<EAction>> ens : this.enacting.values())
{
if (!ens.isEmpty())
{
//Set<EAction> tmp = ens.peek();
List<EAction> tmp = ens.peek();
if (tmp.isEmpty())
{
tmp.add(a);
}
}
}
}
// succ assumed to be this.getEntry()
public void removeEdgeFromPredecessor(EState s, EAction a) throws ScribbleException // Removing prev edge, to be replaced by addRecursionEdge
{
//s.removeEdge(a, this.getEntry());
removeEdgeAux(s, a, this.getEntry());
//this.pred.peek().remove(s); // Need to update both preds and prevs accordingly (consider non-det)
Iterator<EState> preds = this.pred.peek().iterator();
Iterator<EAction> prevs = this.prev.peek().iterator();
while (preds.hasNext())
{
EState nexts = preds.next();
EAction nexta = prevs.next();
if (nexts.equals(s) && nexta.equals(a))
{
preds.remove();
prevs.remove();
return;
}
}
throw new RuntimeException("Shouldn't get in here: " + s + ", " + a);
}
public List<EState> getPredecessors()
{
//return this.pred.peek();
return new LinkedList<>(this.pred.peek()); // Cf. removeEdgeFromPredecessor
}
public List<EAction> getPreviousActions()
{
//return this.prev.peek();
return new LinkedList<>(this.prev.peek());
}
public EState getRecursionEntry(RecVar recvar)
{
return this.recvars.get(recvar).peek();
}
/*
* Finalise graph by treating IntermediateContinueEdges
*/
public EGraph finalise()
{
EState res = new EState(this.entry.getLabels());
EState resTerm = new EState(this.exit.getLabels());
Map<EState, EState> map = new HashMap<>();
map.put(this.entry, res);
map.put(this.exit, resTerm);
Set<EState> seen = new HashSet<>();
fixContinueEdges(seen, map, this.entry, res);
if (!seen.contains(this.exit))
{
resTerm = null;
}
/*Map<Integer, EndpointState> all = getAllStates(res);
EndpointState dfa = determinise(all, res, resTerm);
System.out.println("111: " + dfa.toDot());*/
return new EGraph(res, resTerm);
}
// FIXME: incomplete -- won't fully correctly handle situations involving, e.g., transitive continue-edge fixing?
private void fixContinueEdges(Set<EState> seen, Map<EState, EState> map, EState curr, EState res)
{
if (seen.contains(curr))
{
return;
}
seen.add(curr);
Iterator<EAction> as = curr.getAllActions().iterator();
Iterator<EState> ss = curr.getAllSuccessors().iterator();
while (as.hasNext())
{
EAction a = as.next();
EState succ = ss.next();
EState next;
next = getNext(map, succ);
if (!(a instanceof IntermediateContinueEdge))
{
addEdgeAux(res, a, next);
fixContinueEdges(seen, map, succ, next);
}
else
{
IntermediateContinueEdge ice = (IntermediateContinueEdge) a;
//for (IOAction e : this.enactingMap.get(succ))
RecVar rv = new RecVar(ice.mid.toString());
for (EAction e : this.enactingMap.get(succ).get(rv))
{
for (EState n : succ.getSuccessors(e))
{
next = getNext(map, n);
addEdgeAux(res, e, next);
fixContinueEdges(seen, map, succ, next);
}
}
}
}
}
private EState getNext(Map<EState, EState> map, EState succ)
{
EState next;
if (map.containsKey(succ))
{
next = map.get(succ);
}
else
{
next = new EState(succ.getLabels());
map.put(succ, next);
}
return next;
}
}
class IntermediateContinueEdge extends EAction
{
public IntermediateContinueEdge(RecVar rv)
{
super(Role.EMPTY_ROLE, new Op(rv.toString()), Payload.EMPTY_PAYLOAD); // HACK
}
@Override
public EAction toDual(Role self)
{
throw new RuntimeException("Shouldn't get in here: " + this);
}
@Override
public SAction toGlobal(Role self)
{
throw new RuntimeException("Shouldn't get in here: " + this);
}
@Override
protected String getCommSymbol()
{
return "#";
}
@Override
public int hashCode()
{
int hash = 1021;
hash = 31 * hash + super.hashCode();
return hash;
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (!(o instanceof IntermediateContinueEdge))
{
return false;
}
return ((IntermediateContinueEdge) o).canEqual(this) && super.equals(o);
}
@Override
public boolean canEqual(Object o)
{
return o instanceof IntermediateContinueEdge;
}
}