/* Author: Jeff Dalton * Updated: Tue Dec 4 23:44:06 2001 by Jeff Dalton * Copyright: (c) 2001, AIAI, University of Edinburgh */ package ix.icore.process; import java.util.*; import ix.icore.domain.Schema; import ix.icore.process.Variable; import ix.util.*; import ix.util.lisp.*; import ix.util.match.*; /** * A node that represents an action in a process. This class * supports expansion into subactions and the propagation of status * changes, thus acting as a simple process model and expansion * planner. */ public class PNode implements StatusValues { public LList pattern; public int level = 0; // expansion level, 1 + the parent's public int status = STATUS_BLANK; public Set patternVars; public PNode parent = null; public LList children = Lisp.NIL; public HashMap varTable = new HashMap(); // when instantiating a schema // maps ?name to instance of Variable. public Vector preNodes = new Vector(); // nodes directly before this public Vector postNodes = new Vector(); // nodes directly after this public Schema schema = null; public PNode(PNode parent, LList pattern) { this.pattern = pattern; this.parent = parent; if (parent != null) level = parent.level + 1; // If this is a subnode intriduced by expansion with a schema, // all ?-vars in the pattern should have been replaced by values // or by Variables. But if something gives us a level-0 pattern // that contains ?=vars, we need to convert them to Variables, // like this: if (level == 0) { Map varEnv = new HashMap(); LList newPattern = (LList)SimpleMatcher.emptyEnv.instantiateTree (pattern, new MakeVarIfUnbound(varEnv)); if (!varEnv.isEmpty()) { Debug.noteln("Replacing top-level pattern", pattern); Debug.noteln("with", newPattern); pattern = newPattern; this.pattern = newPattern; } } patternVars = Variable.varsIn(pattern); // computeStatus(); // may be too soon from subclass POV /\/ } public boolean isAncestorOf(PNode n) { for (PNode p = n.parent; p != null; p = p.parent) { if (this == p) return true; } return false; } public boolean isDescendentOf(PNode n) { return n.isAncestorOf(this); } public Set getUnboundVars() { return Variable.unboundVarsIn(patternVars); } /** * Creates child nodes as specified by a schema and installs * ordering links. The subnodes are constructed by a NodeMaker * so that they can be instances of a subclass of Node and to * allow other, related objects to be constructed at the same time. */ public void expandOneLevel(Schema sourceSchema, MatchEnv env, final PNodeMaker nodeMaker) { // Instantiate the schema Debug.assert(schema == null); Function1 ifUnbound = new MakeVarIfUnbound(varTable); schema = sourceSchema.instantiate(env, ifUnbound); // Create the child nodes specified by the schema. // A nodeSpec is a LList (Long:number LList:pattern). children = schema.nodes.mapcar(new Function1() { public Object funcall(Object nodeSpec) { LList spec = (LList)nodeSpec; LList childPattern = (LList)spec.elementAt(1); return nodeMaker.makePNode(PNode.this, childPattern); } }); // Add the orderings processOrderings(children, schema.orderings); // Have all the children determine their status allComputeStatus(children.elements()); } public class MakeVarIfUnbound implements Function1 { protected Map nameToVarMap; public MakeVarIfUnbound(Map nameToVarMap) { this.nameToVarMap = nameToVarMap; }; public Object funcall(Object name) { Variable v = (Variable)nameToVarMap.get(name); if (v == null) { v = new Variable(name); v.sourceNode = PNode.this; // The schema will be null for Variables in top-level patterns v.sourceSchema = PNode.this.schema; nameToVarMap.put(name, v); } return v; } } protected void processOrderings(LList nodes, LList orderings) { for (Enumeration ords = orderings.elements(); ords.hasMoreElements();) { LList ord = (LList)ords.nextElement(); int n1 = ((Number)ord.car()).intValue() - 1; int n2 = ((Number)ord.cdr().car()).intValue() -1; PNode before = (PNode)nodes.elementAt(n1); PNode after = (PNode)nodes.elementAt(n2); before.linkBefore(after); } } protected void linkBefore(PNode after) { Debug.noteln(" " + this + "\n --> " + after); postNodes.addElement(after); after.preNodes.addElement(this); // Since after has a new preNode, its status may change. // after.computeStatus(); } /** * computeStatus() is used to change the status of related nodes * when a node changes its status. */ public void computeStatus() { // We assume that the current status may not be correct, // but only certain transitions are handled. // If the node is BLANK (waiting to become POSSIBLE), then: // if the parent is EXECUTING (or null) and all predecessors // are COMPLETE, then this node should become POSSIBLE. if (status == STATUS_BLANK) { if ((parent == null || parent.status == STATUS_EXECUTING) && allHaveStatus(preNodes.elements(), STATUS_COMPLETE)) setStatus(STATUS_POSSIBLE); } // If the node is EXECUTING, then: // if all children are COMPLETE, this this node should // become COMPLETE. else if (status == STATUS_EXECUTING) { Debug.assert(!children.isNull(), "Executing w/o children"); if (allHaveStatus(children.elements(), STATUS_COMPLETE)) setStatus(STATUS_COMPLETE); } } /** * Changes the node's status and then that of related nodes. */ public void setStatus(int status) { // A change in a node's status may affect the parent, // successor siblings, or children. Debug.noteln("Setting status of " + this + " to " + ViewColor.statusName[status]); this.status = status; if (status == STATUS_COMPLETE) { if (parent != null) parent.computeStatus(); // may -> COMPLETE allComputeStatus(postNodes.elements()); // may -> POSSIBLE } else if (status == STATUS_EXECUTING) { allComputeStatus(children.elements()); // may -> POSSIBLE } else if (status == STATUS_POSSIBLE) { // When a node that already had children becomes POSSIBLE, // we automatically make it start EXECUTING if (!children.isEmpty()) setStatus(STATUS_EXECUTING); } } public static boolean allHaveStatus(Enumeration e, int status) { while(e.hasMoreElements()) { PNode n = (PNode)e.nextElement(); if (n.status != status) return false; } return true; } public static void allComputeStatus(Enumeration e) { while (e.hasMoreElements()) { PNode n = (PNode)e.nextElement(); n.computeStatus(); } } public String toString(){ return "#"; } }