/*
* 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 org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import tefkat.model.Condition;
import tefkat.model.Expression;
import tefkat.model.Extent;
import tefkat.model.MofInstance;
import tefkat.model.MofOrder;
import tefkat.model.NotTerm;
import tefkat.model.OrTerm;
import tefkat.model.Term;
import tefkat.model.TrackingUse;
import tefkat.model.Var;
import tefkat.model.internal.ModelUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* SourceResolver performs SLDNF resolution on a provided goal, creating an SLDNF resolution tree.
* The success nodes of this tree represent different
* variable bindings that make the provided goal true.
*
* Example:
* SourceResolver r = new SourceResolver();
* Tree t = r.resolve(goal, binding);
* Collection solutions = r.solutions(t, vars);
*
* @author David Hearnden, Aug 2003
* @author michael lawley, Aug 2003 -- modified for QVT model
*/
class SourceResolver extends AbstractResolver {
private static final class HandleNewTrackingInstance implements TrackingCallback {
private final Context context;
private final EClass class1;
private final Object[][] map;
HandleNewTrackingInstance(Context context, EClass class1, Object[][] map) {
this.context = context;
this.class1 = class1;
this.map = map;
}
public String toString() {
return context.tree.toString();
}
public void handleInstance(EObject inst) throws ResolutionException, NotGroundException {
if (context.tree.isCompleted()) {
context.error("INTERNAL ERROR: Tree completed too early: " + context);
}
// Check each feature looking for a mismatch
List oldBindings = new ArrayList();
oldBindings.add(new Binding());
boolean isMatch = true;
for (int i = 0; isMatch && i < map.length; i++) {
String featureName = (String) map[i][0];
List featureValues = (List) map[i][1];
List newBindings = null;
EStructuralFeature sFeature = getFeature(context, class1, featureName);
Object value = inst.eGet(sFeature);
if (value == null) {
// NOT_EQUAL, NULL
isMatch = false;
} else if (sFeature.isMany()) {
if (((List) value).size() == 0) {
isMatch = false;
} else if (featureValues.size() == 1 && featureValues.get(0) instanceof WrappedVar) {
// UNIFY
final WrappedVar wrappedVar = (WrappedVar) featureValues.get(0);
newBindings = new ArrayList();
for (Iterator bindingItr = oldBindings.iterator(); bindingItr.hasNext(); ) {
Binding oldUnifier = (Binding) bindingItr.next();
for (Iterator valueItr = ((List) value).iterator(); valueItr.hasNext(); ) {
Binding unifier = new Binding(oldUnifier);
if (null != Binding.bindWrappedVar(unifier, wrappedVar, valueItr.next())) {
// only add if binding of wrapped var succeeded
newBindings.add(unifier);
}
}
}
ExtentUtil.highlightEdge(inst, value, ExtentUtil.FEATURE_LOOKUP);
} else {
for (Iterator valItr = ((List) value).iterator(); valItr.hasNext(); ) {
Object o = valItr.next();
if (o instanceof BindingPair) {
context.delay("Implementation limitiation: BindingPair in TrackingUse not yet supported.");
}
}
if (featureValues.removeAll((List) value)) {
newBindings = oldBindings;
} else {
isMatch = false;
}
}
} else if (featureValues.size() == 1) {
Object featureValue = featureValues.get(0);
if (featureValue instanceof WrappedVar) {
// UNIFY
WrappedVar wrappedVar = (WrappedVar) featureValue;
newBindings = new ArrayList();
for (Iterator bindingItr = oldBindings.iterator(); bindingItr.hasNext(); ) {
Binding oldUnifier = (Binding) bindingItr.next();
Binding unifier = new Binding(oldUnifier);
if (null != Binding.bindWrappedVar(unifier, wrappedVar, value)) {
newBindings.add(unifier);
}
}
ExtentUtil.highlightEdge(inst, value, ExtentUtil.FEATURE_LOOKUP);
} else if (featureValue instanceof BindingPair) {
context.delay("Implementation limitiation: BindingPair in TrackingUse not yet supported.");
} else if (value.equals(featureValue)) {
newBindings = oldBindings;
} else {
// NOT-EQUAL, non-NULL
isMatch = false;
}
} else {
// NOT_EQUAL, cardinality mismatch
isMatch = false;
}
oldBindings = newBindings;
}
if (isMatch) {
for (Iterator itr = oldBindings.iterator(); itr.hasNext(); ) {
Binding unifier = (Binding) itr.next();
/**
* Create a new branch of the tree, and continue
* resolution from the newly created node.
*/
context.createBranch(unifier);
}
}
}
}
SourceResolver(RuleEvaluator evaluator) {
super(evaluator);
}
/**
* Find instances of the referenced tracking class with matching feature values.
* This may involve variable binding.
*
* Tracking queries are strictly match-only, so we don't need to concern
* ourselves with tracking instance creation. So, the
* basic algorithm is to scan all instances of the nominated tracking
* class and filter out any instance that doesn't bind with the supplied
* feature expressions.
*
* @param tree
* @param node
* @param goal
* @param literal
*/
protected void resolveTrackingUse(
final Context context,
final TrackingUse literal)
throws ResolutionException, NotGroundException {
// Get the properties of the TrackingUse
final EClass trackingClass = literal.getTracking();
if (trackingClass.eIsProxy()) {
// If it's still a proxy after the getTracking() call, the cross-document reference proxy has
// not been resolved, meaning the reference was dodgy, i.e. to a non-existent class or something
//
context.error("Unable to locate tracking class: " + trackingClass);
}
List featureList = literal.getFeatures();
final Object[][] featureMap = new Object[featureList.size()][2];
int i = 0;
for (Iterator itr = featureList.iterator(); itr.hasNext(); ) {
Map.Entry entry = (Map.Entry) itr.next();
featureMap[i][0] = entry.getKey();
featureMap[i][1] = exprEval.eval(context, (Expression) entry.getValue());
i++;
}
TrackingCallback callback = new HandleNewTrackingInstance(context, trackingClass, featureMap);
// Get the existing instances of the tracking class.
//
List trackings = ruleEval.getTrackingCache(trackingClass);
ExtentUtil.highlightNodes(trackings, ExtentUtil.CLASS_LOOKUP);
// record that this rule has queried the tracking class
ruleEval.trackingQuery(trackingClass, callback);
for (Iterator trackingItr = trackings.iterator(); trackingItr.hasNext(); ) {
EObject inst = (EObject) trackingItr.next();
callback.handleInstance(inst);
}
}
protected boolean resolveMofInstance(
final Context context,
final MofInstance literal)
throws ResolutionException, NotGroundException {
/**
* Find all instances of the specified class in the (context) extent
*/
Var extentVar = literal.getExtent();
List results = exprEval.eval(context, literal.getTypeName());
if (results.size() != 1) {
context.error("Expected only a single type name, got: " + results);
}
Object typeObj = results.get(0);
EClassifier theClass = null;
String className;
if (typeObj instanceof EClassifier) {
theClass = (EClassifier) typeObj;
className = ModelUtils.getFullyQualifiedName(theClass);
} else if (typeObj instanceof WrappedVar) {
context.error("Unsupported mode (unbound typeName) for MofInstance: " + literal);
// satisfy the compiler (it doesn't know the previous line always throws an exception)
className = null;
} else {
className = String.valueOf(typeObj);
if (!"_".equals(className)) {
theClass = ModelUtils.findClassifierByName(getNameMap(), className);
if (null == theClass) {
ruleEval.fireWarning("Could not find class named: " + className);
return false;
}
}
}
Expression instanceExpr = literal.getInstance();
// Our "package instance" wrapper around an EMOF ExtentUtil (EMF Resource)
Extent extent = (null == extentVar ? null : (Extent) context.lookup(extentVar));
Collection instances = exprEval.eval(context, instanceExpr);
boolean success = false;
for (Iterator itr = instances.iterator(); itr.hasNext(); ) {
Object instance = itr.next();
if (instance instanceof WrappedVar) {
// handle the ??- mode
//
// does lazy expansion of the tree.
// This will cause relatively untested code (and broken?) to be used in
// other parts of Abstract/Source/TargetResolver and Evaluator
// FIXME make this stuff work
// FIXME I think it works, but some Unit tests would make me more comfortable
WrappedVar wVar = (WrappedVar) instance;
wVar.setExtent(extent);
if ("_".equals(className)) {
// Any type will do, isExact in this context is meaningless
// hence, no need to call setType on the WrappedVar
Binding unifier = new Binding();
unifier.add(wVar.getVar(), wVar);
context.createBranch(unifier);
} else if (!(theClass instanceof EClass)) {
ruleEval.fireWarning("Could not find class named: " + className);
} else if (wVar.setType((EClass) theClass, literal.isExact())) {
Binding unifier = new Binding();
unifier.add(wVar.getVar(), wVar);
context.createBranch(unifier);
}
} else {
// handle the ??+ mode
Binding unifier = null;
if (instance instanceof BindingPair) {
unifier = (Binding) instance;
instance = ((BindingPair) instance).getValue();
}
if (instance instanceof EObject && (null == extent || extent.contains((EObject) instance))) {
ExtentUtil.highlightNode(instance, ExtentUtil.OBJECT_LOOKUP);
boolean isOfType =
null == theClass ||
(literal.isExact() ? theClass.equals(((EObject) instance).eClass()) : theClass.isInstance(instance));
if (isOfType) {
success = true;
/**
* Create a new branch of the tree, and continue
* resolution from the newly created node.
*/
context.createBranch(unifier);
}
}
}
}
if (!success) {
context.fail();
}
return success;
}
final private static String[] relOpArray = {
"<", "<=", ">", ">=", "!="
};
final private static List relOpList = Arrays.asList(relOpArray);
protected void resolveCondition(
final Context context,
final Condition term)
throws ResolutionException, NotGroundException {
boolean result = false;
String relation = term.getRelation();
List args = term.getArg();
if ("=".equals(relation)) {
List vals1 = exprEval.eval(context, (Expression) args.get(0));
List vals2 = exprEval.eval(context, (Expression) args.get(1));
// TODO need to check handling of all the possible cases
// Eg X = Y, X = 3, 3 = X, 2 = 3, 3 = 3,
// (Z = 1, X = Z), (Z = 1, 2 = Z), (X = Y, Z = X), etc
for (final Iterator itr1 = vals1.iterator(); itr1.hasNext(); ) {
Object val1 = itr1.next();
for (final Iterator itr2 = vals2.iterator(); itr2.hasNext(); ) {
Object val2 = itr2.next();
Binding unifier = Binding.createBinding(val1, val2);
if (null != unifier) {
result = true;
context.createBranch(unifier);
}
}
}
} else if (relOpList.contains(relation)) {
Collection vals1 = exprEval.eval(context, (Expression) args.get(0));
Collection vals2 = exprEval.eval(context, (Expression) args.get(1));
for (Iterator itr1 = vals1.iterator(); itr1.hasNext(); ) {
Object val1 = itr1.next();
// System.err.println("** " + val1);
for (Iterator itr2 = vals2.iterator(); itr2.hasNext(); ) {
Object val2 = itr2.next();
// System.err.println("**** " + val2);
if (val1 instanceof WrappedVar) {
context.delay("Unbound Var, " + val1 + ", not allowed in Condition.");
} else if (val2 instanceof WrappedVar) {
context.delay("Unbound Var, " + val2 + ", not allowed in Condition.");
} else {
Binding unifier = new Binding();
if (val1 instanceof BindingPair) {
unifier.composeRight((BindingPair) val1);
val1 = ((BindingPair) val1).getValue();
}
if (val2 instanceof BindingPair) {
unifier.composeRight((BindingPair) val2);
val2 = ((BindingPair) val2).getValue();
}
if (compare(context, relation, val1, val2)) {
result = true;
context.createBranch(unifier);
// } else {
// no match - result unchanged
}
}
}
}
} else if (relation.equals("boolean")) {
Collection vals = exprEval.eval(context, (Expression) args.get(0));
List bindings = new ArrayList();
for (Iterator itr = vals.iterator(); itr.hasNext(); ) {
Object val = itr.next();
if (val instanceof BindingPair) {
Object bVal = ((BindingPair) val).getValue();
if (Boolean.TRUE.equals(bVal)) {
result = true;
bindings.add(val);
} else if (Boolean.FALSE.equals(bVal)) {
// do nothing
} else if (bVal instanceof WrappedVar) {
context.delay("Unbound Var, " + bVal + ", not allowed in Condition.");
} else {
context.error("Condition did not reference a boolean valued Expression.");
}
}
if (Boolean.TRUE.equals(val)) {
result = true;
} else if (Boolean.FALSE.equals(val)) {
// do nothing
} else if (val instanceof WrappedVar) {
context.delay("Unbound Var, " + val + ", not allowed in Condition.");
} else {
context.error("Condition did not reference a boolean valued Expression.");
}
}
// This is outside the loop since there's no point in creating
// multiple branches for the same (new) goal and empty Binding.
if (result) {
if (bindings.size() > 0) {
for (Iterator itr = bindings.iterator(); itr.hasNext(); ) {
context.createBranch((Binding) itr.next());
}
} else {
context.createBranch();
}
}
} else {
context.error("Unknown relation '" + relation + "' in Condition");
}
if (!result) {
context.fail();
}
}
private boolean compare(Context context, String relation, Object val1, Object val2)
throws ResolutionException {
long cmp;
if (val1 instanceof Number && val2 instanceof Number) {
if (val1 instanceof Float || val1 instanceof Double ||
val2 instanceof Float || val2 instanceof Double) {
double dval1 = ((Number) val1).doubleValue();
double dval2 = ((Number) val2).doubleValue();
cmp = (long) (dval1 - dval2);
} else if (val1 instanceof BigInteger || val1 instanceof BigDecimal ||
val2 instanceof BigInteger || val2 instanceof BigDecimal) {
try {
cmp = ((Comparable) val1).compareTo(val2);
} catch (ClassCastException e) {
context.error(val1 + " and " + val2 + " are not comparable.", e);
cmp = 0; // notreached
}
} else {
long lval1 = ((Number) val1).longValue();
long lval2 = ((Number) val2).longValue();
cmp = lval1 - lval2;
}
} else if (val1 instanceof Comparable) {
try {
cmp = ((Comparable) val1).compareTo(val2);
} catch (ClassCastException e) {
context.error(val1 + " and " + val2 + " are not comparable.", e);
cmp = 0; // notreached
}
} else if ("!=".equals(relation)) {
return !val1.equals(val2);
} else {
context.error(val1 + " and " + val2 + " are not comparable.");
cmp = 0; // notreached
}
if (cmp < 0 && relation.charAt(0) == '<') { // "<".equals(relation) || "<=".equals(relation)
return true;
} else if (cmp > 0 && relation.charAt(0) == '>') { // ">".equals(relation) || ">=".equals(relation)
return true;
} else if (cmp == 0 && ("<=".equals(relation) || ">=".equals(relation))) {
return true;
} else if (cmp != 0 && "!=".equals(relation)) {
return true;
} else {
return false;
}
}
protected void resolveNotTerm(
final Context context,
final NotTerm literal)
throws ResolutionException, NotGroundException {
// Ensure that all non-local variables are already ground
// (WrappedVars are handled by the Expander)
for (final Iterator itr = literal.getNonLocalVars().iterator(); itr.hasNext(); ) {
Var var = (Var) itr.next();
Object value = context.lookup(var);
if (null == value) {
context.delay("Non-local variable " + var + " is not bound.");
}
}
final Function2 f = new Function2() {
public Object call(Context context, Binding unifier, Object[] params) throws ResolutionException {
evalNegatedGoal(context, unifier, new ArrayList(literal.getTerm()));
return null;
}
};
new VarExpander(context, literal.getNonLocalVars(), f, context.node.getBindings());
}
/**
* @param context
* @param unifier
* @param negGoal
* @return
* @throws ResolutionException
*/
private void evalNegatedGoal(final Context context, final Binding unifier, final Collection negGoal)
throws ResolutionException {
// cannot pass node as context here or delayed terms will get pushed into the "NOT"
// leading to possible spurious flounderings -- see also resolveIfTerm
final Tree newTree = context.createTree(negGoal, unifier, true, true);
// if (ruleEval.INCREMENTAL) {
newTree.addTreeListener(new TreeListener() {
public void solution(Binding answer) {
}
public void completed(Tree theTree) {
if (theTree.isSuccess()) {
newTree.removeTreeListener(this);
context.fail();
} else {
// Negation tree finitely failed, regard as true.
//
context.createBranch(new Binding(unifier));
}
}
public void floundered(Tree theTree) {
}
});
// } else {
// // Any success nodes in the negation tree?
// //
// ruleEval.resolveNode(newTree);
// if (newTree.isSuccess()) {
// context.fail();
// } else {
// // Negation tree finitely failed, regard as true.
// //
// context.createBranch(new Binding(unifier));
// }
// }
}
protected void resolveOrTerm(
final Context context,
final OrTerm literal)
throws ResolutionException {
/**
* Create a node for each disjunct, distributing them into
* the remaining conjuncts of the goal.
*/
Collection terms = literal.getTerm();
if (null == terms || terms.isEmpty()) {
// context.error("Malformed (empty) OrTerm");
context.fail();
}
for (Iterator itr = terms.iterator(); itr.hasNext(); ) {
context.createBranch((Term) itr.next());
}
}
protected void resolveMofOrder(final Context context, final MofOrder term)
throws ResolutionException, NotGroundException {
List instances = exprEval.eval(context, term.getInstance());
List features = exprEval.eval(context, term.getFeature());
List lesserObjects = exprEval.eval(context, term.getLesser());
List greaterObjects = exprEval.eval(context, term.getGreater());
for (Iterator iItr = instances.iterator(); iItr.hasNext(); ) {
Object instance = iItr.next();
if (instance instanceof WrappedVar) {
context.delay("Unsupported mode (unbound '" + term.getInstance() + "') for MofOrder: " + term);
}
for (Iterator fItr = features.iterator(); fItr.hasNext(); ) {
Object featureRef = fItr.next();
if (featureRef instanceof WrappedVar) {
context.delay("Unsupported mode (unbound '" + term.getFeature() + "') for MofOrder: " + term);
} else if (featureRef instanceof EStructuralFeature) {
featureRef = ((EStructuralFeature) featureRef).getName();
}
Object values = context.fetchFeature((String) featureRef, instance);
if (!(values instanceof List)) {
context.error("The feature " + featureRef + " of " + instance + " did not return an ordered collection.");
}
List valueList = (List) values;
for (Iterator lItr = lesserObjects.iterator(); lItr.hasNext(); ) {
Object lesser = lItr.next();
if (lesser instanceof WrappedVar) {
for (int i = 0; i < valueList.size(); i++) {
Binding unifier = Binding.bindWrappedVar(null, (WrappedVar) lesser, valueList.get(i));
processGreaterObjects(context, unifier, greaterObjects, valueList, i);
}
} else {
int index = valueList.indexOf(lesser);
processGreaterObjects(context, null, greaterObjects, valueList, index);
}
}
}
}
}
private void processGreaterObjects(final Context context, final Binding unifier,
final List greaterObjects, final List valueList, final int lindex)
throws ResolutionException {
for (Iterator gItr = greaterObjects.iterator(); gItr.hasNext(); ) {
Object greater = gItr.next();
if (greater instanceof WrappedVar) {
final WrappedVar wrappedVar = (WrappedVar) greater;
for (int i = lindex + 1; i < valueList.size(); i++) {
Object val = valueList.get(i);
Binding unifier2 = Binding.bindWrappedVar(new Binding(unifier), wrappedVar, val);
if (null != unifier2) {
context.createBranch(unifier2);
}
}
} else if (lindex < valueList.indexOf(greater)) {
context.createBranch(unifier);
}
}
}
}