package org.scribble.model.global;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.scribble.main.Job;
import org.scribble.main.ScribbleException;
import org.scribble.model.endpoint.EFSM;
import org.scribble.model.endpoint.actions.ESend;
import org.scribble.model.global.actions.SAction;
import org.scribble.sesstype.name.Role;
public class SModel
{
public final SGraph graph;
protected SModel(SGraph graph)
{
this.graph = graph;
}
public void validate(Job job) throws ScribbleException
{
SState init = this.graph.init;
Map<Integer, SState> states = this.graph.states;
String errorMsg = "";
int count = 0;
for (SState s : states.values())
{
if (job.debug)
{
count++;
if (count % 50 == 0)
{
//job.debugPrintln("(" + this.graph.proto + ") Checking safety: " + count + " states");
job.debugPrintln("(" + this.graph.proto + ") Checking states: " + count);
}
}
SStateErrors errors = s.getErrors();
if (!errors.isEmpty())
{
// FIXME: getTrace can get stuck when local choice subjects are disabled
List<SAction> trace = this.graph.getTrace(init, s); // FIXME: getTrace broken on non-det self loops?
//errorMsg += "\nSafety violation(s) at " + s.toString() + ":\n Trace=" + trace;
errorMsg += "\nSafety violation(s) at session state " + s.id + ":\n Trace=" + trace;
}
if (!errors.stuck.isEmpty())
{
errorMsg += "\n Stuck messages: " + errors.stuck; // Deadlock from reception error
}
if (!errors.waitFor.isEmpty())
{
errorMsg += "\n Wait-for errors: " + errors.waitFor; // Deadlock from input-blocked cycles, terminated dependencies, etc
}
if (!errors.orphans.isEmpty())
{
errorMsg += "\n Orphan messages: " + errors.orphans; // FIXME: add sender of orphan to error message
}
if (!errors.unfinished.isEmpty())
{
errorMsg += "\n Unfinished roles: " + errors.unfinished;
}
}
job.debugPrintln("(" + this.graph.proto + ") Checked all states: " + count); // May include unsafe states
//*/
if (!job.noProgress)
{
//job.debugPrintln("(" + this.graph.proto + ") Checking progress: "); // Incompatible with current errorMsg approach*/
Set<Set<Integer>> termsets = this.graph.getTerminalSets();
for (Set<Integer> termset : termsets)
{
/*job.debugPrintln("(" + this.graph.proto + ") Checking terminal set: "
+ termset.stream().map((i) -> new Integer(all.get(i).id).toString()).collect(Collectors.joining(","))); // Incompatible with current errorMsg approach*/
Set<Role> starved = checkRoleProgress(states, init, termset);
if (!starved.isEmpty())
{
errorMsg += "\nRole progress violation for " + starved + " in session state terminal set:\n " + termSetToString(job, termset, states);
}
Map<Role, Set<ESend>> ignored = checkEventualReception(states, init, termset);
if (!ignored.isEmpty())
{
errorMsg += "\nEventual reception violation for " + ignored + " in session state terminal set:\n " + termSetToString(job, termset, states);
}
}
}
if (!errorMsg.equals(""))
{
//throw new ScribbleException("\n" + init.toDot() + errorMsg);
throw new ScribbleException(errorMsg);
}
//job.debugPrintln("(" + this.graph.proto + ") Progress satisfied."); // Also safety... current errorMsg approach
}
private String termSetToString(Job job, Set<Integer> termset, Map<Integer, SState> all)
{
return job.debug
? termset.stream().map((i) -> all.get(i).toString()).collect(Collectors.joining(","))
: termset.stream().map((i) -> new Integer(all.get(i).id).toString()).collect(Collectors.joining(","));
}
// ** Could subsume terminal state check, if terminal sets included size 1 with reflexive reachability (but not a good approach)
private static Set<Role> checkRoleProgress(Map<Integer, SState> states, SState init, Set<Integer> termset) throws ScribbleException
{
Set<Role> starved = new HashSet<>();
Iterator<Integer> i = termset.iterator();
SState s = states.get(i.next());
Map<Role, SState> ss = new HashMap<>();
s.config.efsms.keySet().forEach((r) -> ss.put(r, s));
while (i.hasNext())
{
SState next = states.get(i.next());
Map<Role, EFSM> tmp = next.config.efsms;
for (Role r : tmp.keySet())
{
if (ss.get(r) != null)
{
/*if (!ss.get(r).equals(tmp.get(r)))
{
ss.put(r, null);
}
else*/
{
for (SAction a : next.getAllActions())
{
if (a.containsRole(r))
{
ss.put(r, null);
break;
}
}
}
}
}
}
for (Role r : ss.keySet())
{
SState foo = ss.get(r);
if (foo != null)
{
EFSM tmp = foo.config.efsms.get(r);
if (tmp != null)
{
if (!foo.config.canSafelyTerminate(r))
{
if (s.config.buffs.get(r).values().stream().allMatch((v) -> v == null))
{
starved.add(r);
}
/*
// Should be redundant given explicit reception error etc checking
else
{
safety.add(r);
}*/
}
}
}
}
return starved;
}
// (eventual reception)
private static Map<Role, Set<ESend>> checkEventualReception(Map<Integer, SState> states, SState init, Set<Integer> termset) throws ScribbleException
{
Set<Role> roles = states.get(termset.iterator().next()).config.efsms.keySet();
Iterator<Integer> i = termset.iterator();
Map<Role, Map<Role, ESend>> b0 = states.get(i.next()).config.buffs.getBuffers();
while (i.hasNext())
{
SState s = states.get(i.next());
SBuffers b = s.config.buffs;
for (Role r1 : roles)
{
for (Role r2 : roles)
{
ESend s0 = b0.get(r1).get(r2);
if (s0 != null)
{
ESend tmp = b.get(r1).get(r2);
if (tmp == null)
{
b0.get(r1).put(r2, null);
}
}
}
}
}
Map<Role, Set<ESend>> ignored = new HashMap<>();
for (Role r1 : roles)
{
for (Role r2 : roles)
{
ESend m = b0.get(r1).get(r2);
if (m != null)
{
Set<ESend> tmp = ignored.get(r2);
if (tmp == null)
{
tmp = new HashSet<>();
ignored.put(r2, tmp);
}
tmp.add(m);
}
}
}
return ignored;
}
@Override
public String toString()
{
return this.graph.toString();
}
}