package org.scribble.main; import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.scribble.ast.Module; import org.scribble.ast.global.GProtocolDecl; import org.scribble.model.endpoint.AutParser; import org.scribble.model.endpoint.EGraph; import org.scribble.model.global.SGraph; import org.scribble.sesstype.name.GProtocolName; import org.scribble.sesstype.name.LProtocolName; import org.scribble.sesstype.name.ModuleName; import org.scribble.sesstype.name.Role; import org.scribble.util.ScribUtil; import org.scribble.visit.context.EGraphBuilder; import org.scribble.visit.context.Projector; // Global "static" context information for a Job -- single instance per Job, should not be shared between Jobs // Mutable: projections, graphs, etc are added mutably later -- replaceModule also mutable setter -- "users" get this from the Job and expect to setter mutate "in place" public class JobContext { private final Job job; public final ModuleName main; // ModuleName keys are full module names -- currently the modules read from file, distinguished from the generated projection modules private final Map<ModuleName, Module> parsed;// = new HashMap<>(); // LProtocolName is the full local protocol name (module name is the prefix) private final Map<LProtocolName, Module> projected = new HashMap<>(); private final Map<LProtocolName, EGraph> fairEGraphs = new HashMap<>(); private final Map<GProtocolName, SGraph> fairSGraphs = new HashMap<>(); private final Map<LProtocolName, EGraph> unfairEGraphs = new HashMap<>(); private final Map<GProtocolName, SGraph> unfairSGraphs = new HashMap<>(); private final Map<LProtocolName, EGraph> minimisedEGraphs = new HashMap<>(); // Toolchain currently depends on single instance of each graph (state id equality), e.g. cannot re-build or re-minimise, would not be the same graph instance // FIXME: currently only minimising "fair" graph, need to consider minimisation orthogonally to fairness -- NO: minimising (of fair) is for API gen only, unfair-transform does not use minimisation (regardless of user flag) for WF protected JobContext(Job job, Map<ModuleName, Module> parsed, ModuleName main) { this.job = job; this.parsed = new HashMap<ModuleName, Module>(parsed); this.main = main; } public Module getMainModule() { return getModule(this.main); } // Used by Job for pass running, includes projections (e.g. for reachability checking) // Safer to get module names and require user to re-fetch the module by the getter each time (after replacing), to make sure the latest is used public Set<ModuleName> getFullModuleNames() { Set<ModuleName> modnames = new HashSet<>(); modnames.addAll(getParsedFullModuleNames()); modnames.addAll(getProjectedFullModuleNames()); return modnames; } public Set<ModuleName> getParsedFullModuleNames() { Set<ModuleName> modnames = new HashSet<>(); modnames.addAll(this.parsed.keySet()); return modnames; } public Set<ModuleName> getProjectedFullModuleNames() { return this.projected.keySet().stream().map((lpn) -> lpn.getPrefix()).collect(Collectors.toSet()); } /*public boolean hasModule(ModuleName fullname) { return isParsedModule(fullname) || isProjectedModule(fullname); }*/ private boolean isParsedModule(ModuleName fullname) { return this.parsed.containsKey(fullname); } private boolean isProjectedModule(ModuleName fullname) { //return this.projected.keySet().stream().filter((lpn) -> lpn.getPrefix().equals(fullname)).count() > 0; return getProjectedFullModuleNames().contains(fullname); } public Module getModule(ModuleName fullname) { if (isParsedModule(fullname)) { return this.parsed.get(fullname); } else if (isProjectedModule(fullname)) { return this.projected.get( this.projected.keySet().stream().filter((lpn) -> lpn.getPrefix().equals(fullname)).collect(Collectors.toList()).get(0)); } else { throw new RuntimeException("Unknown module: " + fullname); } } protected void replaceModule(Module module) { ModuleName fullname = module.getFullModuleName(); if (isParsedModule(fullname)) { this.parsed.put(fullname, module); } else if (isProjectedModule(fullname)) { addProjection(module); } else { throw new RuntimeException("Unknown module: " + fullname); } } // Make context immutable? (will need to assign updated context back to Job) -- will also need to do for Module replacing public void addProjections(Map<GProtocolName, Map<Role, Module>> projections) { for (GProtocolName gpn : projections.keySet()) { Map<Role, Module> mods = projections.get(gpn); for (Role role : mods.keySet()) { addProjection(mods.get(role)); } } /*// Doesn't work for external subprotocols now that Projector doesn't record Module-specific dependencies itself try { ContextBuilder builder = new ContextBuilder(this.job); for (ProtocolName lpn : this.projections.keySet()) { Module mod = this.projections.get(lpn); mod = (Module) mod.accept(builder); replaceModule(mod); } } catch (ScribbleException e) { throw new RuntimeException("Shouldn't get in here: " + e); }*/ } private void addProjection(Module mod) { LProtocolName lpn = (LProtocolName) mod.getProtocolDecls().get(0).getFullMemberName(mod); this.projected.put(lpn, mod); } public Module getProjection(GProtocolName fullname, Role role) throws ScribbleException { Module proj = this.projected.get(Projector.projectFullProtocolName(fullname, role)); if (proj == null) { throw new ScribbleException("Projection not found: " + fullname + ", " + role); // E.g. disamb/enabling error before projection passes (e.g. CommandLine -fsm arg) // FIXME: should not occur any more } return proj; } protected void addEGraph(LProtocolName fullname, EGraph graph) { this.fairEGraphs.put(fullname, graph); } public EGraph getEGraph(GProtocolName fullname, Role role) throws ScribbleException { LProtocolName fulllpn = Projector.projectFullProtocolName(fullname, role); // Moved form LProtocolDecl EGraph graph = this.fairEGraphs.get(fulllpn); if (graph == null) { Module proj = getProjection(fullname, role); // Projected module contains a single protocol EGraphBuilder builder = new EGraphBuilder(this.job); proj.accept(builder); graph = builder.util.finalise(); addEGraph(fulllpn, graph); } return graph; } protected void addUnfairEGraph(LProtocolName fullname, EGraph graph) { this.unfairEGraphs.put(fullname, graph); } public EGraph getUnfairEGraph(GProtocolName fullname, Role role) throws ScribbleException { LProtocolName fulllpn = Projector.projectFullProtocolName(fullname, role); EGraph unfair = this.unfairEGraphs.get(fulllpn); if (unfair == null) { unfair = getEGraph(fullname, role).init.unfairTransform().toGraph(); addUnfairEGraph(fulllpn, unfair); } return unfair; } protected void addSGraph(GProtocolName fullname, SGraph graph) { this.fairSGraphs.put(fullname, graph); } public SGraph getSGraph(GProtocolName fullname) throws ScribbleException { SGraph graph = this.fairSGraphs.get(fullname); if (graph == null) { GProtocolDecl gpd = (GProtocolDecl) getModule(fullname.getPrefix()).getProtocolDecl(fullname.getSimpleName()); Map<Role, EGraph> egraphs = getEGraphsForSGraphBuilding(fullname, gpd, true); boolean explicit = gpd.modifiers.contains(GProtocolDecl.Modifiers.EXPLICIT); graph = SGraph.buildSGraph(egraphs, explicit, this.job, fullname); addSGraph(fullname, graph); } return graph; } private Map<Role, EGraph> getEGraphsForSGraphBuilding(GProtocolName fullname, GProtocolDecl gpd, boolean fair) throws ScribbleException { Map<Role, EGraph> egraphs = new HashMap<>(); for (Role self : gpd.header.roledecls.getRoles()) { egraphs.put(self, fair ? getEGraph(fullname, self) : getUnfairEGraph(fullname, self)); } return egraphs; } protected void addUnfairSGraph(GProtocolName fullname, SGraph graph) { this.unfairSGraphs.put(fullname, graph); } public SGraph getUnfairSGraph(GProtocolName fullname) throws ScribbleException { SGraph graph = this.unfairSGraphs.get(fullname); if (graph == null) { GProtocolDecl gpd = (GProtocolDecl) getModule(fullname.getPrefix()).getProtocolDecl(fullname.getSimpleName()); Map<Role, EGraph> egraphs = getEGraphsForSGraphBuilding(fullname, gpd, false); boolean explicit = gpd.modifiers.contains(GProtocolDecl.Modifiers.EXPLICIT); graph = SGraph.buildSGraph(egraphs, explicit, this.job, fullname); addUnfairSGraph(fullname, graph); } return graph; } protected void addMinimisedEGraph(LProtocolName fullname, EGraph graph) { this.minimisedEGraphs.put(fullname, graph); } public EGraph getMinimisedEGraph(GProtocolName fullname, Role role) throws ScribbleException { LProtocolName fulllpn = Projector.projectFullProtocolName(fullname, role); EGraph minimised = this.minimisedEGraphs.get(fulllpn); if (minimised == null) { String aut = runAut(getEGraph(fullname, role).init.toAut(), fulllpn + ".aut"); minimised = new AutParser().parse(aut); addMinimisedEGraph(fulllpn, minimised); } return minimised; } // Duplicated from CommandLine.runDot // Minimises the FSM up to bisimulation // N.B. ltsconvert will typically re-number the states private static String runAut(String fsm, String aut) throws ScribbleException { String tmpName = aut + ".tmp"; File tmp = new File(tmpName); if (tmp.exists()) // Factor out with CommandLine.runDot (file exists check) { throw new RuntimeException("Cannot overwrite: " + tmpName); } try { ScribUtil.writeToFile(tmpName, fsm); String[] res = ScribUtil.runProcess("ltsconvert", "-ebisim", "-iaut", "-oaut", tmpName); if (!res[1].isEmpty()) { throw new RuntimeException(res[1]); } return res[0]; } finally { tmp.delete(); } } }