package tefkat.engine.runtime; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.FeatureMap; public class Context { private static final String NULL_TYPE = "null"; final private TransformationEvaluation evaluation; final Tree tree; final Node node; final static private Map methodCache = new HashMap(); Context(TransformationEvaluation evaluation, /*RuleEvaluator ruleEval, Evaluator exprEval,*/ Tree tree, Node node) { this.evaluation = evaluation; // this.ruleEval = ruleEval; // this.exprEval = exprEval; this.tree = tree; this.node = node; } public void createBranch() { List newGoal = newGoal(); tree.createBranch(node, null, newGoal); } public void createBranch(Term term) { createBranch(term, null); } public void createBranch(Binding unifier) { List newGoal = newGoal(); tree.createBranch(node, unifier, newGoal); } public void createBranch(Term term, Binding unifier) { List newGoal = newGoal(); newGoal.add(term); tree.createBranch(node, unifier, newGoal); } public void createBranch(Collection terms) { List newGoal = newGoal(); newGoal.addAll(terms); tree.createBranch(node, null, newGoal); } public void delay(String message) throws NotGroundException { throw new NotGroundException(node, message); } public void error(String message) throws ResolutionException { throw new ResolutionException(node, message); } public void error(String message, Exception e) throws ResolutionException { throw new ResolutionException(node, message, e); } private List newGoal() { List newGoal = new ArrayList(node.goal()); newGoal.remove(node.selectedLiteral()); return newGoal; } public void fail() { node.setIsFailure(true); } /** * Find a binding for the variable in the current context. * * @param var The var to lookup in this context * @return The value that var is bound to in the context of this node or null */ public Object lookup(Var var) { return node.lookup(var); } public Tree createTree(Collection goal, Binding unifier, boolean isNegation, boolean subTree) { return createTree(new Node(goal, unifier), isNegation, subTree); } Tree createTree(Node newRoot, boolean isNegation, boolean subTree) { Tree result = new Tree(this, newRoot, tree.getContext(), tree.getTrackingExtent(), isNegation); if (subTree) { result.setLevel(tree.getLevel()-1); } else { result.setLevel(tree.getLevel()); } evaluation.addUnresolvedTree(result); return result; } public Binding getBindings() { return node.getBindings(); } public List expand(WrappedVar wVar) throws NotGroundException { if (null == wVar.getExtent()) { delay("Unsupported mode: unbound extent for " + wVar); } // Var var = wVar.getVar(); // EClassifier type = wVar.getType(); // System.out.println("Expanding " + wVar); // TODO delete // System.out.println("\t" + wVar.getExtent().getObjectsByClass(wVar.getType(), wVar.isExact())); return wVar.getExtent().getObjectsByClass(wVar.getType(), wVar.isExact()); } public EObject lookup(List keys, TRule rule) { return evaluation.injections.lookup(tree.getTrackingExtent(), keys, rule); } public void info(String string) { evaluation.fireInfo(string); } public void warn(String string) { evaluation.fireWarning(string); } public void warn(Throwable throwable) { evaluation.fireWarning(throwable); } public Object fetchFeature(String featureName, Object obj) throws ResolutionException { Object valuesObject = null; if (obj instanceof DynamicObject) { throw new ResolutionException(node, "Illegal attempt to retrieve feature value from target object instance: " + obj); } if (obj instanceof EObject) { EObject instance = (EObject) obj; // If instance is a DynamicObject or it's containing eResource is a target Extent // then we're querying a target object which is an error (until we update the stratification // as outlined by David Hearnden) EStructuralFeature eFeature = instance.eClass().getEStructuralFeature(featureName); if (null != eFeature) { valuesObject = instance.eGet(eFeature); if (valuesObject != null || instance.eIsSet(eFeature) || !eFeature.isRequired()) { // FIXME ExtentUtil.highlightEdge(instance, valuesObject, ExtentUtil.FEATURE_LOOKUP); } else { warn(Context.getFullyQualifiedName(eFeature) + " is not set and no default value"); } return valuesObject; // This was a valid feature - don't want to fall through } } // EFeature not found, so try other ways to get a value for featureName if (obj instanceof FeatureMap.Entry) { FeatureMap.Entry entry = (FeatureMap.Entry) obj; EStructuralFeature eFeature = entry.getEStructuralFeature(); if (eFeature.getName().equals(featureName)) { valuesObject = entry.getValue(); return valuesObject; // This was a valid feature - don't want to fall through } } String methName = "get" + featureName.substring(0, 1).toUpperCase() + featureName.substring(1, featureName.length()); try { try { valuesObject = obj.getClass().getMethod(methName, null).invoke(obj, null); } catch (NoSuchMethodException e) { if (null == valuesObject) { valuesObject = obj.getClass().getField(featureName).get(obj); } } } catch (Exception e) { warn("Could not find a source of values for '" + featureName + "' in '" + obj + "'"); } return valuesObject; } public void addPartialOrder(Object inst, Object feat, Object lesser, Object greater) { evaluation.addPartialOrder(inst, feat, lesser, greater); } public Tree getResultTree(final Term term, final Binding unifier) { final Map cache = evaluation.getPatternCache(term); final Binding parameterContext; if (null == unifier) { parameterContext = tree.getContext(); } else { parameterContext = unifier; parameterContext.composeRight(tree.getContext()); } Tree resultTree = (Tree) cache.get(parameterContext); if (null == resultTree) { final Collection goal = new ArrayList(); goal.add(term); // Collection patGoal = new ArrayList(); // Term pDefTerm = pDefn.getTerm(); // patGoal.add(pDefTerm); // System.err.println("resolving " + patGoal + "\n " + newContext); // TODO delete Node patternNode = new Node(goal, parameterContext); // Maybe Tree (via context) should construct the new tree? resultTree = createTree(patternNode, false, false); cache.put(parameterContext, resultTree); } if (!resultTree.isCompleted()) { // Register listener for floundering (and remove from cache) resultTree.addTreeListener(new TreeListener() { public void solution(Binding answer) { } public void completed(Tree theTree) { theTree.removeTreeListener(this); } public void floundered(Tree theTree) { cache.remove(parameterContext); } }); } return resultTree; } /** * * @param trackingClass * @param isExactly * @param callback Used to notify of subsequently created instances, can be null * @return */ public List getObjectsByClass(EClass trackingClass, boolean isExactly, TrackingCallback callback) { // if necessary, record that this rule has queried the tracking class if (null != callback) { evaluation.trackingQuery(trackingClass, callback); } // ExtentUtil.highlightNodes(trackings, ExtentUtil.CLASS_LOOKUP); return tree.getTrackingExtent().getObjectsByClass(trackingClass, isExactly); } public void addTrackingInstance(EClass trackingClass, EObject trackingInstance) throws ResolutionException, NotGroundException { tree.getTrackingExtent().add(trackingInstance); evaluation.trackingCreate(trackingClass, trackingInstance); } public EStructuralFeature getFeature(EClass klass, String featureName) throws ResolutionException { EStructuralFeature feature = klass.getEStructuralFeature(featureName); if (null == feature) { String featureNames = null; List allFeatures = klass.getEAllStructuralFeatures(); for (Iterator itr = allFeatures.iterator(); itr.hasNext();) { EStructuralFeature esf = (EStructuralFeature) itr.next(); if (null == featureNames) { featureNames = esf.getName(); } else { featureNames = featureNames + ", " + esf.getName(); } } error("Cannot find feature '" + featureName + "' for '" + klass.getName() + ". Valid features are: " + featureNames); } return feature; } public boolean isCompleted() { return tree.isCompleted(); } public Function getFunction(String name) { return evaluation.getFunction(name); } /** * Lookup the supplied name map (containing both unqualifed and fully-qualified names) for the corresponding EClass. * Returns null if it is not found. * * @param name * @return */ public final EClassifier findClassifierByName(String name) { return evaluation.findClassifierByName(name); } public final static String getFullyQualifiedName(Class klass) { return klass.getName(); } public final static String getFullyQualifiedName(EClassifier eClassifier) { return getFullyQualifiedName(eClassifier.getEPackage()) + "::" + eClassifier.getName(); } final static String getFullyQualifiedName(EPackage ePackage) { String name = ""; while (null != ePackage) { name = "::" + ePackage.getName() + name; ePackage = ePackage.getESuperPackage(); } return name; } public final static String getFullyQualifiedName(EStructuralFeature eFeature) { return getFullyQualifiedName(eFeature.getEContainingClass()) + "." + eFeature.getName(); } private static Map getMethodCache(Object instance, String name) { Map namesToTypes = (Map) methodCache.get(instance.getClass()); Map typesToMethods; if (null != namesToTypes) { typesToMethods = (Map) namesToTypes.get(name); if (null == typesToMethods) { typesToMethods = new HashMap(); namesToTypes.put(name, typesToMethods); } } else { namesToTypes = new HashMap(); typesToMethods = new HashMap(); methodCache.put(instance.getClass(), namesToTypes); namesToTypes.put(name, typesToMethods); } return typesToMethods; } public static Method resolveMethod(Object instance, String name, Object[] params) { Map methodCache = getMethodCache(instance, name); Method method = null; // Deal with zero-arity method first if (null == params || params.length == 0) { Class cls = instance.getClass(); method = (Method) methodCache.get(NULL_TYPE); if (null == method) { try { Method[] ms = cls.getMethods(); for (int i = 0; null == method && i < ms.length; i++) { if (name.equals(ms[i].getName()) && ms[i].getParameterTypes().length == 0) { method = ms[i]; } } } catch (SecurityException e) { } } if (null != method) { // Cache the result methodCache.put(NULL_TYPE, method); } return method; } Class[] rawTypes = new Class[params.length]; boolean hasBoxedTypes = false; for (int j = 0; j < params.length; j++) { Class type = params[j].getClass(); rawTypes[j] = type; if (Number.class.isAssignableFrom(type)) { hasBoxedTypes = true; } } method = (Method) methodCache.get(Arrays.asList(rawTypes)); if (null == method) { Class[] unboxedTypes = null; if (hasBoxedTypes) { unboxedTypes = new Class[params.length]; for (int j = 0; j < params.length; j++) { Class type = params[j].getClass(); rawTypes[j] = type; if (Number.class.isAssignableFrom(type)) { try { Field typeField = type.getField("TYPE"); unboxedTypes[j] = (Class) typeField.get(type); } catch (SecurityException e) { } catch (NoSuchFieldException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } } else { unboxedTypes[j] = type; } } } method = resolveMethod(instance, name, rawTypes, unboxedTypes); if (null != method) { // Cache the result methodCache.put(Arrays.asList(rawTypes), method); } } return method; } /** * Beware, this will find the first method (they are in an arbitrary order) that matches subject to * auto-unboxing...eg, the choice between foo(int) and foo(Integer) is arbitrary * * @param instance * @param name * @param types * @param unboxedTypes may be null, but otherwise the same length as types * @return */ static private Method resolveMethod(Object instance, String name, Class[] types, Class[] unboxedTypes) { Method method = null; Class cls = instance.getClass(); // FIXME: This will fail for private Classes implementing public Interfaces Method[] ms = cls.getMethods(); for (int i = 0; null == method && i < ms.length; i++) { if (name.equals(ms[i].getName())) { Class[] parameterTypes = ms[i].getParameterTypes(); if (parameterTypes.length != types.length) { continue; } method = ms[i]; for (int j = 0; j < parameterTypes.length; j++) { if (!parameterTypes[j].isAssignableFrom(types[j]) && (null == unboxedTypes || !parameterTypes[j].isAssignableFrom(unboxedTypes[j]))) { method = null; break; } } } } return method; } /** * Choose a literal from the goal of the given node. * * @return A chosen literal, or null if the node's goal is empty (i.e. * success) * @throws ResolutionException * @throws NotGroundException */ public Term selectLiteral() throws ResolutionException, NotGroundException { Term[] literals = 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.setSelectedLiteral(literals[i]); return literals[i]; } } for (int i = 0; i < literals.length; i++) { if (!literals[i].isTarget()) { node.setSelectedLiteral(literals[i]); return literals[i]; } } if (!groundWrappedVars()) { return null; } if (null != node.getDelayed() && !node.getDelayed().isEmpty()) { throw new ResolutionException(node, "Flounder: All source terms delayed: " + formatDelayedNode()); } for (int i = 0; i < literals.length; i++) { if (literals[i] instanceof Injection) { node.setSelectedLiteral(literals[i]); return literals[i]; } } for (int i = 0; i < literals.length; i++) { if (literals[i] instanceof MofInstance) { node.setSelectedLiteral(literals[i]); return literals[i]; } } if (literals.length > 0) { node.setSelectedLiteral(literals[0]); return literals[0]; } throw new ResolutionException(node, "Could not select a valid literal from goal: " + node.goal()); } /** * 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 */ boolean groundWrappedVars() throws NotGroundException { boolean done = true; // fireInfo("grounding..."); final Binding binding = 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 = expand((WrappedVar) v); // fireInfo(var + " <- #instances: " + l.size()); if (l.size() == 0) { 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); createBranch(unifier); } } } } return done; } private String formatDelayedNode() { 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(); } }