/*
* Copyright (c) 2003- michael lawley and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation
* which accompanies this distribution, and is available by writing to
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Contributors:
* michael lawley
* David Hearnden
*
*/
package tefkat.engine;
import java.util.Collection;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.impl.DynamicEObjectImpl;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import tefkat.model.Extent;
import tefkat.model.Var;
import tefkat.model.VarUse;
/**
*
* Represents a mapping from Variables to Objects.
* Supports the following operations between unifiers:
*
* u.composeLeft(t) u <- u o t
* u.composeRight(t) u <- t o u
*
* Constraints maintained by this class:
*
* keySet().intersect(values).isEmpty()
*
* @author David Hearnden, 2003
* @author Michael Lawley, 2003-2005
*/
public class Binding {
static int counter = 0;
private boolean frozen = false;
final private Map varToTerm; // Each entry is a Var -> Object
// Note, Object may be a WrappedVar...
/**
* Constructs an empty unifier.
*/
public Binding() {
this(null);
}
/**
* Constructs a duplicate unifier.
* @param unifier The unifier to duplicate.
*/
public Binding(Binding unifier) {
incrementCounter();
if (null == unifier) {
varToTerm = new HashMap();
} else {
varToTerm = new HashMap(unifier.varToTerm);
}
}
private static void incrementCounter() {
counter++;
}
/**
* Removes all bindings to leave an empty unifier.
*/
public void clear() {
if (frozen) {
throw new BindingError("Cannot modify a frozen Binding");
}
varToTerm.clear();
}
public int size() {
return varToTerm.size();
}
/**
* Puts a binding from var to val into this unifier. This should only be
* done if var is not in the range of the unifier. If this is not the
* case, then add(var, val) should be used.
*
* Precondition:
* !(varToTerm.values().contains(var))
* !(val.variables().contains(var)) (occurs check)
* @param var The variable to bind.
* @param val The value to bind the variable to.
*/
public void put(Var var, Object val) {
if (frozen) {
throw new BindingError("Cannot modify a frozen Binding");
}
if (null == val) {
throw new BindingError("Attempting to bind " + var + " to null");
}
varToTerm.put(var, val);
}
/**
* Adds the binding from the var referenced by varUse to val into this unifier @see #add(Var, Object).
*
* Precondition:
* !(varToTerm.keySet().contains(var))
* !(val.variables().contains(var)) (occurs check)
* @param varUse The reference to the variable to bind.
* @param val The value to bind the variable to.
*/
public void add(VarUse varUse, Object val) {
add(varUse.getVar(), val);
}
/**
* Adds the binding from var to val into this unifier, first substituting
* away any instances of var in the range of the unifier. If var is not in
* the range of the unifier, then put(var, val) should be used for
* efficiency.
*
* Precondition:
* !(varToTerm.keySet().contains(var))
* !(val.variables().contains(var)) (occurs check)
* @param var The variable to bind.
* @param val The value to bind the variable to.
*/
public void add(Var var, Object val) {
if (frozen) {
throw new BindingError("Cannot modify a frozen Binding");
}
if (null == val) {
throw new BindingError("Attempting to bind " + var + " to null");
}
// TODO delete this debugging check
// if (val instanceof Var) {
// System.err.println("Warning: storing unwrapped Var in Binding");
// }
for (Iterator i = varToTerm.entrySet().iterator(); i.hasNext(); ) {
Map.Entry vt = (Map.Entry) i.next();
Object obj = vt.getValue();
if (obj instanceof WrappedVar) {
obj = ((WrappedVar) obj).getVar();
// FIXME - Moved this code inside above IF otherwise add fails
// when transforming the transformation itself (!) since
// a Var may (will) bind to itself.
if (var.equals(obj)) {
vt.setValue(val);
}
}
}
varToTerm.put(var, val);
}
/**
* Retrieves the binding for var if it exists, otherwise returns null.
* @param var The variable to query.
* @return The value of the variable in this unifier.
*/
public Object lookup(Var var) {
Object obj = varToTerm.get(var);
//boolean flag = var.getName().equals("V") && obj instanceof Var && ((Var) obj).getName().equals("T");
//if(flag) {
// System.err.println(hashCode() + " V == " + obj);
//}
if (obj instanceof DynamicObject && ((DynamicObject) obj).hasStaticInstance()) {
obj = ((DynamicObject) obj).getStaticInstance();
varToTerm.put(var, obj);
}
//if (var.getName().equals("V")) {
// System.err.println(hashCode() + " V /= " + obj);
//}
return obj;
}
/**
* If obj is a WrappedVar and is bound in u, return the corresponding value, otherwise return obj.
* @param obj
* @param u
* @return
*/
private Object substitute(Object obj, Binding u) {
Object val = obj;
if (obj instanceof WrappedVar) {
val = u.lookup(((WrappedVar) obj).getVar());
if (null == val) {
val = obj;
}
}
return val;
}
/**
* Applies unifier u to all the terms in this unifier.
*
* Precondition:
* u.values().intersect(keySet()).isEmpty()
*/
public void apply(Binding u) {
if (frozen) {
throw new BindingError("Cannot modify a frozen Binding");
}
if (null == u) {
return; // application of the empty binding does nothing
}
for (Iterator i = varToTerm.entrySet().iterator(); i.hasNext(); ) {
Map.Entry vt = (Map.Entry) i.next();
Object o = substitute(vt.getValue(), u);
vt.setValue(o);
}
}
/**
* Combines this unifier with u with the result being the composition
* of the two unifiers, with this unifier being on the right. This has
* the effect of applying u to all the terms in this, followed by the
* addition (putting) of u's bindings into this unifier.
*
* composeRight(u) this <- u o this
* where
* (u o this)(V) == u(this(V))
*
* Precondition: keySet().intersect(u.keySet()).isEmpty()
*
* @param u The unifier to compose with.
*/
public void composeRight(Binding u) {
if (frozen) {
throw new BindingError("Cannot modify a frozen Binding");
}
if (null == u) {
return; // composition of the empty binding does nothing
}
apply(u);
varToTerm.putAll(u.varToTerm);
}
/**
* Combines this unifier with u with the result being the composition
* of the two unifiers, with this unifier being on the left. This has
* the effect of applying this unifier's bindings to all the terms in u,
* followed by the addition (putting) of u's bindings into this unifier.
*
* composeLeft(u) this <- this o u
* where
* (this o u)(V) == this(u(V))
*
* Precondition: keySet().intersect(u.keySet()).isEmpty()
*
* We have weakened the precondition to allow vars to be self-bound or
* to be bound to the same thing multiple times (see resolvePatternUse)
*
* Note, we also account for DynamicObject/static instance equivalence
*
* @param u The unifier to compose with.
*/
public void composeLeft(Binding u) {
if (frozen) {
throw new BindingError("Cannot modify a frozen Binding");
}
if (null == u) {
return; // composition of the empty binding does nothing
}
Iterator i = u.varToTerm.entrySet().iterator();
while (i.hasNext()) {
Map.Entry vt = (Map.Entry) i.next();
Var uKey = (Var) vt.getKey();
Object uVal = vt.getValue();
// check precondition
if (true &&
varToTerm.containsKey(uKey) &&
// uKey != uVal && // Vars can be bound to themselves
!uVal.equals(lookup(uKey)) // Can be bound to the same thing
&& !(uVal instanceof WrappedVar)
) {
// System.err.println(this);
// System.err.println(u);
if (uVal instanceof DynamicObject) {
DynamicObject dyn = (DynamicObject) uVal;
if (dyn.getStaticInstance().equals(varToTerm.get(uKey))) {
break;
}
}
throw new BindingError("INTERNAL ERROR: Binding precondition violated for " + uKey + " =\n\t" + uVal + "\n\t" + varToTerm.get(uKey));
}
Object o = substitute(uVal, this);
if (o instanceof WrappedVar && varToTerm.containsKey(vt.getKey())) {
if (null == bindWrappedVar(this, (WrappedVar) o, vt.getValue())) {
throw new BindingError("Incompatible WrappedVar and value: " + o + ", and " + vt.getValue());
}
} else {
varToTerm.put(vt.getKey(), o);
}
}
}
/**
* @return The set of variables in this unifier.
*/
public Collection keys() {
return varToTerm.keySet();
}
public Set entrySet() {
return varToTerm.entrySet();
}
/**
* unifier ::= "{" (binding (", " binding)* "}"
* binding ::= <var> " -> " <term>
* @return A string representation of the unifier.
*/
public String toString() {
String s = null;
for (Iterator i = varToTerm.keySet().iterator(); i.hasNext(); ) {
Var var = (Var) i.next();
Object subs = lookup(var);
if (null == subs) {
throw new BindingError("NULL binding for " + var);
} else if (subs.getClass().equals(EObjectImpl.class) || subs instanceof DynamicEObjectImpl) {
subs = subs.hashCode() + ":" + ((EObject) subs).eClass().getName();
} else if (subs instanceof WrappedVar) {
subs = "W(" + subs + ")";
} else if (subs instanceof Extent) {
continue;
}
s = (s == null ? "" : s + ",\n ")
+ formatVar(var)
+ " -> " + subs;
}
return "{" + (s == null ? "" : s) + "}";
}
private String formatVar(Var var) {
return var.getScope().getName() + "::"
+ (null == var.getName() ? "_V" + var.hashCode() : var.getName());
}
public boolean equals(Object obj) {
if (obj instanceof Binding) {
Binding b = (Binding) obj;
return entrySet().equals(b.entrySet());
}
return false;
}
public int hashCode() {
return entrySet().hashCode() + 42;
}
public static class BindingError extends Error {
private static final long serialVersionUID = 6575697896092447618L;
public BindingError(String message) {
super(message);
}
}
/**
* You can not update the binding after calling freeze
*/
public void freeze() {
frozen = true;
}
public static Binding createBinding(Object val1, Object val2) {
Binding unifier = null;
if (val1 instanceof BindingPair) {
unifier = new Binding();
unifier.composeLeft((BindingPair) val1);
val1 = ((BindingPair) val1).getValue();
}
if (val2 instanceof BindingPair) {
if (null == unifier) {
unifier = new Binding();
}
unifier.composeLeft((BindingPair) val2);
val2 = ((BindingPair) val2).getValue();
}
if (val1 instanceof WrappedVar) {
unifier = bindWrappedVar(unifier, (WrappedVar) val1, val2);
} else if (val2 instanceof WrappedVar) {
unifier = bindWrappedVar(unifier, (WrappedVar) val2, val1);
} else if (val1 instanceof Number && val2 instanceof Number) {
if (val1 instanceof Float || val1 instanceof Double ||
val2 instanceof Float || val2 instanceof Double) {
double v1 = ((Number) val1).doubleValue();
double v2 = ((Number) val2).doubleValue();
if (v1 == v2) {
if (null == unifier) {
unifier = new Binding();
}
} else {
unifier = null;
}
} else {
long l1 = ((Number) val1).longValue();
long l2 = ((Number) val2).longValue();
if (l1 == l2) {
if (null == unifier) {
unifier = new Binding();
}
} else {
unifier = null;
}
}
} else {
// bloody EMF - a "generated" EEnumLiteral is not "equal" to a
// dynamic EEnumLiteral or something like that
// - you need to get the EEnumerator for comparisons...
if (val1 instanceof EEnumLiteral) {
val1 = ((EEnumLiteral) val1).getInstance();
}
if (val2 instanceof EEnumLiteral) {
val2 = ((EEnumLiteral) val2).getInstance();
}
if (val1.equals(val2)) {
if (null == unifier) {
unifier = new Binding();
}
} else {
unifier = null;
}
}
return unifier;
}
static public Binding bindWrappedVar(Binding unifier, WrappedVar wVar, Object val) {
final Binding result;
Var var = wVar.getVar();
EClass eClass = wVar.getType();
if (null != eClass) {
if (val instanceof WrappedVar) {
WrappedVar wVar2 = (WrappedVar) val;
Var var2 = wVar2.getVar();
EClass eClass2 = wVar2.getType();
boolean isExact2 = wVar2.isExact();
if (null != eClass2) {
if (wVar.setType(eClass2, isExact2)) {
result = (null == unifier) ? new Binding() : unifier;
result.add(var, wVar);
result.add(var2, wVar);
} else { // type mismatch
result = null;
}
} else {
result = (null == unifier) ? new Binding() : unifier;
result.add(var, wVar);
result.add(var2, wVar);
}
} else if (val instanceof EObject) {
if (eClass.isSuperTypeOf(((EObject) val).eClass())) {
result = (null == unifier) ? new Binding() : unifier;
result.add(var, val);
} else {
result = null;
}
} else {
Class cls = eClass.getInstanceClass();
if (null != cls) {
if (cls.isAssignableFrom(val.getClass())) {
result = (null == unifier) ? new Binding() : unifier;
result.add(var, val);
} else {
result = null;
}
} else {
result = null;
}
}
} else {
result = (null == unifier) ? new Binding() : unifier;
result.add(var, val);
}
return result;
}
}