/* * This file is part of the X10 project (http://x10-lang.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.opensource.org/licenses/eclipse-1.0.php * * (C) Copyright IBM Corporation 2006-2010. */ package x10.constraint.xnative; import java.io.Serializable; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import x10.constraint.xnative.XNativeConstraint; import x10.constraint.XFailure; import x10.constraint.XTerm; import x10.constraint.xnative.XNativeField; import x10.constraint.xnative.XNativeLit; import x10.constraint.xnative.XNativeTerm; import x10.constraint.xnative.XNativeVar; import x10.constraint.visitors.XGraphVisitor; import x10.util.CollectionFactory; /** * All nodes that occur in the graph maintained by a constraint * must implement Promise. Thus a Promise is a pointer into the state of some specific constraint. * <p> * A promise contains fields: * <ol> * <li>XPromise value -- if non-null, points to a promise this one has been equated with. * <li>Collection<XPromise> disequals -- contains set of other nodes this has been disequated with * <li>XTerm var -- externally visible term labeling this promise * <li>Map<XName, XPromise> fields -- hashmap of fields of this promise (if any). * </ol> * It maintains the invariant <code>> value != null</code> implies <code>disequals==null && fields == null</code>. * That is, if <code>value !=null</code> (in this case we say the promise ie <em>bound</em>), all further * information about this node in the graph is found by going to to <code>value</code>, except for the <code>XTerm</code> * labeling this node (<code>var</code>). See <code>term()</code>. * * <p>If <code>a.value==b</code> * we sometimes say that <code>b</code> has an incoming "eq" link, and <code>a</code> has an outgoing "eq" link. * * <p> Constraint graphs may contain sequences of such bindings. These can be shortened without any * semantic consequence. * * <p> Constraint graphs are always acyclic. * * <p> Note: This class is not public only so that extensions of this constraint system may use it. * This interface is <em>not</em> intended to be used by customers of the constraint system. * @author vj * */ public class XPromise implements Cloneable, Serializable{ /** * The externally visible XTerm that this node represents in the constraint * graph. May be null, if this promise corresponds to an internal node. */ protected XNativeTerm nodeLabel; /** * This node may have been equated to another node, n. If so, value contains * the reference to n. Can be null. */ protected XPromise value; /** * Lazily created collection of XPromises to which this one has been disequated. */ protected Collection<XPromise> disEquals; /** * fields captures constraints on fields of this variable, if any. * Invariant: if value is not null, then fields must be null, the * constraints are translated to the target of the value. * * We represent composite terms (e.g., binary operators, unary operators, * calls) as promises with fields representing the operator and the * operands. */ protected Map<Object, XPromise> fields; /** * Create a new promise labeled with the external term c. * @param c -- the term labeling the promise. */ public XPromise(XNativeTerm term) { super(); value = null; nodeLabel = term; } /** * Create a new promise with the given fields. * @param fields -- the fields of the promise */ public XPromise(Map<Object, XPromise> fields) { this(fields, null); } /** * Create a new promise with the given fields, and labeled with the given term. * @param fields -- the fields of the promise * @param term -- the term labeling the promise */ public XPromise(Map<Object, XPromise> fields, XNativeTerm term) { this(term, null, fields); } /** * Create a new promise with the given fields, the given label (var) and * pointing to value. Either value==null || fields==null. var may also be null. * @param var -- the term labeling the promise * @param value -- the XTerm that this promise points to * @param fields -- the fields of the promise. */ public XPromise(XNativeTerm var, XPromise value, Map<Object, XPromise> fields) { assert value==null || fields == null; this.nodeLabel = var; this.value = value; if (fields != null) this.fields = new LinkedHashMap<Object, XPromise>(fields); } /** A promise to which this promise is bound. */ public XPromise value() { return value; } /** * Return the term that labels this promise. This term is intended to be the canonical XTerm * labeling this promise, following the direct path from the root node. * Note: this promise may be forwarded to another; yet the term returned is this's term, * not the term corresponding to the target promise. * @return null if this promise is an internal promise. */ public XNativeTerm term() {return nodeLabel;} /** Map from field names f to promises term().f */ public Map<Object, XPromise> fields() {return fields;} /** * Set the term corresponding to this promise. The term may be null, such a promise is * called a silent promise. Silent promises are not dumped (that is, they do not * correspond to constraints on externally visible terms). They correspond to internal * nodes that are used just for implementation efficiency. * @param term */ public void setTerm(XNativeTerm term) { Set<XPromise> visited = CollectionFactory.newHashSet(); visited.add(this); setTerm(term, visited); } public void setTerm(XNativeTerm term, Set<XPromise> visited) { nodeLabel = (XNativeTerm)term; if (nodeLabel != null && fields != null) { for (Map.Entry<Object, XPromise> entry : fields.entrySet()) { Object key = entry.getKey(); XPromise p = entry.getValue(); if (visited.contains(p)) continue; visited.add(p); assert p.term() instanceof XNativeField : term + "." + key + " = " + p + " (not a field)"; XNativeField<?> f = (XNativeField<?>) p.term(); assert f.field().equals(key) : term + "." + key + " = " + p + " (different field)"; p.setTerm((XNativeTerm)f.copyReceiver((XNativeVar)term), visited); } } } /** Has this promise been forwarded? * * @return true if it has been forwarded (its value !=null) */ public boolean forwarded() {return value != null;} /** Does this node have children? * A node cannot both have children and be forwarded. */ public boolean hasChildren() {return fields != null;} public void setLabel(XNativeVar v) {nodeLabel = v;} protected XPromise clone() { XPromise result = null; try { result = (XPromise) super.clone(); } catch (CloneNotSupportedException z) { // But it is! } return result; } /** Follow the chain of non-null value's, returning the one at the end. * * @return */ public XPromise lookup() { if (value != null) return value.lookup(); return this; } /** * this must be the promise corresponding to the index'th element in the list vars. * Return the node in the graph of constraint c obtained * by following the path specified by vars[index],.., vars[vars.length-1] from this * node. * * <p> Create new nodes if necessary so that this path is defined in the graph of c. * @param vars vars must be a sequence XVar, XField, ... XField, satisfying the property * that the receiver of each element (other than the first) is the preceding element. * @param index * @param c * @return the node at the end of the path. This must not be a forwarded node. * @throws XFailure */ public XPromise intern(XNativeVar[] vars, int index) throws XFailure { return intern(vars, index, null); } /** * vars is as above. If last is not null, then do not create a new promise * for vars[index-1], instead use last. * @return * @throws XFailure */ public XPromise intern(XNativeVar[] vars, int index, XPromise last) { assert index >= 1; // follow the eq link if there is one. if (value != null ) return value.intern(vars, index, last); if (index == vars.length)return this; // if not, we need to add this path here. Ensure fields is initialized. if (fields == null) fields = CollectionFactory.<Object, XPromise>newHashMap(); assert vars[index] instanceof XNativeField; XNativeField<?> f = (XNativeField<?>) vars[index]; Object s = f.field(); // check this edge already exists. XPromise p = (XPromise) fields.get(s); if (p == null) { // no edge. Create a new promise and add this edge. p = (index == vars.length - 1 && last != null) ? last : new XPromise(nodeLabel instanceof XNativeVar ? (XNativeTerm)f.copyReceiver((XNativeVar)nodeLabel) : f); fields.put(s, p); } // recursively, intern the rest of the path on the child. return p.intern(vars, index + 1, last); } /** * An eq link entering this has just been established. Now the * children of the source of the link have to be transferred to this. * If this does not have an s-child, then install the child as its s-child. * If it does, then recursively bind the s-child to this's s child. * * @param s -- the name of the field * @param child -- the s child of the source of the eq link. */ public void addIn(Object s, XPromise orphan, XNativeConstraint parent) throws XFailure { if (value != null) { // Alternative is to fwd it blindly, that would be correct, but i // want to know // if this is happening. It should not happen. throw new XFailure("The node " + this + " is forwarded to " + value + "; the " + s + " child, " + orphan + ", cannot be added to it."); } if (fields == null) fields = new LinkedHashMap<Object, XPromise>(); XPromise child = (XPromise) fields.get(s); if (child != null) { while (child.forwarded()) child = child.value(); while (orphan.forwarded()) orphan = orphan.value(); orphan.bind(child, parent); return; } fields.put(s, orphan); } /** * Bind this promise to the given target. All the children of this, if any, have to be * added into the target. target should satisfy the condition that it is not forwarded. * Return true if the execution of this operation caused a change to the constraint graph; * false otherwise. * @param target */ public boolean bind(/* @nonnull */XPromise target, XNativeConstraint parent) throws XFailure { assert target.value() == null; if (disEquals != null) for (XPromise i : disEquals) { // Note: i's value may be set.. so need to get to the end of the chain. boolean result = i.lookup().equals(target); if (result) throw new XFailure("The promise " + this + " cannot be bound to the already disequated " + target + "."); } if (!target.equals(value) /*&& !target.equals(nodeLabel)*//*always true*/) { if (forwarded()) throw new XFailure("The promise " + this + " is already bound to " + value + "; cannot bind it to " + target + "."); if (this == target) return false;// nothing to do! // Check for cycles! if (canReach(target) || target.canReach(this)) throw new XFailure("Binding " + this + " to " + target + " creates a cycle."); int pref = term().prefersBeingBound(); int opref = target.term().prefersBeingBound(); if (pref == XNativeTerm.TERM_MUST_NOT_BE_BOUND) { if (opref == XNativeTerm.TERM_MUST_NOT_BE_BOUND) { if (term().equals(target.term())) return true; throw new XFailure("Cannot bind literal " + term() + " to " + target.term()); } return target.bind(this, parent); } if (pref == XNativeTerm.TERM_SHRUGS_ABOUT_BEING_BOUND && opref == XNativeTerm.TERM_PREFERS_BEING_BOUND) return target.bind(this, parent); value = target; // bind } if (fields != null) { // transfer fields for (Map.Entry<Object, XPromise> i : fields.entrySet()) { XPromise val = i.getValue(); // Need to find out the value down this path. XNativeTerm t = val.term(); if (t instanceof XNativeField) { XNativeField<?> thisTerm = (XNativeField<?>) t; XNativeVar root = thisTerm.rootVar(); if (root == this.term()) { // Need to relabel, recursively, because this --> target val.setTerm((XNativeTerm)thisTerm.copyReceiver((XNativeVar) target.term())); } } target.addIn(i.getKey(), val, parent); } fields = null; } if (disEquals != null) { // transfer disequals links for (XPromise i : disEquals) target.addDisEquals(i.lookup()); disEquals = null; } return true; } /** * Bind this promise to the given target using a NOT link. target should satisfy the condition * that it is not forwarded. Note that the following is consistent: X !=Y, X.f=Y.f. * @param target The term to be notBound to. * @return true if the execution of this operation caused a change in the constraint graph; * false otherwise. * @throws XFailure in case this is already bound to target. */ public boolean disBind(/* @nonnull */XPromise target) throws XFailure { assert target.value() == null; assert forwarded() == false; if (disEquals == null) disEquals = CollectionFactory.newHashSet(); boolean result = disEquals.add(target); return result; //if (!target.equals(value) && !target.equals(var)) { } /** * Is there a path from here to p? * Can this promise reach p in the directed graph representing the * constraints? */ public boolean canReach(XPromise p) { if (p == this) return true; if (value != null) return value.canReach(p); if (fields != null) for (XPromise q : fields.values()) if (q.canReach(p)) return true; return false; } /** * Let t1 be path (if path is not null), else the term labeling this promise. * If this promise has an outgoing edge to t2, and either dumpEQV is true or * ! t1.hasEQV(), then output t1==t2 to result, unless hideFake is true and one of t1 or t2 is * a fake field. * If this promise has fields, then recursively continue dumping with * the children, passing them a path t1.f (for field f) if * t1 is an instance of XVar, otherwise passing a path null (in this case t1 is an * atom and the children are its subterms). * (This takes care of multiple paths entering the promise.) * @param result * @param oldSelf */ public boolean visit(XNativeVar path, XGraphVisitor xg, XNativeConstraint parent) { XNativeTerm t1 = path == null? term() : path; if (t1 == null) return true; if (t1.isAtomicFormula()) return xg.rawVisitAtomicFormula(t1); if (value != null) { XNativeTerm t2 = lookup().term(); //canonical term for this boolean result = xg.rawVisitEquals(t1, t2); if (! result) return result; // Continue processing the target node if the target is an eqv. // there may be fields that need to be processed. /* if (t2.hasEQV()) result = value.visit((XVar) t1, xg, parent);*/ return result; } if (fields != null) { XNativeVar v = t1 instanceof XNativeVar ? (XNativeVar) t1 : null; // If t1 is not an XVar, it is an atomic formula, and the fields // are its sub-terms. Hence v should be null. for (Map.Entry<Object,XPromise> m : fields.entrySet()) { XPromise p=m.getValue(); XNativeTerm t=p.term(); XNativeVar path2=v==null? null : (XNativeVar)((XNativeField<?>) t).copyReceiver(v); boolean result=p.visit(path2,xg, parent); if (!result) return result; } } if (disEquals != null) { for (XPromise i:disEquals) { XNativeTerm nf=i.lookup().term(); boolean result = xg.rawVisitDisEquals(t1, nf); if (! result) return result; } } return true; } private boolean toStringMark = false; public String toString(String prefix) { if (toStringMark) return "..."; toStringMark = true; String fieldsString = "{"; if (fields != null) { for (Map.Entry<Object, XPromise> entry : fields.entrySet()) { fieldsString += "\n" /*prefix + " " + entry.getKey() + "=" */ + entry.getValue().toString(prefix + " "); } } fieldsString += "\n" + prefix + "}"; String res = prefix + nodeLabel + ((value != null) ? "->" + value : ((fields != null) ? fieldsString : "") + (disEquals != null ? " != " + disEquals.toString() : "")); toStringMark = false; return res; } public String toString() { return toString(""); } /** * An eq link entering this has just been established. Now the * disEquals targets of the source of the link have to be transferred to this. * * @param other -- the XPromise this is to be disequated to */ public void addDisEquals(XPromise other) { if (disEquals == null) disEquals = CollectionFactory.newHashSet(); disEquals.add(other); } public boolean hasDisBindings() { return disEquals != null && ! disEquals.isEmpty();} /** * Is this promise asserted to be disequal to other? * @param other * @return */ public boolean isDisBoundTo(XPromise other) { if (term() instanceof XNativeLit) { XNativeLit term = (XNativeLit) term(); XNativeTerm o = other.term(); if (o instanceof XNativeLit) return ! (term.equals(o)); } if (disEquals == null) return false; for (XPromise p : disEquals) if (p.canReach(other)) return true; return false; } public void ensureFields() { if (this.fields == null) this.fields = new LinkedHashMap<Object, XPromise>(); } }