package org.scribble.model;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.scribble.main.RuntimeScribbleException;
import org.scribble.main.ScribbleException;
import org.scribble.sesstype.kind.ProtocolKind;
public abstract class MState<
L, // Node label type (cosmetic)
A extends MAction<K>, // Edge type
S extends MState<L, A, S, K>, // State type
K extends ProtocolKind // Global/Local
>
{
private static int count = 0; // FIXME: factor out with ModelAction
public final int id;
protected final Set<L> labs; // Was RecVar and SubprotocolSigs, now using inlined protocol for FSM building so just RecVar
// **: clients should use the pair of getAllAcceptable/getSuccessors for correctness -- getAcceptable/accept don't support non-det
//protected final LinkedHashMap<A, S> edges; // Want predictable ordering of entries for e.g. API generation (state enumeration)*/
protected final List<A> actions;
protected final List<S> succs;
public MState(Set<L> labs) // Immutable singleton node
{
this.id = MState.count++;
this.labs = new HashSet<>(labs);
this.actions = new LinkedList<>();
this.succs = new LinkedList<>();
}
protected final void addLabel(L lab)
{
this.labs.add(lab);
}
public final Set<L> getLabels()
{
return Collections.unmodifiableSet(this.labs);
}
// Mutable (can also overwrite edges)
//protected final void addEdge(A a, S s)
public final void addEdge(A a, S s) // FIXME: currently public for SGraph building -- make a global version of EGraphBuilderUtil
{
//this.edges.put(a, s);
Iterator<A> as = this.actions.iterator(); // Needed?..
Iterator<S> ss = this.succs.iterator();
while (as.hasNext()) // Duplicate edges preemptively pruned here, but could leave to later minimisation
{
A tmpa = as.next();
S tmps = ss.next();
if (tmpa.equals(a) && tmps.equals(s))
{
return;
}
} // ..needed?
this.actions.add(a);
this.succs.add(s);
}
protected final void removeEdge(A a, S s) throws ScribbleException
{
Iterator<A> ia = this.actions.iterator();
Iterator<S> is = this.succs.iterator();
while (ia.hasNext())
{
A tmpa = ia.next();
S tmps = is.next();
if (tmpa.equals(a) && tmps.equals(s))
{
ia.remove();
is.remove();
return;
}
}
//throw new RuntimeException("No such transition to remove: " + a + "->" + s);
throw new ScribbleException("No such transition to remove: " + a + "->" + s); // Hack? EFSM building on bad-reachability protocols now done before actual reachability check
}
// The "deterministic" variant, cf., getAllActions
public final List<A> getActions()
{
Set<A> as = new HashSet<>(this.actions);
if (as.size() != this.actions.size())
{
throw new RuntimeScribbleException("[TODO] Non-deterministic state: " + this.actions + " (Try -minlts if available)"); // This getter checks for determinism -- affects e.g. API generation
}
//return as;
return getAllActions();
}
public final List<A> getAllActions()
{
return Collections.unmodifiableList(this.actions);
}
public final boolean hasAction(A a)
{
return this.actions.contains(a);
}
public final S getSuccessor(A a)
{
Set<A> as = new HashSet<>(this.actions);
if (as.size() != this.actions.size())
{
throw new RuntimeException("FIXME: " + this.actions);
}
return getSuccessors(a).get(0);
}
public final List<S> getSuccessors()
{
Set<A> as = new HashSet<>(this.actions);
if (as.size() != this.actions.size())
{
throw new RuntimeScribbleException("[TODO] Non-deterministic state: " + this.actions + " (Try -minlts if available)"); // This getter checks for determinism -- affects e.g. API generation
}
return getAllSuccessors();
}
// For non-deterministic actions
public final List<S> getSuccessors(A a)
{
return IntStream.range(0, this.actions.size())
.filter((i) -> this.actions.get(i).equals(a))
.mapToObj((i) -> this.succs.get(i))
.collect(Collectors.toList());
}
public final List<S> getAllSuccessors()
{
return Collections.unmodifiableList(this.succs);
}
public final boolean isTerminal()
{
return this.actions.isEmpty();
}
public static <L, A extends MAction<K>, S extends MState<L, A, S, K>, K extends ProtocolKind>
S getTerminal(S start)
{
if (start.isTerminal())
{
return start;
}
Set<S> terms = MState.getReachableStates(start).stream().filter((s) -> s.isTerminal()).collect(Collectors.toSet());
if (terms.size() > 1)
{
throw new RuntimeException("Shouldn't get in here: " + terms);
}
return (terms.isEmpty()) ? null : terms.iterator().next(); // FIXME: return empty Set instead of null?
}
public boolean canReach(MState<L, A, S, K> s)
{
return MState.getReachableStates(this).contains(s);
}
// Note: doesn't implicitly include start (only if start is explicitly reachable from start, of course)
/*public static <A extends ModelAction<K>, S extends ModelState<A, S, K>, K extends ProtocolKind>
Set<S> getAllReachable(S start)*/
@SuppressWarnings("unchecked")
public static <L, A extends MAction<K>, S extends MState<L, A, S, K>, K extends ProtocolKind>
Set<S> getReachableStates(MState<L, A, S, K> start)
{
Map<Integer, S> all = new HashMap<>();
Map<Integer, S> todo = new LinkedHashMap<>();
todo.put(start.id, (S) start); // Suppressed: assumes ModelState subclass correctly instantiates S parameter
while (!todo.isEmpty())
{
Iterator<S> i = todo.values().iterator();
S next = i.next();
todo.remove(next.id);
/*if (all.containsKey(next.id))
{
continue;
}
all.put(next.id, next);*/
for (S s : next.getAllSuccessors())
{
/*if (!all.containsKey(s.id) && !todo.containsKey(s.id))
{
todo.put(s.id, s);
}*/
if (!all.containsKey(s.id))
{
all.put(s.id, s);
//if (!todo.containsKey(s.id)) // Redundant
{
todo.put(s.id, s);
}
}
}
}
return new HashSet<>(all.values());
}
@SuppressWarnings("unchecked")
public static <L, A extends MAction<K>, S extends MState<L, A, S, K>, K extends ProtocolKind>
//Set<A> getAllReachableActions(S start)
Set<A> getReachableActions(MState<L, A, S, K> start)
{
Set<S> all = new HashSet<>();
all.add((S) start); // Suppressed: assumes ModelState subclass correctly instantiates S parameter
all.addAll(MState.getReachableStates(start));
Set<A> as = new HashSet<>();
for (S s : all)
{
as.addAll(s.getAllActions());
}
return as;
}
@Override
public int hashCode()
{
int hash = 73;
hash = 31 * hash + this.id; // N.B. using state ID only
return hash;
}
// N.B. Based only on state ID
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (!(o instanceof MState))
{
return false;
}
return ((MState<?, ?, ?, ?>) o).canEquals(this) && this.id == ((MState<?, ?, ?, ?>) o).id; // Good to use id, due to edge mutability
}
protected abstract boolean canEquals(MState<?, ?, ?, ?> s);
@Override
public String toString()
{
return Integer.toString(this.id); // FIXME -- ?
}
}