/* * 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 * Anna Gerber * * */ package tefkat.engine; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import tefkat.model.Extent; import tefkat.model.Injection; import tefkat.model.MofInstance; import tefkat.model.NotTerm; import tefkat.model.PatternDefn; import tefkat.model.Query; import tefkat.model.TRule; import tefkat.model.TefkatException; import tefkat.model.Term; import tefkat.model.Transformation; import tefkat.model.Var; import tefkat.model.VarScope; import tefkat.model.internal.ModelUtils; public class RuleEvaluator { /** * If ONE_TREE is set to true, then we cannot separate solutions on a per-rule basis :-( */ private static final boolean ONE_TREE = false; private final Evaluator exprEval; final SourceResolver srcResolver; final TargetResolver tgtResolver; private final Extent trackingExtent; private final Binding _context; private final Map<TRule, Collection<Binding>> evalCache; private final Map<Term, Map> patternCache; protected Map nameMap; protected List listeners; private volatile boolean isInterrupted = false; private volatile boolean stepMode = false; private volatile int returnMode = 0; private volatile int step = 0; private int depth = 0; // // Used to manage simple coarse-grain fix-point evaluation // private TRule currentRule; final private Map<EClass, List<TrackingCallback>> trackingQueryMap = new HashMap<EClass, List<TrackingCallback>>(); // final private Set trackingUpdateSet = new HashSet(); final private Set<Term> breakpoints = new HashSet<Term>(); final private List<Tree> unresolvedTrees = new ArrayList<Tree>(); final Injections injections = new Injections(); /** * */ RuleEvaluator(Binding context, Extent trackingExtent, Map nameMap, List listeners) { // TODO check for null target model, either here or in the engine this.trackingExtent = trackingExtent; this.listeners = listeners; this._context = context; this.nameMap = nameMap; if (null != trackingExtent) { injections.loadTrace(trackingExtent); } exprEval = new Evaluator(this); srcResolver = new SourceResolver(this); tgtResolver = new TargetResolver(this, null, listeners); evalCache = new HashMap<TRule, Collection<Binding>>(); patternCache = new HashMap<Term, Map>(); } // Tree getUnresolvedTree() { // if (unresolvedTrees.size() > 0) { // return (Tree) unresolvedTrees.remove(0); // } else { // return null; // } // } void addUnresolvedTree(Tree tree) { // insert the tree at the start of the list // This means we perform a depth-first traversal which will // make debugging more intuitive. unresolvedTrees.add(0, tree); fireTreeAdded(tree); } void removeUnresolvedTree(Tree tree) { unresolvedTrees.remove(tree); fireTreeRemoved(tree); } protected void addBreakpoint(Term t) { breakpoints.add(t); } protected void removeBreakpoint(Term t) { breakpoints.remove(t); } protected boolean getInterrupted() { return isInterrupted; } protected void setInterrupted(boolean state) { isInterrupted = state; } /** * @return */ public Evaluator getEvaluator() { return exprEval; } public Collection<Binding> runQuery(final Query query) throws TefkatException { try { final Collection<Binding> answers = new ArrayList<Binding>(); final Tree tree = new Tree(null, null, _context, trackingExtent, false); tree.setLevel(0); addUnresolvedTree(tree); final Collection<Term> goal = new ArrayList<Term>(); goal.add(query.getTerm()); final Binding context = new Binding(_context); tree.createBranch(null, context, goal ); tree.addTreeListener(new TreeListener() { final List<Var> vars = query.getParameterVar(); public void solution(Binding answer) throws ResolutionException { final Binding result = new Binding(); for (Var var: vars) { Object val = answer.lookup(var); if (null != val) { result.add(var, val); } else { result.add(var, var); } } answers.add(result); } public void completed(Tree theTree) { // if (theTree.isSuccess()) { // fireInfo("Query: " + query.getName() + " completed."); // } else { // fireWarning("Query: " + query.getName() + " matched nothing."); // } } public void floundered(Tree theTree) { // floundering of top-level tree is handled in the // resolve/resolveNode loop } }); while (unresolvedTrees.size() > 0) { resolve(); // System.err.println("completing trees..."); int minLevel = Integer.MAX_VALUE; List<Tree> done = new ArrayList<Tree>(); for (int j = 0; j < unresolvedTrees.size(); j++) { Tree cTree = unresolvedTrees.get(j); if (cTree.getLevel() < minLevel) { done.clear(); done.add(cTree); minLevel = cTree.getLevel(); } else if (cTree.getLevel() == minLevel) { done.add(cTree); } } if (done.size() == 0) { // I don't think we should ever reach here... throw new TefkatException("Internal Error. Please file a bug report."); } else { // System.err.println("Min level: " + minLevel); for (Iterator<Tree> itr = done.iterator(); itr.hasNext(); ) { Tree cTree = itr.next(); // System.err.println(cTree + " " + cTree.isNegation() + "\t" + cTree.getLevel()); removeUnresolvedTree(cTree); cTree.completed(); } } // System.err.println(" #trees = " + unresolvedTrees.size()); } if (tree.isSuccess()) { return answers; } else { return null; } } catch (ResolutionException e) { fireError(e); if (stepMode) { breakpoint(e.getNode().selectedLiteral()); } throw e; } catch (RuntimeException e) { fireError(e); if (stepMode) { // FIXME - this stuff doesn't work as intended pause(); waitStep(); } throw new ResolutionException(null, "Internal error", e); } } public void runTransformation(Transformation transformation, boolean force) throws TefkatException { // if (INCREMENTAL) { runIncrementalTransformation(transformation, force); // } else { // runFixpointTransformation(transformation, force); // } } @SuppressWarnings("unchecked") // void runFixpointTransformation(Transformation transformation, boolean force) // throws TefkatException { // // try { // buildMaps(transformation); // // fireInfo("Constructing stratification..."); // List[] strata = transformation.getStrata(); // fireInfo("... " + strata.length + " levels."); // // for (int i = 0; i < strata.length; i++) { // fireInfo("Stratum " + i + " : " + formatStrata(strata[i])); // fixpoint(strata[i], force); // } // // topologicalSort(); // } catch (ResolutionException e) { // fireError(e); // if (stepMode) { // breakpoint(e.getNode().selectedLiteral()); // } // throw e; // } catch (RuntimeException e) { // fireError(e); // if (stepMode) { // // FIXME - this stuff doesn't work as intended // pause(); // waitStep(); // } // throw e; // } // } void runIncrementalTransformation(Transformation transformation, boolean force) throws TefkatException { try { buildMaps(transformation); fireInfo("Constructing stratification..."); List<VarScope>[] strata = transformation.getStrata(); fireInfo("... " + strata.length + " levels."); for (int level = 0; level < strata.length; level++) { fireInfo("Stratum " + level + " : " + formatStrata(strata[level])); // Currently, we use a single Tree per stratum which means // that it's really a forest with one root Node per TRule // Tree tree; if (ONE_TREE) { tree = new Tree(null, null, _context, trackingExtent, false); tree.setLevel(level); addUnresolvedTree(tree); } for (final Iterator itr = strata[level].iterator(); itr.hasNext(); ) { Object scope = itr.next(); if (scope instanceof TRule) { TRule tRule = (TRule) scope; if (!tRule.isAbstract()) { if (!ONE_TREE) { tree = new Tree(null, null, _context, trackingExtent, false); tree.setLevel(level); addUnresolvedTree(tree); } incrementalEvaluate(tRule, tree); } } } while (unresolvedTrees.size() > 0) { try { resolve(); // System.err.println("completing trees..."); int minLevel = Integer.MAX_VALUE; List<Tree> done = new ArrayList<Tree>(); for (int j = 0; j < unresolvedTrees.size(); j++) { Tree cTree = unresolvedTrees.get(j); if (cTree.getLevel() < minLevel) { done.clear(); done.add(cTree); minLevel = cTree.getLevel(); } else if (cTree.getLevel() == minLevel) { done.add(cTree); } } if (done.size() == 0) { // I don't think we should ever reach here... throw new TefkatException("Internal Error. Please file a bug report."); } else { // System.err.println("Min level: " + minLevel); for (Iterator<Tree> itr = done.iterator(); itr.hasNext(); ) { Tree cTree = itr.next(); // System.err.println(cTree + " " + cTree.isNegation() + "\t" + cTree.getLevel()); removeUnresolvedTree(cTree); cTree.completed(); } } // System.err.println(" #trees = " + unresolvedTrees.size()); } catch (ResolutionException e) { if (force) { fireError(e); } else { throw e; } } } } topologicalSort(); } catch (ResolutionException e) { fireError(e); if (stepMode) { breakpoint(e.getNode().selectedLiteral()); } throw e; } catch (RuntimeException e) { fireError(e); if (stepMode) { // FIXME - this stuff doesn't work as intended pause(); waitStep(); } throw new ResolutionException(null, "Internal error", e); } } private String formatStrata(List<VarScope> strata) { final StringBuilder sb = new StringBuilder(); for (VarScope s: strata) { if (s instanceof TRule && !((TRule) s).isAbstract()) { sb.append(", ").append(s.getName()); } } return sb.length() > 0 ? sb.substring(2) : ""; } // private void fixpoint(List tRules, boolean force) throws ResolutionException { // trackingQueryMap.clear(); // while (tRules.size() > 0) { // trackingUpdateSet.clear(); // // for (TRule tRule = selectTRule(tRules); null != tRule; tRule = selectTRule(tRules)) { // tRules.remove(tRule); // if (!tRule.isAbstract()) { // if (force) { // try { // evaluate(tRule); // } catch (ResolutionException e) { // fireError(e); // } // } else { // evaluate(tRule); // } // } // } // // for (final Iterator itr = trackingUpdateSet.iterator(); itr.hasNext(); ) { // Object key = itr.next(); // key is an updated tracking (E)Class // Set[] scopes = (Set []) trackingQueryMap.get(key); // if (null != scopes) { // fireInfo("Will re-evaluate rule(s): " + scopes[0]); // tRules.addAll(scopes[0]); // for (final Iterator ruleItr = scopes[0].iterator(); ruleItr.hasNext(); ) { // TRule rule = (TRule) ruleItr.next(); // evalCache.remove(rule); // } // for (final Iterator patternItr = scopes[1].iterator(); patternItr.hasNext(); ) { // PatternDefn pDefn = (PatternDefn) patternItr.next(); // if (patternCache.containsKey(pDefn)) { // fireInfo("\t clearing cache of " + pDefn); // patternCache.remove(pDefn); // } // } // } // } // } // } // private void evaluate(TRule trule) throws ResolutionException { // // Only evaluate rules once // if (evalCache.containsKey(trule)) { // fireInfo("Using cached results for " + trule.getName()); // fireEvaluateRule(trule, _context, true); // return; // } // fireEvaluateRule(trule, _context, false); // doEvaluate(trule, _context); // } @SuppressWarnings("unchecked") private void incrementalEvaluate(final TRule trule, final Tree tree) throws ResolutionException { // Only evaluate rules once if (evalCache.containsKey(trule)) { // FIXME this should never happen fireInfo("Using cached results for " + trule.getName()); fireEvaluateRule(trule, _context, true); return; } fireEvaluateRule(trule, _context, false); Collection<Term> goal = trule.getGoal(); Collection<Binding> ruleContexts = generateContexts(trule, _context); // FIXME - no rule caching any more final Collection<Binding> truleSolutions = new HashSet<Binding>(); if (ruleContexts.size() > 0) { // IMPORTANT!!! took this out of the enclosing loop below! // only one listener required... // tree.addTreeListener(new TreeListener() { public void solution(Binding answer) throws ResolutionException { // nothing to do in this case } public void completed(Tree theTree) { if (theTree.isSuccess()) { fireInfo("TRule: " + trule.getName() + " completed."); } else { fireWarning("TRule: " + trule.getName() + " matched nothing."); } } public void floundered(Tree theTree) { // floundering of top-level tree is handled in the // resolve/resolveNode loop } }); for (Iterator<Binding> itr = ruleContexts.iterator(); itr.hasNext();) { final Binding ruleContext = itr.next(); tree.createBranch(null, ruleContext, goal); } } // record the results for later use by extending TRules // FIXME truleSolutions never gets populated... evalCache.put(trule, truleSolutions); } final protected void pause() { synchronized (unresolvedTrees) { stepMode = true; } } final protected void step() { synchronized (unresolvedTrees) { step++; unresolvedTrees.notifyAll(); } } final protected void stepReturn() { synchronized (unresolvedTrees) { returnMode = depth; resume(); } } final protected void resume() { synchronized (unresolvedTrees) { stepMode = false; unresolvedTrees.notifyAll(); } } final private void breakpoint(Term t) { synchronized (unresolvedTrees) { pause(); fireBreakpoint(t); try { unresolvedTrees.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } final private void waitStep() { synchronized (unresolvedTrees) { while (stepMode && step < 1) { try { fireSuspend(); if (step < 1) { unresolvedTrees.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } if (stepMode && step > 0) { step--; } fireResume(); } } protected void fireBreakpoint(Term t) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).breakpoint(t); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireSuspend() { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).suspended(); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireResume() { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).resumed(); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireInfo(String message) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).info(message); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireWarning(Throwable t) { String message = t.getMessage(); if (null == message) { message = String.valueOf(t); } for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).warning(message); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireWarning(String message) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).warning(message); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireError(Throwable t) { String message = t.getMessage(); if (null == message) { message = String.valueOf(t); } for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { final TefkatListener listener = (TefkatListener) itr.next(); listener.error(message, t); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireError(String message) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).error(message, null); } catch (Throwable e) { e.printStackTrace(); } } } // protected void fireResourceLoaded(Resource res) { // for (Iterator itr = listeners.iterator(); itr.hasNext();) { // try { // ((TefkatListener) itr.next()).resourceLoaded(res); // } catch (Throwable e) { // e.printStackTrace(); // } // } // } private void fireTreeAdded(Tree tree) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { Object listener = itr.next(); if (listener instanceof TefkatListener2) { ((TefkatListener2) listener).treeAdded(tree); } } catch (Throwable e) { e.printStackTrace(); } } } private void fireTreeRemoved(Tree tree) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { Object listener = itr.next(); if (listener instanceof TefkatListener2) { ((TefkatListener2) listener).treeRemoved(tree); } } catch (Throwable e) { e.printStackTrace(); } } } private void fireEnterTree(Tree tree) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).enterTree(tree); } catch (Throwable e) { e.printStackTrace(); } } } private void fireExitTree(Tree tree) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).exitTree(tree); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireEnterTerm(Node node) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).enterTerm(node); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireExitTerm(Node node) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).exitTerm(node); } catch (Throwable e) { e.printStackTrace(); } } } protected void fireDelayTerm(Node node) { for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).delayTerm(node); } catch (Throwable e) { e.printStackTrace(); } } } private void fireEvaluateRule(TRule rule, Binding context, boolean cached) { // currentRule = rule; for (Iterator itr = listeners.iterator(); itr.hasNext();) { try { ((TefkatListener) itr.next()).evaluateRule(rule, context, cached); } catch (Throwable e) { e.printStackTrace(); } } } // private void fireEvaluateSource(TRule rule, Binding context) { // for (Iterator itr = listeners.iterator(); itr.hasNext();) { // ((TefkatListener) itr.next()).evaluateSource(rule, context); // } // } // // private void fireEvaluateTarget(TRule rule, Binding context) { // for (Iterator itr = listeners.iterator(); itr.hasNext();) { // ((TefkatListener) itr.next()).evaluateTarget(rule, context); // } // } // private void doEvaluate(TRule trule, Binding context) // throws ResolutionException { // Collection truleSolutions = new HashSet(); // // Collection goal = trule.getGoal(); // // // System.out.println(trule.getName() + ": S " + getSourceTerms(trule)); // // System.out.println(trule.getName() + ": O " + // // getOverrideTerms(trule)); // // System.out.println(trule.getName() + ": T " + getTargetTerms(trule)); // // System.out.println(trule.getName() + ": B " + getBinding(bindingMap, // // trule)); // // Collection ruleContexts = generateContexts(trule, context); // // for (Iterator itr = ruleContexts.iterator(); itr.hasNext();) { // Binding ruleContext = (Binding) itr.next(); // // System.out.println(trule.getName() + ": C " + ruleContext); // // Node root = new Node(goal, ruleContext); // Tree tree = new Tree(null, root, context, trackingExtent, false); // // resolveNode(tree); // // if (tree.isSuccess()) { // HashSet vars = new HashSet(trule.getVars()); // vars.addAll(ruleContext.keys()); // Add the extent vars to the // // vars provided to solutions // // truleSolutions.addAll(tree.getAnswers()); // } else { // // System.err.println(tree.getUnresolvedNode()); // // fireInfo("TRule: " + trule.getName() + " matched nothing."); // } // } // // // record the results for later use by extending TRules // evalCache.put(trule, truleSolutions); // } /** * Iterate until no trees with unresolved nodes. * This should be called from within a loop over the unresolvedTrees * that completes all unresolvedTrees that belong to the minimum strata. * * @throws ResolutionException */ void resolve() throws ResolutionException { while (!isInterrupted && unresolvedTrees.size() > 0) { Tree tree = null; Node node = null; for (Iterator<Tree> itr = unresolvedTrees.iterator(); null == node && itr.hasNext(); ) { tree = itr.next(); node = tree.getUnresolvedNode(); } if (null == node) { // No Trees with unresolved nodes break; } try { depth++; fireEnterTree(tree); // Is the goal already a success? // if (node.goal().isEmpty()) { fireEnterTerm(node); if (groundWrappedVars(tree, node)) { if (null == node.getDelayed() || node.getDelayed().isEmpty()) { tree.success(node); } else { // If this is a subtree (created for IFs or PATTERNs/TEMPLATEs) // propagate delay -- only top-level Trees (for TRules) should flounder Node flounder = tree.flounder(node.getDelayReasons()); if (null != flounder) { // Don't continue with this tree - it's obsolete unresolvedTrees.remove(tree); fireDelayTerm(flounder); } else { throw new ResolutionException(node, "Floundered - all terms are delayed: " + formatDelayedNoded(node)); } } } fireExitTerm(node); } else { // Select a literal for node. // Term literal = selectLiteral(tree, node); if (null != literal) { try { fireEnterTerm(node); // if (breakpoints.contains(literal)) { // breakpoint(literal); // } else if (stepMode) { waitStep(); } final Context context = new Context(this, exprEval, tree, node); // Grow the tree according to the type of literal using the // appropriate resolver. // if (literal.isTarget()) { tgtResolver.doResolveNode(context, literal); } else { srcResolver.doResolveNode(context, literal); } fireExitTerm(node); } catch (NotGroundException e) { // fireInfo("delaying: " + literal + " : " + e); if (node.selectedLiteral() != literal) { throw new AssertionError( "Internal Error: inconsistent state, please report this problem to the developers."); } node.delay(e); tree.addUnresolvedNode(node); fireDelayTerm(node); } } } } catch (NotGroundException e) { // TODO Auto-generated catch block // FIXME need to work out what to do here... e.printStackTrace(); } finally { if (returnMode >= depth) { returnMode = 0; pause(); // waitStep(); } if (tree.isCompleted()) { removeUnresolvedTree(tree); } fireExitTree(tree); depth--; } } } /** * Looks for a WrappedVar in the Node's Binding and creates new nodes with bindings to found instances. * If there are no instances to bind to then node fails. * * @param tree * @param node * @return true if no WrappedVars were found * @throws NotGroundException */ private boolean groundWrappedVars(Tree tree, Node node) throws NotGroundException { boolean done = true; //fireInfo("grounding..."); final Context context = new Context(this, exprEval, tree, node); final Binding binding = context.getBindings(); for (final Iterator itr = binding.entrySet().iterator(); done && itr.hasNext(); ) { final Map.Entry entry = (Map.Entry) itr.next(); final Var var = (Var) entry.getKey(); final Object v = entry.getValue(); if (v instanceof WrappedVar) { done = false; final List l = exprEval.expand(context, (WrappedVar) v); //fireInfo(var + " <- #instances: " + l.size()); if (l.size() == 0) { context.fail(); } else { for (final Iterator itr2 = l.iterator(); itr2.hasNext(); ) { final Object o = itr2.next(); Binding unifier = new Binding(); unifier.add(var, o); //fireInfo(" " + var + " = " + o); context.createBranch(unifier); } } } } return done; } private String formatDelayedNoded(final Node node) { final StringBuilder sb = new StringBuilder(); sb.append("[\n"); Collection<NotGroundException> reasons = node.getDelayReasons(); for (final Iterator<NotGroundException> itr = reasons.iterator(); itr.hasNext(); ) { final NotGroundException reason = itr.next(); final Node reasonNode = reason.getNode(); Term term = reasonNode.selectedLiteral(); EObject c = term.eContainer(); while (null != c && !(c instanceof VarScope)) { c = c.eContainer(); } sb.append(" ").append(reason.getMessage()).append(" in ").append(c).append("\n"); } sb.append("]"); // sb.append("[\n"); // Collection<Term> terms = node.getDelayed(); // for (final Iterator<Term> itr = terms.iterator(); itr.hasNext(); ) { // final Term term = itr.next(); // sb.append(" ").append(term).append("\n"); // } // sb.append("]"); // // sb.append(node.getBindings()); return sb.toString(); } /** * Grow the tree from the given node. * * @param node * The node from which to grow the tree. * @param isNegation * True iff the node is in a negation tree. * @return True iff this node or one of its (transitive) children is a * success node. */ // protected void resolveNode(final Tree tree) throws ResolutionException { // // try { // depth++; // fireEnterTree(tree); // // // Iterate over all unresolved nodes until there are none left // // (or the interrupted flag is set) // // // for (Node node = tree.getUnresolvedNode(); // !isInterrupted && !tree.isCompleted() && null != node; // node = tree.getUnresolvedNode()) { // // // Is the goal already a success? // // // if (node.goal().isEmpty()) { // //// if (stepMode) { //// waitStep(); //// } // // fireEnterTerm(node); // // if (null == node.getDelayed() || node.getDelayed().isEmpty()) { // tree.success(node); // } else { // throw new ResolutionException(node, // "Floundered - all terms are delayed: " // + node.getDelayed() + "\t" // + node.getBindings()); // } // // fireExitTerm(node); // } else { // // Select a literal for node. // // // Term literal = selectLiteral(tree, node); // // try { // fireEnterTerm(node); // //// if (breakpoints.contains(literal)) { //// breakpoint(literal); //// } else // if (stepMode) { // waitStep(); // } // // // Grow the tree according to the type of literal using the // // appropriate resolver. // // // if (literal.isTarget()) { // tgtResolver.doResolveNode(new Context(this, exprEval, tree, node), literal); // } else { // srcResolver.doResolveNode(new Context(this, exprEval, tree, node), literal); // } // // fireExitTerm(node); // // } catch (NotGroundException e) { // // fireInfo("delaying: " + literal + " : " + e); // if (node.selectedLiteral() != literal) { // throw new AssertionError( // "Internal Error: inconsistent state, please report this problem to the developers."); // } // node.delay(e); // tree.addUnresolvedNode(node); // fireDelayTerm(node); // } // // } // } // // } finally { // if (returnMode >= depth) { // returnMode = 0; // pause(); // // waitStep(); // } // // fireExitTree(tree); // depth--; // } // } /** * Choose a literal from the goal of the given node. * * @param node * The node containing the goal from which to choose a literal. * @return A chosen literal, or null if there were only delayed source terms but a WrappedVar had bindings * @throws ResolutionException * @throws NotGroundException */ private Term selectLiteral(final Tree tree, final Node node) throws ResolutionException, NotGroundException { Term[] literals = (Term[]) node.goal().toArray(new Term[node.goal().size()]); // Simple selection rule: // + select non-target, non-negation terms first // + select non-target terms next // + select Injections next // + select target MofInstances next // + select anything else (target terms) last // for (int i = 0; i < literals.length; i++) { if (!(literals[i].isTarget() || literals[i] instanceof NotTerm)) { node.selectLiteral(literals[i]); return literals[i]; } } for (int i = 0; i < literals.length; i++) { if (!literals[i].isTarget()) { node.selectLiteral(literals[i]); return literals[i]; } } if (!groundWrappedVars(tree, node)) { return null; } if (null != node.getDelayed() && !node.getDelayed().isEmpty()) { throw new ResolutionException(node, "Flounder: All source terms delayed: " + formatDelayedNoded(node)); } for (int i = 0; i < literals.length; i++) { if (literals[i] instanceof Injection) { node.selectLiteral(literals[i]); return literals[i]; } } for (int i = 0; i < literals.length; i++) { if (literals[i] instanceof MofInstance) { node.selectLiteral(literals[i]); return literals[i]; } } if (literals.length > 0) { node.selectLiteral(literals[0]); return literals[0]; } throw new ResolutionException(node, "Could not select a valid literal from goal: " + node.goal()); } // private String formatBinding(Binding solution) { // String s = ""; // for (Iterator itr = solution.entrySet().iterator(); itr.hasNext();) { // Map.Entry es = (Map.Entry) itr.next(); // Var k = (Var) es.getKey(); // s += "\n\t" + k.getScope().getName() + "." + k.getName() + " -> "; // Object v = es.getValue(); // if (v instanceof Var) { // Var av = (Var) v; // s += av.getScope().getName() + "." + av.getName(); // } else if (v instanceof WrappedVar) { // Var av = ((WrappedVar) v).getVar(); // s += "W(" + av.getScope().getName() + "." + av.getName() + ")"; // } else { // s += v; // } // } // return s; // } // ================================================================================ // Utility methods for constructing suitable goals that account for the // extends and supersedes associations. // /** * Stores a map to the transitive closure of the extends references. */ private Map<TRule, List<TRule>> extRulesMap; /** * Stores a map to the (inverted) supersedes references. */ private Map<TRule, List<TRule>> invertedSupMap; private final Map<TRule, Binding> extendsBindingMap = new HashMap<TRule, Binding>(); /** * Construct variable bindings required for extends relationships. Depends * on buildExtBindings(), buildSupBindings() and itself. * * @param rule * @return * @throws ResolutionException */ @SuppressWarnings("unchecked") private Binding getExtendsBinding(TRule rule) throws ResolutionException { Binding binding = extendsBindingMap.get(rule); if (null == binding) { binding = new Binding(); buildExtBindings(rule.getVars(), binding); buildSupBindings(rule.getVars(), binding); for (Iterator<TRule> itr = rule.getExtended().iterator(); itr.hasNext();) { TRule extRule = itr.next(); binding.composeRight(getExtendsBinding(extRule)); } for (Iterator<TRule> itr = rule.getSuperseded().iterator(); itr.hasNext();) { TRule supRule = itr.next(); binding.composeRight(getExtendsBinding(supRule)); } extendsBindingMap.put(rule, binding); } return binding; } private final Map<TRule, Binding> overrideBindingMap = new HashMap<TRule, Binding>(); /** * Construct variable bindings required for superseding relationships. * Depends on invertedSupMap, sPrimeBinding() and itself. * * @param rule * @return * @throws ResolutionException */ @SuppressWarnings("unchecked") private Binding getOverrideBinding(TRule rule) throws ResolutionException { Binding binding = overrideBindingMap.get(rule); if (null == binding) { binding = new Binding(); List<TRule> supersedingRules = getList(invertedSupMap, rule); for (Iterator<TRule> itr = supersedingRules.iterator(); itr.hasNext(); ) { TRule supersedingRule = itr.next(); binding.composeRight(getExtendsBinding(supersedingRule)); } for (Iterator<TRule> itr = rule.getExtended().iterator(); itr.hasNext(); ) { TRule extRule = itr.next(); binding.composeRight(getOverrideBinding(extRule)); } overrideBindingMap.put(rule, binding); } return binding; } /** * Populate the supMap and extMap fields with the (inverted) supersedes * references and the transitive closure of the extends references. * * @param tr * @throws ResolutionException */ @SuppressWarnings("unchecked") private void buildMaps(Transformation tr) throws ResolutionException { extRulesMap = new HashMap<TRule, List<TRule>>(); invertedSupMap = new HashMap<TRule, List<TRule>>(); for (Iterator itr = tr.getTRule().iterator(); itr.hasNext();) { TRule rule = (TRule) itr.next(); // Compute the TC of the rules that 'rule' extends List<TRule> ext = getList(extRulesMap, rule); buildExtList(rule, ext); // For each rule that this rule supersedes, bulid the inverted // association for (Iterator<TRule> supItr = rule.getSuperseded().iterator(); supItr.hasNext();) { TRule supRule = supItr.next(); List<TRule> sup = getList(invertedSupMap, supRule); sup.add(rule); } } } @SuppressWarnings("unchecked") private void buildExtBindings(List vars, Binding binding) throws ResolutionException { for (Iterator itr = vars.iterator(); itr.hasNext();) { Var var = (Var) itr.next(); for (Iterator<Var> extItr = var.getExtended().iterator(); extItr.hasNext();) { Var extVar = extItr.next(); linkVars(binding, var, extVar); } } } @SuppressWarnings("unchecked") private void buildSupBindings(List vars, Binding binding) throws ResolutionException { for (Iterator itr = vars.iterator(); itr.hasNext();) { Var var = (Var) itr.next(); for (Iterator<Var> supItr = var.getSuperseded().iterator(); supItr.hasNext();) { Var supVar = supItr.next(); linkVars(binding, var, supVar); } } } private Object lookup(Binding binding, Var var) { Object obj = binding.lookup(var); if (null == obj) { return new WrappedVar(var); } return obj; } private void linkVars(Binding binding, Var lhVar, Var rhVar) throws ResolutionException { Object lhObj = lookup(binding, lhVar); Object rhObj = lookup(binding, rhVar); if (lhObj instanceof WrappedVar) { Var var = ((WrappedVar) lhObj).getVar(); binding.add(var, rhObj); } else { binding.add((Var) lhObj, rhObj); } } private List<TRule> getList(Map<TRule, List<TRule>> map, TRule rule) { List<TRule> list = map.get(rule); if (null == list) { list = new ArrayList<TRule>(); map.put(rule, list); } return list; } // private Binding getBinding(Map map, TRule rule) { // Binding binding = (Binding) map.get(rule); // if (null == binding) { // binding = new Binding(); // map.put(rule, binding); // } // return binding; // } @SuppressWarnings("unchecked") private void buildExtList(TRule rule, List<TRule> rules) { rules.addAll(rule.getExtended()); for (Iterator<TRule> itr = rule.getExtended().iterator(); itr.hasNext();) { TRule extRule = itr.next(); buildExtList(extRule, rules); } } // // // ================================================================================ // private TRule selectTRule(List rules) { // while (rules.size() > 0) { // Object scope = rules.get(0); // if (scope instanceof TRule) { // return (TRule) scope; // } else { // // Now that we're doing stratification, rules may include // // PatternDefns so we need to filter them out // rules.remove(0); // } // } // return null; // } private Collection<Binding> generateContexts(TRule trule, Binding context) throws ResolutionException { Collection<Binding> contextSet = new HashSet<Binding>(); Binding ruleContext = new Binding(context); ruleContext.composeRight(getExtendsBinding(trule)); ruleContext.composeRight(getOverrideBinding(trule)); contextSet.add(ruleContext); return contextSet; } // static class TestBuildMaps { // public static void main(String[] args) throws ResolutionException { // Transformation t = TefkatFactory.eINSTANCE.createTransformation(); // // TRule r1 = TefkatFactory.eINSTANCE.createTRule(); // r1.setTransformation(t); // r1.setName("R1"); // Var v1 = TefkatFactory.eINSTANCE.createVar(); // v1.setScope(r1); // v1.setName("v1"); // r1.setSrc(mi(v1, "Src1")); // r1.getTgt().add(mi(v1, "Tgt1")); // // TRule r2 = TefkatFactory.eINSTANCE.createTRule(); // r2.setTransformation(t); // r2.setName("R2"); // Var v2 = TefkatFactory.eINSTANCE.createVar(); // v2.setScope(r2); // v2.setName("v2"); // v2.getExtended().add(v1); // r2.setSrc(mi(v2, "Src2")); // r2.getTgt().add(mi(v2, "Tgt2")); // r2.getExtended().add(r1); // // TRule r3 = TefkatFactory.eINSTANCE.createTRule(); // r3.setTransformation(t); // r3.setName("R3"); // Var v3 = TefkatFactory.eINSTANCE.createVar(); // v3.setScope(r3); // v3.setName("v3"); // v3.getSuperseded().add(v1); // r3.setSrc(mi(v3, "Src3")); // r3.getTgt().add(mi(v3, "Tgt3")); // r3.getSuperseded().add(r1); // // TRule r4 = TefkatFactory.eINSTANCE.createTRule(); // r4.setTransformation(t); // r4.setName("R4"); // Var v4 = TefkatFactory.eINSTANCE.createVar(); // v4.setScope(r4); // v4.setName("v4"); // v4.getExtended().add(v3); // r4.setSrc(mi(v4, "Src4")); // r4.getTgt().add(mi(v4, "Tgt4")); // r4.getExtended().add(r3); // // RuleEvaluator re = new RuleEvaluator(null, null, Collections.EMPTY_MAP, Collections.EMPTY_LIST); // // re.buildMaps(t); // //// System.out.println("s1prime: " + re.getSourceTerms(r1)); //// System.out.println("t1prime: " + re.getTargetTerms(r1)); //// System.out.println("over1 : " + re.getOverrideTerms(r1)); //// System.out.println(); //// //// System.out.println("s2prime: " + re.getSourceTerms(r2)); //// System.out.println("t2prime: " + re.getTargetTerms(r2)); //// System.out.println("over2 : " + re.getOverrideTerms(r2)); //// System.out.println(); //// //// System.out.println("s3prime: " + re.getSourceTerms(r3)); //// System.out.println("t3prime: " + re.getTargetTerms(r3)); //// System.out.println("over3 : " + re.getOverrideTerms(r3)); //// System.out.println(); //// //// System.out.println("s4prime: " + re.getSourceTerms(r4)); //// System.out.println("t4prime: " + re.getTargetTerms(r4)); //// System.out.println("over4 : " + re.getOverrideTerms(r4)); //// System.out.println(); // // Binding b1 = new Binding(re.getExtendsBinding(r1)); // b1.composeRight(re.getOverrideBinding(r1)); // Binding b2 = new Binding(re.getExtendsBinding(r2)); // b2.composeRight(re.getOverrideBinding(r2)); // Binding b3 = new Binding(re.getExtendsBinding(r3)); // b3.composeRight(re.getOverrideBinding(r3)); // Binding b4 = new Binding(re.getExtendsBinding(r4)); // b4.composeRight(re.getOverrideBinding(r4)); // // System.out.println("bind1 : " + b1);// + "\t" + // // re.sPrimeBinding(r1) + "\t" // // + re.overBinding(r1)); // System.out.println("bind2 : " + b2);// + "\t" + // // re.sPrimeBinding(r2) + "\t" // // + re.overBinding(r2)); // System.out.println("bind3 : " + b3);// + "\t" + // // re.sPrimeBinding(r3) + "\t" // // + re.overBinding(r3)); // System.out.println("bind4 : " + b4);// + "\t" + // // re.sPrimeBinding(r4) + "\t" // // + re.overBinding(r4)); // } // // private static MofInstance mi(Var v, String type) { // MofInstance mi = TefkatFactory.eINSTANCE.createMofInstance(); // tefkat.model.StringConstant t = TefkatFactory.eINSTANCE // .createStringConstant(); // t.setRepresentation(type); // tefkat.model.VarUse vu = TefkatFactory.eINSTANCE // .createVarUse(); // vu.setVar(v); // mi.setTypeName(t); // mi.setInstance(vu); // return mi; // } // } /** * Records a TRule's interest in a given tracking class for fix-point * computation. * * @param trackingClass * @param callback */ void trackingQuery(EClass trackingClass, TrackingCallback callback) { // if (INCREMENTAL) { List<TrackingCallback> callbacks = trackingQueryMap.get(trackingClass); if (null == callbacks) { callbacks = new ArrayList<TrackingCallback>(); trackingQueryMap.put(trackingClass, callbacks); } callbacks.add(callback); // } else { // Set[] scopes = (Set[]) trackingQueryMap.get(trackingClass); // if (null == scopes) { // scopes = new Set[] {new HashSet(), new HashSet()}; // trackingQueryMap.put(trackingClass, scopes); // } // scopes[0].add(currentRule); // scopes[1].addAll(patternStack); // } } Map<EClass, List<EObject>> tcCache = new HashMap<EClass, List<EObject>>(); /** * Records that an instance of a given tracking class was created. * * @param trackingClass * @throws NotGroundException * @throws ResolutionException */ void trackingCreate(EClass trackingClass, EObject instance) throws ResolutionException, NotGroundException { // if (INCREMENTAL) { updateTrackingCache(trackingClass, instance); for (Iterator itr = trackingClass.getEAllSuperTypes().iterator(); itr.hasNext(); ) { updateTrackingCache((EClass) itr.next(), instance); } List callbacks = trackingQueryMap.get(trackingClass); if (null != callbacks) { for (Iterator itr = callbacks.iterator(); itr.hasNext(); ) { TrackingCallback callback = (TrackingCallback) itr.next(); callback.handleInstance(instance); // int count = srcResolver.callCount.get(TefkatPackage.Literals.TRACKING_USE); // count++; // srcResolver.callCount.put(TefkatPackage.Literals.TRACKING_USE, count); } } for (EClass superClass : trackingClass.getEAllSuperTypes()) { List<TrackingCallback> superCallbacks = trackingQueryMap.get(superClass); if (null != superCallbacks) { for (TrackingCallback callback : superCallbacks) { callback.handleInstance(instance); // int count = srcResolver.callCount.get(TefkatPackage.Literals.TRACKING_USE); // count++; // srcResolver.callCount.put(TefkatPackage.Literals.TRACKING_USE, count); } } } // } else { // trackingUpdateSet.add(trackingClass); // } } private void updateTrackingCache(EClass trackingClass, EObject instance) { getTrackingCache(trackingClass).add(instance); } List<EObject> getTrackingCache(EClass trackingClass) { List<EObject> list = tcCache.get(trackingClass); if (null == list) { list = new ArrayList<EObject>(); tcCache.put(trackingClass, list); } return list; } final private Map<Object, Map<Object, PartialOrder>> featureOrderings = new HashMap<Object, Map<Object, PartialOrder>>(); void addPartialOrder(Object inst, Object feat, Object lesser, Object greater) { Map<Object, PartialOrder> instanceOrderings = featureOrderings.get(feat); if (null == instanceOrderings) { instanceOrderings = new HashMap<Object, PartialOrder>(); featureOrderings.put(feat, instanceOrderings); } PartialOrder partialOrder = instanceOrderings.get(inst); if (null == partialOrder) { partialOrder = new PartialOrder(feat + " of " + inst); instanceOrderings.put(inst, partialOrder); } partialOrder.lessThan(lesser, greater); } @SuppressWarnings("unchecked") private void topologicalSort() throws ResolutionException { if (featureOrderings.size() > 0) { fireInfo("Resolving ordering constraints."); } for (final Iterator fItr = featureOrderings.entrySet().iterator(); fItr.hasNext(); ) { final Map.Entry fEntry = (Map.Entry) fItr.next(); final String feat = (String) fEntry.getKey(); final Map featureOrderings = (Map) fEntry.getValue(); for (final Iterator iItr = featureOrderings.entrySet().iterator(); iItr.hasNext(); ) { final Map.Entry iEntry = (Map.Entry) iItr.next(); final EObject inst = (EObject) iEntry.getKey(); final PartialOrder partialOrder = (PartialOrder) iEntry.getValue(); final EStructuralFeature feature = ModelUtils.getFeature(inst.eClass(), feat); final Object val = inst.eGet(feature); if (!(val instanceof List)) { throw new ResolutionException(null, "The feature " + feat + " of " + inst + " did not return an ordered collection."); } partialOrder.sort((List<Object>) val); } } } static class PartialOrder { final private String context; final private Map<Object, Set<Object>> instanceOrderings = new HashMap<Object, Set<Object>>(); final private Map<Object, Counter> instanceCounters = new HashMap<Object, Counter>(); PartialOrder(String context) { this.context = context; } void lessThan(Object lesser, Object greater) { // System.out.println(lesser + " < " + greater); if (null == instanceOrderings.get(greater)) { instanceOrderings.put(greater, new HashSet<Object>()); instanceCounters.put(greater, new Counter()); } Set<Object> adjacentNodes = instanceOrderings.get(lesser); if (null == adjacentNodes) { adjacentNodes = new HashSet<Object>(); instanceOrderings.put(lesser, adjacentNodes); instanceCounters.put(lesser, new Counter()); } if (!adjacentNodes.contains(greater)) { instanceCounters.get(greater).increment(); adjacentNodes.add(greater); } } void lessThanEqual(Object lesser, Object greater) { // System.out.println(lesser + " <= " + greater); if (null == instanceOrderings.get(greater)) { instanceOrderings.put(greater, new HashSet<Object>()); instanceCounters.put(greater, new Counter()); } Set<Object> adjacentNodes = instanceOrderings.get(lesser); if (null == adjacentNodes) { adjacentNodes = new HashSet<Object>(); instanceOrderings.put(lesser, adjacentNodes); instanceCounters.put(lesser, new Counter()); } adjacentNodes.add(greater); } void sort(List<Object> vals) throws ResolutionException { List<Object> no_pred = new ArrayList<Object>(); // Identify all nodes with no predecessors for (final Iterator<Object> itr = instanceOrderings.keySet().iterator(); itr.hasNext(); ) { Object node = itr.next(); if (instanceCounters.get(node).isZero()) { no_pred.add(node); } } // As long as there are nodes with no predecessors, output and "delete" // them for (int i = 0; !no_pred.isEmpty(); i++) { Object lesser = no_pred.remove(0); // ((org.eclipse.emf.common.util.EList) vals).move(i, lesser); int idx = vals.lastIndexOf(lesser); if (idx >= 0) { vals.remove(idx); } else { throw new ResolutionException(null, "Cannot order non-existent member of " + context + " : " + lesser); } try { vals.add(i, lesser); } catch (ArrayStoreException e) { throw new ResolutionException(null, "Cannot store " + lesser + " in " + context, e); } Collection adjacentNodes = instanceOrderings.get(lesser); if (null == adjacentNodes) { continue; } for (Iterator neighbors = adjacentNodes.iterator(); neighbors.hasNext(); ) { Object greater = neighbors.next(); Counter counter = instanceCounters.get(greater); if (null == counter) { no_pred.add(greater); } else { counter.decrement(); if (counter.isZero()) { no_pred.add(greater); } } } } // Are there nodes remaining? If so, it was a cyclic graph. List<Object> cycle = new ArrayList<Object>(); for (final Iterator itr = instanceCounters.entrySet().iterator(); itr.hasNext();) { final Map.Entry entry = (Map.Entry) itr.next(); final Counter value = (Counter) entry.getValue(); if (!value.isZero()) { final Object key = entry.getKey(); cycle.add(key); } } if (!cycle.isEmpty()) { throw new ResolutionException(null, "Found a cyclic partial order in " + context + ": " + cycle); } } private static class Counter { int val = 0; Counter() { // no init required } void increment() { val++; } void decrement() { val--; } boolean isZero() { return 0 == val; } } } // private static class TestSort { // public static void main(String[] args) { // EClass inst = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE.createEClass(); // String feat = "eStructuralFeatures"; // EStructuralFeature[] a = {org.eclipse.emf.ecore.EcoreFactory.eINSTANCE.createEReference(), // org.eclipse.emf.ecore.EcoreFactory.eINSTANCE.createEReference(), // org.eclipse.emf.ecore.EcoreFactory.eINSTANCE.createEAttribute(), // org.eclipse.emf.ecore.EcoreFactory.eINSTANCE.createEAttribute(), // org.eclipse.emf.ecore.EcoreFactory.eINSTANCE.createEAttribute(), // }; // a[0].setName("zero"); // a[1].setName("one"); // a[2].setName("two"); // a[3].setName("three"); // a[4].setName("four"); // inst.getEStructuralFeatures().addAll(java.util.Arrays.asList(a)); // // RuleEvaluator re = new RuleEvaluator(null, null, Collections.EMPTY_MAP, Collections.EMPTY_LIST); // // re.addPartialOrder(inst, feat, a[4], a[3]); // re.addPartialOrder(inst, feat, a[3], a[2]); // re.addPartialOrder(inst, feat, a[2], a[1]); // re.addPartialOrder(inst, feat, a[1], a[0]); // // re.addPartialOrder(inst, feat, a[4], "foo"); // Should cause a resolution exception below // // try { // for (final Iterator itr = inst.getEStructuralFeatures().iterator(); itr.hasNext(); ) { // System.out.println(((EStructuralFeature) itr.next()).getName()); // } // re.topologicalSort(); // System.out.println("-----------------"); // for (final Iterator itr = inst.getEStructuralFeatures().iterator(); itr.hasNext(); ) { // System.out.println(((EStructuralFeature) itr.next()).getName()); // } // } catch (ResolutionException e) { // e.printStackTrace(); // } // } // } final private Stack<PatternDefn> patternStack = new Stack<PatternDefn>(); /** * @param defn */ void pushPattern(PatternDefn defn) { patternStack.push(defn); } /** * */ void popPattern() { patternStack.pop(); } /** * @param term * @return */ public Map getPatternCache(Term term) { Map cache = patternCache.get(term); if (null == cache) { cache = new HashMap(); patternCache.put(term, cache); } return cache; } }