package org.scribble.codegen.java.endpointapi;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.scribble.ast.Module;
import org.scribble.codegen.java.util.ClassBuilder;
import org.scribble.codegen.java.util.TypeBuilder;
import org.scribble.main.Job;
import org.scribble.main.JobContext;
import org.scribble.main.ScribbleException;
import org.scribble.model.endpoint.EState;
import org.scribble.model.endpoint.actions.EAction;
import org.scribble.sesstype.name.GProtocolName;
import org.scribble.sesstype.name.LProtocolName;
import org.scribble.sesstype.name.Role;
import org.scribble.visit.context.Projector;
// TODO: "wildcard" unary async: op doesn't matter -- for branch-receive op "still needed" to cast to correct branch state
// TODO: "functional state interfaces", e.g. for smtp ehlo and quit actions
// FIXME: selector(?) hanging on runtimeexception (from message formatter)
// FIXME: consume futures before wrap/reconnect
public class StateChannelApiGenerator extends ApiGenerator
{
public static final String SCRIBMESSAGE_CLASS = "org.scribble.net.ScribMessage";
public static final String SCRIBBLERUNTIMEEXCEPTION_CLASS = "org.scribble.main.ScribbleRuntimeException";
public static final String RECEIVE_OP_PARAM = "op";
public static final String SCRIBMESSAGE_OP_FIELD = "op";
private final Role self;
private final LProtocolName lpn;
private final EState init;
//private final String root;
protected final boolean skipIOInterfacesGeneration;
private int counter = 1;
private Map<EState, String> classNames = new HashMap<>(); // Doesn't include terminal states
private Map<String, TypeBuilder> types = new HashMap<>(); // class/iface name key
public StateChannelApiGenerator(Job job, GProtocolName fullname, Role self) throws ScribbleException // FIXME: APIGenerationException?
{
super(job, fullname);
this.self = self;
this.lpn = Projector.projectFullProtocolName(fullname, self);
//this.init = job.getContext().getEndpointGraph(fullname, self).init;
JobContext jc = job.getContext();
this.init = job.minEfsm ? jc.getMinimisedEGraph(fullname, self).init : jc.getEGraph(fullname, self).init;
this.skipIOInterfacesGeneration = skipIOInterfacesGeneration(this.init);
generateClassNames(this.init);
//this.root = this.classNames.get(this.init);
constructClasses(this.init);
//EndpointState term = EndpointState.findTerminalState(new HashSet<>(), this.init);
EState term = EState.getTerminal(this.init);
if (term != null)
{
ClassBuilder cb = new EndSocketGenerator(this, term).generateType();
this.types.put(cb.getName(), cb);
}
}
// Cf. IOInterfacesGenerator constructor
private static boolean skipIOInterfacesGeneration(EState init)
{
Set<EAction> as = EState.getReachableActions(init);
if (as.stream().anyMatch((a) -> !a.isSend() && !a.isReceive())) // HACK FIXME (connect/disconnect)
{
return true;
}
return false;
}
// Return: key (package and Java class file path) -> val (Java class source)
@Override
public Map<String, String> generateApi()
{
Map<String, String> map = new HashMap<String, String>();
// FIXME: factor out with ScribSocketBuilder.getPackageName
String prefix = SessionApiGenerator.getEndpointApiRootPackageName(this.gpn).replace('.', '/') + "/channels/" + this.self + "/" ;
for (String s : this.types.keySet())
{
String path = prefix + s + ".java";
map.put(path, this.types.get(s).build());
}
return map;
}
private void generateClassNames(EState ps)
{
if (this.classNames.containsKey(ps))
{
return;
}
if (ps.isTerminal())
{
//this.classNames.put(ps, ENDSOCKET_CLASS); // FIXME: add generic parameters? or don't record?
return;
}
this.classNames.put(ps, newSocketClassName());
for (EState succ : ps.getAllSuccessors())
{
generateClassNames(succ);
}
}
private String newSocketClassName()
{
return this.lpn.getSimpleName().toString() + "_" + this.counter++;
}
private void constructClasses(EState curr) throws ScribbleException
{
if (curr.isTerminal())
{
return; // Generic EndSocket for terminal states
}
String className = this.classNames.get(curr);
if (this.types.containsKey(className))
{
return;
}
this.types.put(className, constructClass(curr));
for (EState succ : curr.getAllSuccessors())
{
constructClasses(succ);
}
// Depends on the above being done first (for this.root)
/*String init = this.lpn.getSimpleName().toString() + "_" + 0; // FIXME: factor out with newClassName
this.classes.put(init, constructInitClass(init));*/
}
// Pre: curr is not terminal state
private ClassBuilder constructClass(EState curr) throws ScribbleException // FIXME: APIGenerationException?
{
switch (curr.getStateKind())
{
case OUTPUT:
{
/*Set<IOAction> as = curr.getTakeable();
if (as.stream().allMatch((a) -> a.isSend()))*/
{
return new OutputSocketGenerator(this, curr).generateType();
}
//throw new RuntimeException("TODO: " + curr.toLongString());
}
case ACCEPT:
{
return new AcceptSocketGenerator(this, curr).generateType();
}
case UNARY_INPUT:
{
return new ReceiveSocketGenerator(this, curr).generateType();
}
case POLY_INPUT:
{
// Receive only
return new BranchSocketGenerator(this, curr).generateType();
}
default:
{
throw new RuntimeException("[TODO] State Channel API generation not supported for: " + curr.getStateKind() + ", " + curr.toLongString());
}
}
}
public GProtocolName getGProtocolName()
{
return this.gpn;
}
public Role getSelf()
{
return this.self;
}
protected EState getInitialState()
{
return this.init;
}
protected Module getMainModule()
{
return this.job.getContext().getMainModule();
}
protected void addTypeDecl(TypeBuilder tb)
{
this.types.put(tb.getName(), tb);
}
public String getSocketClassName(EState s)
{
return this.classNames.get(s);
}
public TypeBuilder getType(String key)
{
return this.types.get(key);
}
}