/*
* 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
*
* Created on 13/11/2003
*
*
*/
package tefkat.engine;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import tefkat.model.*;
import tefkat.model.internal.IntMap;
import tefkat.model.internal.ModelUtils;
/**
* @author steel, lawley
*
*/
abstract class AbstractResolver {
class VarExpander {
final private Context context;
final private List vars;
final private Function2 function;
VarExpander(Context context, List vars, Function2 function, Binding unifier)
throws NotGroundException, ResolutionException {
this.context = context;
this.vars = vars;
this.function = function;
expandVars(0, unifier);
}
private void expandVars(final int idx, final Binding unifier)
throws NotGroundException, ResolutionException {
if (idx == vars.size()) {
// reached end of list of vars, now do the work
function.call(context, unifier, null);
return;
}
Var var = (Var) vars.get(idx);
Object value = unifier.lookup(var);
if (value instanceof WrappedVar) {
List objs = exprEval.expand(context, (WrappedVar) value);
for (final Iterator itr = objs.iterator(); itr.hasNext(); ) {
Object obj = itr.next();
Binding unifier2 = new Binding(unifier);
unifier2.add(var, obj);
expandVars(idx + 1, unifier2);
}
} else {
expandVars(idx + 1, unifier);
}
}
}
private abstract class PatternCall extends Function2 {
private final List vars;
PatternCall(List vars) {
this.vars = vars;
}
/**
* @param binding a set of variable bindings that needs to be propagated to the answer Binding
* @param actuals actual values or WrappedVaes for the pDefVars for a call
*/
public Object call(Context context, Binding binding, Object[] actuals) throws ResolutionException {
Binding parameterContext = new Binding();
Binding callContext = new Binding(binding);
// System.err.println(pDefn.getName());
// System.err.println(" C: " + context);
// System.err.println(" P: " + Arrays.asList(actuals));
// Example calls to p(X,Y,Z) that we must handle:
// 1. Foo A AND p(A, A, A) -> {}, [A/Foo, A/Foo, A/Foo]
// 2. Foo A AND p(A, A.a, A.b) -> {A -> 1:Foo}, [1:Foo, 2, 3]
//
// 1. varContext = {A -> W(X/Foo)}
// parameterContext = {X -> W(X/Foo), Y -> W(X/Foo), Z -> W(X/Foo)}
//
// 2. varContext = {A -> 1:Foo}
// parameterContext = {X -> 1:Foo, Y -> 2, Z -> 3}
//
for (int i = 0; i < actuals.length; i++) {
Var pVar = (Var) vars.get(i);
Object varValue;
if (actuals[i] instanceof WrappedVar) {
WrappedVar wrappedActual = (WrappedVar) actuals[i];
Var var = wrappedActual.getVar();
varValue = callContext.lookup(var);
if (null == varValue) {
WrappedVar wvar = new WrappedVar(pVar);
wvar.setExtent(wrappedActual.getExtent());
wvar.setType(wrappedActual.getType(), wrappedActual.isExact());
callContext.add(var, wvar);
varValue = wvar;
}
} else {
varValue = actuals[i];
}
if (null != varValue) {
parameterContext.add(pVar, varValue);
}
// System.err.println(" " + i + " " + pVar + " = " + varValue);
}
// System.err.println(" V: " + varContext);
invoke(callContext, parameterContext);
return Collections.EMPTY_LIST;
}
abstract void invoke(Binding callContext, Binding parameterContext) throws ResolutionException;
}
final IntMap elapsedTime = new IntMap();
final IntMap callCount = new IntMap();
final protected RuleEvaluator ruleEval;
protected Evaluator exprEval;
/**
*
*/
protected AbstractResolver(RuleEvaluator evaluator) {
ruleEval = evaluator;
exprEval = evaluator.getEvaluator();
}
protected Map getNameMap() {
return ruleEval.nameMap;
}
/**
* @param tree
* @param node
* @param goal
* @return
* @throws ResolutionException
*/
protected void doResolveNode(final Context context, final Term literal)
throws ResolutionException, NotGroundException {
EClass idc = literal.eClass();
int id = idc.getClassifierID();
long start = System.currentTimeMillis();
// Grow the tree according to the type of literal.
// A predicate is searched for in the rule database.
// A conjunction is flattened into the enclosing goal conjunction.
// A negation spawns a new tree
// A disjunction has its disjunctions distributed into subtrees
//
switch (id) {
case TefkatPackage.MOF_INSTANCE:
resolveMofInstance(context, (MofInstance) literal);
break;
case TefkatPackage.TRACKING_USE:
resolveTrackingUse(context, (TrackingUse) literal);
break;
case TefkatPackage.PATTERN_USE:
resolvePatternUse(context, (PatternUse) literal);
break;
case TefkatPackage.NOT_TERM:
resolveNotTerm(context, (NotTerm) literal);
break;
case TefkatPackage.OR_TERM:
resolveOrTerm(context, (OrTerm) literal);
break;
case TefkatPackage.AND_TERM:
resolveAndTerm(context, (AndTerm) literal);
break;
case TefkatPackage.CONDITION:
resolveCondition(context, (Condition) literal);
break;
case TefkatPackage.IF_TERM:
resolveIfTerm(context, (IfTerm) literal);
break;
case TefkatPackage.MOF_ORDER:
resolveMofOrder(context, (MofOrder) literal);
break;
default:
context.error("Unrecognised term type: " + literal);
}
final long end = System.currentTimeMillis();
int total = elapsedTime.get(idc);
total += (end - start);
elapsedTime.put(idc, total);
int count = callCount.get(idc);
count++;
callCount.put(idc, count);
}
/**
* @param tree
* @param node
* @param order
*/
protected abstract void resolveMofOrder(Context context, MofOrder term)
throws ResolutionException, NotGroundException;
/**
* @param tree
* @param node
* @param literal
*/
protected abstract void resolveCondition(Context context, Condition literal)
throws ResolutionException, NotGroundException;
/**
* @param tree
* @param node
* @param literal
*/
protected abstract void resolveTrackingUse(Context context,
TrackingUse literal) throws ResolutionException, NotGroundException;
/**
* @param tree
* @param node
* @param literal
* @return
*/
protected abstract boolean resolveMofInstance(Context context,
MofInstance literal) throws ResolutionException, NotGroundException;
/**
* @param tree
* @param node
* @param literal
*/
protected abstract void resolveOrTerm(Context context,
OrTerm literal) throws ResolutionException ;
/**
* @param tree
* @param node
* @param literal
* @return
*/
protected abstract void resolveNotTerm(Context context,
NotTerm literal) throws ResolutionException, NotGroundException;
/**
* Resolve the PatternDefn passing in a set of bindings for the vars.
* Need to do this on a new tree so that a fresh set of variables is used
* in case there is recursion.
*
* @param tree
* @param node
* @param literal
* @return
*
* TODO Cache PatternUse calls to trap infinite recursion.
*/
protected void resolvePatternUse(final Context context, final PatternUse literal)
throws ResolutionException, NotGroundException {
final PatternDefn pDefn = literal.getDefn();
final List args = literal.getArg();
if (null == pDefn) { // TODO fix this println hack
String mesg = "";
for (int i = 0; i < args.size(); i++) {
String str;
try {
List values = exprEval.eval(context, (Expression) args.get(i));
str = "[";
for (Iterator itr = values.iterator(); itr.hasNext(); ) {
Object o = itr.next();
str += (o instanceof BindingPair ? ((BindingPair) o).getValue() : o);
if (itr.hasNext()) {
str += ", ";
}
}
str += "]";
} catch (NotGroundException e) {
str = "(" + args.get(i) + " not sufficiently ground)";
}
mesg += (i > 0 ? " " : "") + str;
}
ruleEval.fireInfo(mesg);
context.createBranch();
return;
}
final List pDefVars = pDefn.getParameterVar();
// Check that the sizes of formals and actuals matches, then add the bound variables, pairwise,
// to the initial binding to be passed to the tree resolution of the pattern.
if (args.size() != pDefVars.size()) {
context.error("Invalid arity for pattern "
+ pDefn.getName() + ": " + args.size());
}
// handle arity-zero (no args) pattern
if (pDefVars.size() == 0) {
// if (ruleEval.INCREMENTAL) {
incrementalResolvePatternDefn(context,
literal, pDefVars, args, pDefn, null, context.tree.getContext());
// } else {
// resolvePatternDefn(context,
// literal, pDefVars, args, pDefn, null, context.tree.getContext());
// }
} else {
// final Binding context = tree.getContext();
Function resolver;
// if (ruleEval.INCREMENTAL) {
resolver = new PatternCall(pDefVars) {
void invoke(Binding callContext, Binding parameterContext) throws ResolutionException {
parameterContext.composeRight(context.tree.getContext());
incrementalResolvePatternDefn(context,
literal, pDefVars, args, pDefn,
callContext, parameterContext);
}
};
// } else {
// resolver = new PatternCall(pDefVars) {
// void invoke(Binding callContext, Binding parameterContext) throws ResolutionException {
// parameterContext.composeRight(context.tree.getContext());
// resolvePatternDefn(context,
// literal, pDefVars, args, pDefn,
// callContext, parameterContext);
// }
// };
// }
exprEval.evalAll(context, context.node.getBindings(), args, resolver);
}
}
private void incrementalResolvePatternDefn(final Context context, final PatternUse literal,
final List pDefVars, final List args, final PatternDefn pDefn,
final Binding callContext, final Binding parameterContext)
throws ResolutionException {
Tree resultTree = context.getResultTree(pDefn.getTerm(), parameterContext);
if (!resultTree.isCompleted()) {
// Register listener for any new solutions
resultTree.addTreeListener(new TreeListener() {
public void solution(Binding answer) throws ResolutionException {
Binding unifier = createOutputBinding(context, callContext, pDefVars, args, answer);
context.createBranch(unifier);
}
public void completed(Tree theTree) {
if (!theTree.isSuccess()) {
context.fail();
}
}
public void floundered(Tree theTree) {
}
});
}
if (resultTree.isSuccess()) {
// Process any existing solutions
for (Iterator itr = resultTree.getAnswers().iterator(); itr.hasNext();) {
Binding answer = (Binding) itr.next();
Binding unifier = createOutputBinding(context, callContext, pDefVars, args, answer);
context.createBranch(unifier);
}
}
}
private void resolvePatternDefn(final Context context, final PatternUse literal,
final List pDefVars, final List args, final PatternDefn pDefn,
final Binding callContext, final Binding parameterContext)
throws ResolutionException {
try {
ruleEval.pushPattern(pDefn);
Tree resultTree = context.getResultTree(pDefn.getTerm(), parameterContext);
if (resultTree.isSuccess()) {
for (Iterator itr = resultTree.getAnswers().iterator(); itr.hasNext(); ) {
Binding currentSolution = (Binding) itr.next();
Binding unifier = createOutputBinding(context, callContext, pDefVars, args, currentSolution);
context.createBranch(unifier);
}
} else {
context.fail();
}
} finally {
ruleEval.popPattern();
}
}
static private Binding createOutputBinding(final Context context, final Binding callContext, final List formals, final List actuals, Binding solution) throws ResolutionException {
Binding unifier = new Binding(callContext);
Map linkMap = new HashMap();
// System.err.println(" s " + currentSolution); // TODO delete
for (int j = 0; j < actuals.size(); j++) {
Expression argExpr = (Expression) actuals.get(j);
if (argExpr instanceof VarUse) {
final Var var = ((VarUse) argExpr).getVar();
final Object val = context.lookup(var);
Object outVal = solution.lookup((Var) formals.get(j));
// Handle case where unbound input params are unified
// but not grounded within the pattern.
// For example: p(A, B) call to
// PATTERN p(X, Y) WHERE X = Y;
// should bind A to B, but not to X or Y.
//
if (outVal instanceof WrappedVar) {
if (linkMap.containsKey(outVal)) {
outVal = linkMap.get(outVal);
} else {
WrappedVar wVar = new WrappedVar(var);
wVar.setExtent(((WrappedVar) outVal).getExtent());
wVar.setType(((WrappedVar) outVal).getType(),
((WrappedVar) outVal).isExact());
linkMap.put(outVal, wVar);
outVal = wVar;
}
}
if (null == val) {
// We only add a new binding if argExpr is not already
// bound, otherwise we end up with multiple identical bindings
// for the same Var.
unifier.add(var, outVal);
} else if (val instanceof WrappedVar) {
Binding.bindWrappedVar(unifier, (WrappedVar) val, outVal);
} else if (!val.equals(outVal)) {
// System.err.println("Arg:\t" + argExpr);
// System.err.println("actual:\t" + val);
// System.err.println("formal:\t" + formals.get(j));
// System.err.println("result:\t" + outVal);
context.error("conflicting pattern arg and result: " + val + "\t" + outVal);
}
}
}
return unifier;
}
protected void resolveAndTerm(final Context context, final AndTerm literal)
throws ResolutionException {
/**
* Add the conjuncts of this literal into the goal.
*/
Collection terms = literal.getTerm();
if (null == terms) {
context.error("Malformed (null) AndTerm contents");
}
context.createBranch(terms);
}
static protected EStructuralFeature getFeature(Context context, EClass klass, String featureName)
throws ResolutionException {
try {
return ModelUtils.getFeature(klass, featureName);
} catch (RuntimeException e) {
context.error(e.getMessage(), e);
return null; // notreached
}
}
protected void resolveIfTerm(Context context, final IfTerm literal)
throws ResolutionException, NotGroundException {
// Ensure that all non-local variables are already ground
// (WrappedVars are handled by the Expander)
for (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 {
evalIfGoal(context, unifier, literal.getTerm());
return null;
}
};
new VarExpander(context, literal.getNonLocalVars(), f, context.getBindings());
}
private void evalIfGoal(final Context context, final Binding unifier, final List terms)
throws ResolutionException {
List condGoal = new ArrayList();
condGoal.add(terms.get(0));
// cannot pass node as context here or delayed terms will get pushed into the "NOT"
// leading to possible spurious flounderings -- see also evalNegatedGoal
Tree newTree = context.createTree(condGoal, unifier, false, true);
// if (ruleEval.INCREMENTAL) {
newTree.addTreeListener(new TreeListener() {
public void solution(Binding answer) {
// THEN
Binding sContext = new Binding(answer);
context.createBranch((Term) terms.get(1), sContext);
}
public void completed(Tree theTree) {
if (!theTree.isSuccess()) {
// ELSE
context.createBranch((Term) terms.get(2), new Binding(unifier));
}
}
public void floundered(Tree theTree) {
// nothing to do in this case
}
});
// } else {
// ruleEval.resolveNode(newTree);
//
// if (newTree.isSuccess()) {
// // THEN
// for (final Iterator itr = newTree.getAnswers().iterator(); itr.hasNext(); ) {
// Binding answer = (Binding) itr.next();
// Binding sContext = new Binding(answer);
// context.createBranch((Term) terms.get(1), sContext);
// }
// } else {
// // ELSE
// context.createBranch((Term) terms.get(2), new Binding(unifier));
// }
// }
}
}