| /** |
| * <copyright> |
| * Copyright (c) 2010-2014 Henshin developers. All rights reserved. |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Public License v1.0 which |
| * accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * </copyright> |
| */ |
| package org.eclipse.emf.henshin.interpreter.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| |
| import javax.script.ScriptEngine; |
| import javax.script.ScriptException; |
| |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EDataType; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature.Setting; |
| import org.eclipse.emf.ecore.EcorePackage; |
| import org.eclipse.emf.ecore.util.EContentAdapter; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.emf.henshin.interpreter.Change; |
| import org.eclipse.emf.henshin.interpreter.Change.CompoundChange; |
| import org.eclipse.emf.henshin.interpreter.EGraph; |
| import org.eclipse.emf.henshin.interpreter.Engine; |
| import org.eclipse.emf.henshin.interpreter.Match; |
| import org.eclipse.emf.henshin.interpreter.PartitionedEGraph; |
| import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.AttributeChangeImpl; |
| import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.CompoundChangeImpl; |
| import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.IndexChangeImpl; |
| import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.ObjectChangeImpl; |
| import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.ReferenceChangeImpl; |
| import org.eclipse.emf.henshin.interpreter.info.ConditionInfo; |
| import org.eclipse.emf.henshin.interpreter.info.RuleChangeInfo; |
| import org.eclipse.emf.henshin.interpreter.info.RuleInfo; |
| import org.eclipse.emf.henshin.interpreter.info.VariableInfo; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.AndFormula; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.ApplicationCondition; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.ConditionHandler; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.IFormula; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.NotFormula; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.OrFormula; |
| import org.eclipse.emf.henshin.interpreter.matching.conditions.XorFormula; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.BinaryConstraint; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.DanglingConstraint; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.DomainSlot; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.Solution; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.SolutionFinder; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.TypeConstraint.PartitionThread; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.UnaryConstraint; |
| import org.eclipse.emf.henshin.interpreter.matching.constraints.Variable; |
| import org.eclipse.emf.henshin.model.And; |
| import org.eclipse.emf.henshin.model.Attribute; |
| import org.eclipse.emf.henshin.model.Edge; |
| import org.eclipse.emf.henshin.model.Formula; |
| import org.eclipse.emf.henshin.model.Graph; |
| import org.eclipse.emf.henshin.model.Mapping; |
| import org.eclipse.emf.henshin.model.MappingList; |
| import org.eclipse.emf.henshin.model.NestedCondition; |
| import org.eclipse.emf.henshin.model.Node; |
| import org.eclipse.emf.henshin.model.Not; |
| import org.eclipse.emf.henshin.model.Or; |
| import org.eclipse.emf.henshin.model.Parameter; |
| import org.eclipse.emf.henshin.model.Rule; |
| import org.eclipse.emf.henshin.model.Xor; |
| import org.eclipse.emf.henshin.model.staticanalysis.PathFinder; |
| import org.eclipse.emf.henshin.model.util.ScriptEngineWrapper; |
| |
| /** |
| * Default {@link Engine} implementation. |
| * |
| * @author Christian Krause, Gregor Bonifer, Enrico Biermann |
| */ |
| public class EngineImpl implements Engine { |
| |
| /** |
| * Default value for option {@link Engine#OPTION_SORT_VARIABLES}. |
| */ |
| private static final boolean DEFAULT_SORT_VARIABLES = true; |
| |
| /** |
| * Default value for option {@link Engine#OPTION_INVERSE_MATCHING_ORDER}. |
| */ |
| private static final boolean DEFAULT_INVERSE_MATCHING_ORDER = true; |
| |
| /** |
| * Default value for option {@link Engine#OPTION_DESTROY_MATCHES}. |
| */ |
| private static final boolean DEFAULT_DESTROY_MATCHES = false; |
| |
| /** |
| * Options to be used. |
| */ |
| protected final Map<String, Object> options; |
| |
| /** |
| * Script engine used to compute Java expressions in attributes. |
| */ |
| protected final ScriptEngineWrapper scriptEngine; |
| |
| /** |
| * Cached information lookup map for each rule. |
| */ |
| protected final Map<Rule, RuleInfo> ruleInfos; |
| |
| /** |
| * Cached graph options. |
| */ |
| protected final Map<Graph, MatchingOptions> graphOptions; |
| |
| /** |
| * Listen for rule changes. |
| */ |
| protected final EContentAdapter ruleListener; |
| |
| /** |
| * Whether to sort variables. |
| */ |
| protected boolean sortVariables; |
| |
| /** |
| * Whether to use inverse matching order. |
| */ |
| protected boolean inverseMatchingOrder; |
| |
| /** |
| * Whether destruction of matches is allowed. |
| */ |
| protected boolean destroyMatches; |
| |
| /** |
| * Worker thread pool. |
| */ |
| protected ExecutorService workerPool; |
| |
| /** |
| * Constructor. |
| * |
| * @param globalJavaImports |
| * List of global Java imports to be used in the script engine. |
| */ |
| public EngineImpl(String... globalJavaImports) { |
| |
| // Initialize fields: |
| ruleInfos = new HashMap<Rule, RuleInfo>(); |
| graphOptions = new HashMap<Graph, MatchingOptions>(); |
| options = new EngineOptions(); |
| sortVariables = DEFAULT_SORT_VARIABLES; |
| inverseMatchingOrder = DEFAULT_INVERSE_MATCHING_ORDER; |
| destroyMatches = DEFAULT_DESTROY_MATCHES; |
| |
| // Initialize the script engine: |
| scriptEngine = new ScriptEngineWrapper(globalJavaImports); |
| |
| // Rule listener for automatically clearing caches when rules are |
| // changed at run-time: |
| ruleListener = new RuleChangeListener(); |
| } |
| |
| /** |
| * Change listener for rules. If a rule is changed externally, the listener |
| * drops all cached options for this rule. This enables dynamic high-order |
| * transformation of rules. |
| */ |
| private final class RuleChangeListener extends EContentAdapter { |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.emf.ecore.util.EContentAdapter#notifyChanged(org.eclipse |
| * .emf.common.notify.Notification) |
| */ |
| @Override |
| public void notifyChanged(Notification notification) { |
| super.notifyChanged(notification); |
| int eventType = notification.getEventType(); |
| if (eventType == Notification.RESOLVE || eventType == Notification.REMOVING_ADAPTER) { |
| return; |
| } |
| if (notification.getNotifier() instanceof EObject) { |
| EObject object = (EObject) notification.getNotifier(); |
| while (object != null) { |
| if (object instanceof Rule) { |
| ruleInfos.remove(object); |
| object.eAdapters().remove(ruleListener); |
| } |
| if (object instanceof Graph) { |
| graphOptions.remove(object); |
| } |
| object = object.eContainer(); |
| } |
| } |
| } |
| }; |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.emf.henshin.interpreter.Engine#findMatches(org.eclipse.emf |
| * .henshin.model.Rule, org.eclipse.emf.henshin.interpreter.EGraph, |
| * org.eclipse.emf.henshin.interpreter.Match) |
| */ |
| @Override |
| public Iterable<Match> findMatches(Rule rule, EGraph graph, Match partialMatch) { |
| if (rule == null || graph == null) { |
| throw new NullPointerException("Rule and graph must not be null"); |
| } |
| if (partialMatch == null) { |
| partialMatch = new MatchImpl(rule); |
| } |
| return new MatchGenerator(rule, graph, partialMatch); |
| } |
| |
| /** |
| * Match generator class. Delegates to {@link MatchFinder}. |
| */ |
| final class MatchGenerator implements Iterable<Match> { |
| |
| /** |
| * Rule to be matched. |
| */ |
| private final Rule rule; |
| |
| /** |
| * Object graph. |
| */ |
| private final EGraph graph; |
| |
| /** |
| * A partial match. |
| */ |
| private final Match partialMatch; |
| |
| /** |
| * Default constructor. |
| * |
| * @param rule |
| * Rule to be matched. |
| * @param graph |
| * Object graph. |
| * @param partialMatch |
| * Partial match. |
| */ |
| public MatchGenerator(Rule rule, EGraph graph, Match partialMatch) { |
| this.rule = rule; |
| this.graph = graph; |
| this.partialMatch = partialMatch; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.lang.Iterable#iterator() |
| */ |
| @Override |
| public Iterator<Match> iterator() { |
| return new MatchFinder(rule, graph, partialMatch, new HashSet<EObject>()); |
| } |
| |
| } // MatchGenerator |
| |
| /** |
| * Match finder class. Uses {@link SolutionFinder} to find matches. |
| */ |
| public final class MatchFinder implements Iterator<Match> { |
| |
| /** |
| * The next match. |
| */ |
| private Match nextMatch; |
| |
| /** |
| * Flag indicating whether the next match was computed. |
| */ |
| private boolean computedNextMatch; |
| |
| /** |
| * Target graph. |
| */ |
| private final EGraph graph; |
| |
| /** |
| * Solution finder to be used. |
| */ |
| private final SolutionFinder solutionFinder; |
| |
| /** |
| * Rule to be matched. |
| */ |
| private final Rule rule; |
| |
| /** |
| * Cached rule info. |
| */ |
| protected final RuleInfo ruleInfo; |
| |
| /** |
| * Used objects. |
| */ |
| private final Set<EObject> usedObjects; |
| |
| /** |
| * Default constructor. |
| * |
| * @param rule |
| * Rule to be matched. |
| * @param graph |
| * Object graph. |
| * @param partialMatch |
| * A partial match. |
| * @param usedObjects |
| * Used objects (for ensuring injectivity). |
| */ |
| public MatchFinder(Rule rule, EGraph graph, Match partialMatch, Set<EObject> usedObjects) { |
| this.rule = rule; |
| this.ruleInfo = getRuleInfo(rule); |
| this.graph = graph; |
| this.usedObjects = usedObjects; |
| this.solutionFinder = createSolutionFinder(partialMatch); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.Iterator#hasNext() |
| */ |
| @Override |
| public boolean hasNext() { |
| if (!computedNextMatch) { |
| computeNextMatch(); |
| computedNextMatch = true; |
| } |
| return (nextMatch != null); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.Iterator#next() |
| */ |
| @Override |
| public Match next() { |
| if (hasNext()) { |
| computedNextMatch = false; |
| } |
| return nextMatch; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.Iterator#remove() |
| */ |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Compute the next match. |
| */ |
| private void computeNextMatch() { |
| |
| // We definitely need a solution finder: |
| if (solutionFinder == null) { |
| nextMatch = null; |
| return; |
| } |
| |
| // Find the next solution: |
| Solution solution = solutionFinder.getNextSolution(); |
| if (solution == null) { |
| nextMatch = null; |
| return; |
| } |
| |
| // Create the new match: |
| nextMatch = new MatchImpl(rule); |
| |
| // Parameter values: |
| for (Entry<String, Object> entry : solution.parameterValues.entrySet()) { |
| Parameter param = nextMatch.getUnit().getParameter(entry.getKey()); |
| if (param != null) { |
| nextMatch.setParameterValue(param, entry.getValue()); |
| } |
| } |
| |
| // LHS node targets: |
| Map<Node, Variable> node2var = ruleInfo.getVariableInfo().getNode2variable(); |
| for (Node node : rule.getLhs().getNodes()) { |
| nextMatch.setNodeTarget(node, solution.objectMatches.get(node2var.get(node))); |
| } |
| |
| // Handle the multi-rules: |
| for (Rule multiRule : rule.getMultiRules()) { |
| |
| // The used kernel objects: |
| Set<EObject> usedKernelObjects = new HashSet<EObject>(usedObjects); |
| usedKernelObjects.addAll(nextMatch.getNodeTargets()); |
| |
| // Create the partial multi match: |
| Match partialMultiMatch = new MatchImpl(multiRule); |
| for (Parameter param : rule.getParameters()) { |
| Parameter multiParam = multiRule.getParameter(param.getName()); |
| if (multiParam != null) { |
| partialMultiMatch.setParameterValue(multiParam, nextMatch.getParameterValue(param)); |
| } |
| } |
| for (Mapping mapping : multiRule.getMultiMappings()) { |
| partialMultiMatch.setNodeTarget(mapping.getImage(), nextMatch.getNodeTarget(mapping.getOrigin())); |
| } |
| |
| // Find nested multi-matches: |
| List<Match> nestedMatches = nextMatch.getMultiMatches(multiRule); |
| |
| // Check if we can use worker threads: |
| if (workerPool != null && (graph instanceof PartitionedEGraph) |
| && (multiRule.getLhs().getNodes().size() > 1)) { |
| |
| // Create match finder workers: |
| List<Future<List<Match>>> matchFinderFutures = new ArrayList<Future<List<Match>>>(); |
| int partitions = ((PartitionedEGraph) graph).getNumPartitions(); |
| for (int p = 0; p < partitions; p++) { |
| Set<EObject> freshUsedObjects = new HashSet<EObject>(usedKernelObjects); |
| MatchFinder matchFinder = new MatchFinder(multiRule, graph, partialMultiMatch, |
| freshUsedObjects); |
| MatchFinderWorker worker = new MatchFinderWorker(matchFinder, p); |
| matchFinderFutures.add(workerPool.submit(worker)); |
| } |
| |
| // Collect found matches: |
| try { |
| for (Future<List<Match>> futures : matchFinderFutures) { |
| nestedMatches.addAll(futures.get()); |
| } |
| } catch (Throwable t) { |
| throw new RuntimeException(t); |
| } |
| |
| } else { |
| |
| // Otherwise execute directly in this thread: |
| MatchFinder matchFinder = new MatchFinder(multiRule, graph, partialMultiMatch, usedKernelObjects); |
| while (matchFinder.hasNext()) { |
| nestedMatches.add(matchFinder.next()); |
| } |
| |
| } |
| |
| boolean valid = rule.getMultiRules().isEmpty() || doPostponedDanglingChecks(solution, nextMatch); |
| if (!valid) |
| computeNextMatch(); |
| } |
| |
| } |
| |
| private boolean doPostponedDanglingChecks(Solution solution, Match nextMatch) { |
| for (Variable var : solution.objectMatches.keySet()) { |
| Node node = ruleInfo.getVariableInfo().getVariableForNode(var); |
| EObject val = solution.objectMatches.get(var); |
| |
| for (DanglingConstraint constraint : var.danglingConstraints) { |
| if (constraint.postpone) { |
| boolean result = doCheckOnMultiRulesTransitively(nextMatch, node, val, constraint); |
| if (!result) |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private boolean doCheckOnMultiRulesTransitively(Match nextMatch, Node node, EObject val, DanglingConstraint constraint) { |
| boolean result = true; |
| DanglingConstraint constraint_ = constraint.copy(); |
| if (rule.getMultiRules().isEmpty()) |
| return constraint_.check(val, graph); |
| |
| // Main idea: The number of matches of each multi-rule determines |
| // how many (incoming and outgoing) edges the node provided as value |
| // may have s.t. no dangling edge remains. |
| for (Rule multi : rule.getMultiRules()) { |
| int count = nextMatch.getMultiMatches(multi).size(); |
| MappingList map = multi.getMultiMappings(); |
| Node mulNode = map.getImage(node, multi.getLhs()); |
| if (mulNode != null) { |
| for (Edge mulEdge : mulNode.getOutgoing()) { |
| Node kerNode = map.getOrigin(mulEdge.getTarget()); |
| if (kerNode == null) { |
| constraint_.increaseOutgoing(mulEdge.getType(), count); |
| } |
| } |
| for (Edge mulEdge : mulNode.getIncoming()) { |
| Node kerNode = map.getOrigin(mulEdge.getSource()); |
| if (kerNode == null) { |
| constraint_.increaseIncoming(mulEdge.getType(), count); |
| } |
| } |
| |
| for (Rule nextmulti : multi.getMultiRules()) { |
| for (Match m : nextMatch.getMultiMatches(nextmulti)) { |
| result &= doCheckOnMultiRulesTransitively(m, mulNode, val, constraint_); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Create a solution finder. |
| * |
| * @param partialMatch |
| * A partial match. |
| * @return The solution finder. |
| */ |
| protected SolutionFinder createSolutionFinder(Match partialMatch) { |
| |
| // Get the required info objects: |
| final ConditionInfo conditionInfo = ruleInfo.getConditionInfo(); |
| final VariableInfo varInfo = ruleInfo.getVariableInfo(); |
| |
| // Evaluates attribute conditions of the rule: |
| ConditionHandler conditionHandler = new ConditionHandler(conditionInfo.getConditionParameters(), |
| scriptEngine.getEngine()); |
| |
| /* |
| * The set "usedObjects" ensures injective matching by removing |
| * already matched objects from other DomainSlots |
| */ |
| |
| /* |
| * Create a domain map where all variables are mapped to slots. |
| * Different variables may share one domain slot, if there is a |
| * mapping between the nodes of the variables. |
| */ |
| |
| Map<Variable, DomainSlot> domainMap = new HashMap<Variable, DomainSlot>(); |
| |
| for (Variable mainVariable : varInfo.getMainVariables()) { |
| Node node = varInfo.getVariableForNode(mainVariable); |
| MatchingOptions opt = getGraphOptions(node.getGraph()); |
| DomainSlot domainSlot = new DomainSlot(conditionHandler, usedObjects, opt.injective, opt.dangling, |
| opt.deterministic, inverseMatchingOrder); |
| |
| // Fix this slot? |
| EObject target = partialMatch.getNodeTarget(node); |
| if (target != null) { |
| domainSlot.fixInstantiation(target); |
| } |
| |
| // Add the dependent variables to the domain map: |
| for (Variable dependendVariable : varInfo.getDependendVariables(mainVariable)) { |
| domainMap.put(dependendVariable, domainSlot); |
| } |
| } |
| |
| // Check if any of the conditions failed: |
| for (Parameter param : rule.getParameters()) { |
| Object value = partialMatch.getParameterValue(param); |
| if (value != null && !conditionHandler.setParameter(param.getName(), value)) { |
| return null; |
| } |
| } |
| |
| // Sort the main variables based on the size of their domains: |
| Map<Graph, List<Variable>> graphMap = new HashMap<Graph, List<Variable>>(); |
| for (Entry<Graph, List<Variable>> entry : ruleInfo.getVariableInfo().getGraph2variables().entrySet()) { |
| List<Variable> vars = new ArrayList<Variable>(entry.getValue()); |
| if (sortVariables) { |
| // sorting the variables |
| Collections.sort(vars, new VariableComparator(graph, varInfo, partialMatch)); |
| } |
| graphMap.put(entry.getKey(), vars); |
| } |
| |
| // Now initialize the match finder: |
| SolutionFinder solutionFinder = new SolutionFinder(graph, domainMap, conditionHandler); |
| solutionFinder.variables = graphMap.get(rule.getLhs()); |
| solutionFinder.formula = initFormula(rule.getLhs().getFormula(), graph, graphMap, domainMap); |
| return solutionFinder; |
| |
| } |
| |
| /** |
| * Initialize a formula. |
| */ |
| private IFormula initFormula(Formula formula, EGraph graph, Map<Graph, List<Variable>> graphMap, |
| Map<Variable, DomainSlot> domainMap) { |
| if (formula instanceof And) { |
| And and = (And) formula; |
| IFormula left = initFormula(and.getLeft(), graph, graphMap, domainMap); |
| IFormula right = initFormula(and.getRight(), graph, graphMap, domainMap); |
| return new AndFormula(left, right); |
| } else if (formula instanceof Or) { |
| Or or = (Or) formula; |
| IFormula left = initFormula(or.getLeft(), graph, graphMap, domainMap); |
| IFormula right = initFormula(or.getRight(), graph, graphMap, domainMap); |
| return new OrFormula(left, right); |
| } else if (formula instanceof Xor) { |
| Xor xor = (Xor) formula; |
| IFormula left = initFormula(xor.getLeft(), graph, graphMap, domainMap); |
| IFormula right = initFormula(xor.getRight(), graph, graphMap, domainMap); |
| return new XorFormula(left, right); |
| } else if (formula instanceof Not) { |
| Not not = (Not) formula; |
| IFormula child = initFormula(not.getChild(), graph, graphMap, domainMap); |
| return new NotFormula(child); |
| } else if (formula instanceof NestedCondition) { |
| NestedCondition nc = (NestedCondition) formula; |
| // check if we really need nested condition |
| if (nc.isTrue() || PathFinder.pacConsistsOnlyOfPaths(nc)) { |
| return IFormula.TRUE; |
| } |
| if (nc.isFalse()) { |
| return IFormula.FALSE; |
| } |
| return initApplicationCondition(nc, graph, graphMap, domainMap); |
| } |
| return IFormula.TRUE; |
| } |
| |
| /** |
| * Initialize an application condition. |
| */ |
| private ApplicationCondition initApplicationCondition(NestedCondition nc, EGraph graph, |
| Map<Graph, List<Variable>> graphMap, Map<Variable, DomainSlot> domainMap) { |
| ApplicationCondition ac = new ApplicationCondition(graph, domainMap); |
| ac.variables = graphMap.get(nc.getConclusion()); |
| ac.formula = initFormula(nc.getConclusion().getFormula(), graph, graphMap, domainMap); |
| return ac; |
| } |
| |
| public SolutionFinder getSolutionFinder() { |
| return solutionFinder; |
| } |
| |
| } // MatchFinder |
| |
| /** |
| * Match finding worker. To be used in a worker thread pool. It MUST be |
| * executed in a {@link PartitionThread}. |
| * |
| * @author Christian Krause |
| */ |
| private final class MatchFinderWorker implements Callable<List<Match>> { |
| |
| /** |
| * Match finder to be used. |
| */ |
| private final MatchFinder matchFinder; |
| |
| /** |
| * Partition to be used. |
| */ |
| private final int partition; |
| |
| /** |
| * Constructor. |
| * |
| * @param matchFinder |
| * Match finder to be used. |
| * @param partition |
| * Partition to be used. |
| */ |
| MatchFinderWorker(MatchFinder matchFinder, int partition) { |
| this.matchFinder = matchFinder; |
| this.partition = partition; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.concurrent.Callable#call() |
| */ |
| @Override |
| public List<Match> call() throws Exception { |
| Variable firstVar = matchFinder.solutionFinder.variables.get(0); |
| PartitionThread thread = (PartitionThread) Thread.currentThread(); |
| thread.partition = partition; |
| thread.firstDomainSlot = matchFinder.solutionFinder.domainMap.get(firstVar); |
| List<Match> matches = new ArrayList<Match>(); |
| while (matchFinder.hasNext()) { |
| matches.add(matchFinder.next()); |
| } |
| return matches; |
| } |
| |
| } // MatchFinderWorker |
| |
| /** |
| * Comparator for variables. Used to sort variables for optimal matching |
| * order. |
| */ |
| private class VariableComparator implements Comparator<Variable> { |
| |
| /** |
| * Target graph. |
| */ |
| private final EGraph graph; |
| |
| /** |
| * Variable info. |
| */ |
| private final VariableInfo varInfo; |
| |
| /** |
| * Partial match. |
| */ |
| private final Match partialMatch; |
| |
| /** |
| * Default sign to be used. |
| */ |
| private final int sign; |
| |
| /** |
| * Constructor. |
| * |
| * @param graph |
| * Target graph |
| * @param varInfo |
| * Variable info. |
| * @param partialMatch |
| * Partial match. |
| */ |
| public VariableComparator(EGraph graph, VariableInfo varInfo, Match partialMatch) { |
| this.graph = graph; |
| this.varInfo = varInfo; |
| this.partialMatch = partialMatch; |
| this.sign = inverseMatchingOrder ? 1 : -1; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) |
| */ |
| @Override |
| public int compare(Variable v1, Variable v2) { |
| |
| // Get the corresponding nodes: |
| Node n1 = varInfo.getVariableForNode(v1); |
| if (n1 == null) |
| return sign; |
| Node n2 = varInfo.getVariableForNode(v2); |
| if (n2 == null) |
| return -sign; |
| |
| // One of the nodes already matched or an attribute given as a |
| // parameter? |
| if (partialMatch != null) { |
| if (isNodeObjectMatched(n1) && !isNodeObjectMatched(n2)) { |
| return -sign; |
| } |
| if (isNodeObjectMatched(n2) && !isNodeObjectMatched(n1)) { |
| return sign; |
| } |
| if (isNodeAttributeMatched(n1) && !isNodeAttributeMatched(n2)) { |
| return -sign; |
| } |
| if (isNodeAttributeMatched(n2) && !isNodeAttributeMatched(n1)) { |
| return sign; |
| } |
| } |
| |
| // Get the domain sizes (smaller number wins): |
| int s = (graph.getDomainSize(v1.typeConstraint.type, v1.typeConstraint.strictTyping) |
| - graph.getDomainSize(v2.typeConstraint.type, v2.typeConstraint.strictTyping)); |
| if (s != 0) { |
| return (sign * s); |
| } |
| |
| // Attribute count (larger number wins): |
| int a = (n2.getAttributes().size() - n1.getAttributes().size()); |
| if (a != 0) { |
| return (sign * a); |
| } |
| |
| // Outgoing edge count (larger number wins): |
| return (sign * (n2.getOutgoing().size() - n1.getOutgoing().size())); |
| |
| } |
| |
| private boolean isNodeObjectMatched(Node node) { |
| return (partialMatch.getNodeTarget(node) != null); |
| } |
| |
| private boolean isNodeAttributeMatched(Node node) { |
| for (Attribute attribute : node.getAttributes()) { |
| String value = attribute.getValue(); |
| if (value != null) { |
| Parameter param = node.getGraph().getRule().getParameter(value); |
| if (param != null && partialMatch.getParameterValue(param) != null) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| } // VariableComparator |
| |
| /** |
| * Get the cached rule info for a given rule. |
| * |
| * @param rule |
| * Rule. |
| * @return The (cached) rule info. |
| */ |
| public RuleInfo getRuleInfo(Rule rule) { |
| RuleInfo ruleInfo = ruleInfos.get(rule); |
| if (ruleInfo == null) { |
| ruleInfo = new RuleInfo(rule, this); |
| ruleInfos.put(rule, ruleInfo); |
| |
| // Listen to changes: |
| rule.eAdapters().add(ruleListener); |
| |
| // Check for missing factories: |
| for (Node node : ruleInfo.getChangeInfo().getCreatedNodes()) { |
| if (node.getType() == null) { |
| throw new RuntimeException("Missing type for " + node); |
| } |
| if (node.getType().getEPackage() == null |
| || node.getType().getEPackage().getEFactoryInstance() == null) { |
| throw new RuntimeException("Missing factory for '" + node |
| + "'. Register the corresponding package, e.g. using PackageName.eINSTANCE.getName()."); |
| } |
| } |
| } |
| return ruleInfo; |
| } |
| |
| /** |
| * Clear the cache of this engine. |
| */ |
| public void clearCache() { |
| ruleInfos.clear(); |
| graphOptions.clear(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.emf.henshin.interpreter.Engine#createChange(org.eclipse.emf |
| * .henshin.model.Rule, org.eclipse.emf.henshin.interpreter.EGraph, |
| * org.eclipse.emf.henshin.interpreter.Match, |
| * org.eclipse.emf.henshin.interpreter.Match) |
| */ |
| @Override |
| public Change createChange(Rule rule, EGraph graph, Match completeMatch, Match resultMatch) { |
| |
| // We need a result match: |
| if (resultMatch == null) { |
| resultMatch = new MatchImpl(rule, true); |
| } |
| |
| // Create the object changes: |
| CompoundChangeImpl complexChange = new CompoundChangeImpl(graph); |
| createChanges(rule, graph, completeMatch, resultMatch, complexChange); |
| return complexChange; |
| |
| } |
| |
| /** |
| * Recursively create the changes and result matches. |
| * |
| * @param rule |
| * Rule to be applied. |
| * @param graph |
| * Host graph. |
| * @param completeMatch |
| * The complete match. |
| * @param resultMatch |
| * The result match. |
| * @param complexChange |
| * The final complex change. |
| */ |
| public void createChanges(Rule rule, EGraph graph, Match completeMatch, Match resultMatch, |
| CompoundChange complexChange) { |
| |
| // Get the rule change info and the object change list: |
| RuleChangeInfo ruleChange = getRuleInfo(rule).getChangeInfo(); |
| List<Change> changes = complexChange.getChanges(); |
| |
| // Set parameters: |
| for (Parameter param : rule.getParameters()) { |
| Object value = completeMatch.getParameterValue(param); |
| if (value != null) { |
| resultMatch.setParameterValue(param, value); |
| } else { |
| value = resultMatch.getParameterValue(param); |
| } |
| // Parameter matched by multi-rules? |
| if (value == null && !rule.getMultiRules().isEmpty()) { |
| List<Object> valueList = new ArrayList<Object>(); |
| for (Rule multiRule : rule.getMultiRules()) { |
| Parameter p = multiRule.getParameter(param.getName()); |
| if (p != null) { |
| for (Match multiMatch : completeMatch.getMultiMatches(multiRule)) { |
| value = multiMatch.getParameterValue(p); |
| if (value != null) { |
| valueList.add(value); |
| } |
| } |
| } |
| } |
| value = valueList; |
| } |
| |
| scriptEngine.getEngine().put(param.getName(), value); |
| } |
| |
| // Created objects: |
| for (Node node : ruleChange.getCreatedNodes()) { |
| EClass type = node.getType(); |
| EObject createdObject = type.getEPackage().getEFactoryInstance().create(type); |
| changes.add(new ObjectChangeImpl(graph, createdObject, true)); |
| resultMatch.setNodeTarget(node, createdObject); |
| } |
| |
| // Deleted objects: |
| for (Node node : ruleChange.getDeletedNodes()) { |
| EObject deletedObject = completeMatch.getNodeTarget(node); |
| changes.add(new ObjectChangeImpl(graph, deletedObject, false)); |
| // TODO: Shouldn't we check the rule options? |
| if (!rule.isCheckDangling()) { |
| Collection<Setting> removedEdges = graph.getCrossReferenceAdapter().getInverseReferences(deletedObject); |
| for (Setting edge : removedEdges) { |
| EReference ref = (EReference) edge.getEStructuralFeature(); |
| if (ref.isChangeable()) { |
| changes.add(new ReferenceChangeImpl(graph, edge.getEObject(), deletedObject, ref, false)); |
| } |
| } |
| } |
| } |
| |
| // Preserved objects: |
| for (Node node : ruleChange.getPreservedNodes()) { |
| Node lhsNode = rule.getMappings().getOrigin(node); |
| resultMatch.setNodeTarget(node, completeMatch.getNodeTarget(lhsNode)); |
| } |
| |
| // Deleted edges: |
| for (Edge edge : ruleChange.getDeletedEdges()) { |
| changes.add(new ReferenceChangeImpl(graph, completeMatch.getNodeTarget(edge.getSource()), |
| completeMatch.getNodeTarget(edge.getTarget()), edge.getType(), false)); |
| } |
| |
| // Created edges: |
| for (Edge edge : ruleChange.getCreatedEdges()) { |
| changes.add(new ReferenceChangeImpl(graph, resultMatch.getNodeTarget(edge.getSource()), |
| resultMatch.getNodeTarget(edge.getTarget()), edge.getType(), true)); |
| } |
| |
| // Edge index changes: |
| for (Edge edge : ruleChange.getIndexChanges()) { |
| Integer newIndex = edge.getIndexConstant(); |
| if (newIndex == null) { |
| Parameter param = rule.getParameter(edge.getIndex()); |
| if (param != null) { |
| newIndex = ((Number) resultMatch.getParameterValue(param)).intValue(); |
| } else { |
| try { |
| newIndex = ((Number) scriptEngine.eval(edge.getIndex(), rule.getAllJavaImports())).intValue(); |
| } catch (ScriptException e) { |
| throw new RuntimeException( |
| "Error evaluating edge index expression \"" + edge.getIndex() + "\": " + e.getMessage(), |
| e); |
| } |
| } |
| } |
| changes.add(new IndexChangeImpl(graph, resultMatch.getNodeTarget(edge.getSource()), |
| resultMatch.getNodeTarget(edge.getTarget()), edge.getType(), newIndex)); |
| } |
| |
| // Attribute changes: |
| for (Attribute attribute : ruleChange.getAttributeChanges()) { |
| EObject object = resultMatch.getNodeTarget(attribute.getNode()); |
| Object value; |
| Parameter param = rule.getParameter(attribute.getValue()); |
| if (param != null) { |
| value = castValueToDataType(resultMatch.getParameterValue(param), |
| attribute.getType().getEAttributeType(), attribute.getType().isMany()); |
| } else { |
| value = evalAttributeExpression(attribute, rule); // casting |
| // done here |
| // automatically |
| } |
| changes.add(new AttributeChangeImpl(graph, object, attribute.getType(), value)); |
| } |
| |
| // Now recursively for the multi-rules: |
| for (Rule multiRule : rule.getMultiRules()) { |
| Iterator<Match> multiMatches = completeMatch.getMultiMatches(multiRule).iterator(); |
| while (multiMatches.hasNext()) { |
| Match multiResultMatch = new MatchImpl(multiRule, true); |
| for (Mapping mapping : multiRule.getMultiMappings()) { |
| if (mapping.getImage().getGraph().isRhs()) { |
| multiResultMatch.setNodeTarget(mapping.getImage(), |
| resultMatch.getNodeTarget(mapping.getOrigin())); |
| } |
| } |
| createChanges(multiRule, graph, multiMatches.next(), multiResultMatch, complexChange); |
| if (destroyMatches) { |
| multiMatches.remove(); |
| } else { |
| resultMatch.getMultiMatches(multiRule).add(multiResultMatch); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Evaluates a given attribute expression using the JavaScript engine. |
| * |
| * @param attribute |
| * Attribute to be interpreted. |
| * @return The value. |
| */ |
| public Object evalAttributeExpression(Attribute attribute, Rule rule) { |
| |
| // Is it a constant or null? |
| Object constant = attribute.getConstant(); |
| if (constant != null) { |
| return constant; |
| } |
| if (attribute.isNull()) { |
| return null; |
| } |
| |
| // Try to evaluate the expression and cast it to the correct type: |
| try { |
| Object evalResult = scriptEngine.eval(attribute.getValue(), rule.getAllJavaImports()); |
| return castValueToDataType(evalResult, attribute.getType().getEAttributeType(), |
| attribute.getType().isMany()); |
| } catch (ScriptException e) { |
| throw new RuntimeException(e.getMessage()); |
| } |
| |
| } |
| |
| /** |
| * Cached Ecore package. |
| */ |
| private static final EcorePackage ECORE = EcorePackage.eINSTANCE; |
| |
| /** |
| * Cast a data value into a given data type. |
| * |
| * @param value |
| * Value. |
| * @param type |
| * Data type. |
| * @param isMany |
| * Many-flag. |
| * @return The casted object. |
| */ |
| private static Object castValueToDataType(Object value, EDataType type, boolean isMany) { |
| |
| // List of values? |
| if (isMany) { |
| EList<Object> list = new BasicEList<Object>(); |
| if (value instanceof Collection) { |
| for (Object elem : ((Collection<?>) value)) { |
| list.add(castValueToDataType(elem, type, false)); |
| } |
| } else if (value != null) { |
| list.add(value); |
| } |
| return list; |
| } |
| |
| // Null? |
| if (value == null) { |
| return null; |
| } |
| |
| // Number format conversions: |
| if (value instanceof Number) { |
| if (type == ECORE.getEInt() || type == ECORE.getEIntegerObject()) { |
| return ((Number) value).intValue(); |
| } |
| if (type == ECORE.getEDouble() || type == ECORE.getEDoubleObject()) { |
| return ((Number) value).doubleValue(); |
| } |
| if (type == ECORE.getEByte() || type == ECORE.getEByteObject()) { |
| return ((Number) value).byteValue(); |
| } |
| if (type == ECORE.getELong() || type == ECORE.getELongObject()) { |
| return ((Number) value).longValue(); |
| } |
| if (type == ECORE.getEFloat() || type == ECORE.getEFloatObject()) { |
| return ((Number) value).floatValue(); |
| } |
| } |
| |
| // Just a string? |
| if (type == ECORE.getEString()) { |
| if (value != null) |
| value = value.toString(); |
| return value; |
| } |
| |
| // A plain Java object? |
| if (type == ECORE.getEJavaObject() || type == ECORE.getEJavaClass()) { |
| return value; |
| } |
| |
| // Generic attribute value creation as fall-back. |
| return EcoreUtil.createFromString(type, value.toString()); |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.emf.henshin.interpreter.Engine#getOptions() |
| */ |
| @Override |
| public Map<String, Object> getOptions() { |
| return options; |
| } |
| |
| /** |
| * Data class for storing matching options. |
| */ |
| static class MatchingOptions { |
| boolean injective; |
| boolean dangling; |
| boolean deterministic; |
| } |
| |
| /** |
| * Get the options for a specific rule graph. The graph should be either the |
| * LHS or a nested condition. |
| * |
| * @param graph |
| * The graph. |
| * @return The cached options. |
| */ |
| protected MatchingOptions getGraphOptions(Graph graph) { |
| MatchingOptions options = graphOptions.get(graph); |
| if (options == null) { |
| |
| // Use the base options: |
| options = new MatchingOptions(); |
| Rule rule = graph.getRule(); |
| Boolean injective = (Boolean) this.options.get(OPTION_INJECTIVE_MATCHING); |
| Boolean dangling = (Boolean) this.options.get(OPTION_CHECK_DANGLING); |
| Boolean determistic = (Boolean) this.options.get(OPTION_DETERMINISTIC); |
| options.injective = (injective != null) ? injective.booleanValue() : rule.isInjectiveMatching(); |
| options.dangling = (dangling != null) ? dangling.booleanValue() : rule.isCheckDangling(); |
| options.deterministic = (determistic == null || determistic.booleanValue()); |
| |
| // Always use default values for nested conditions: |
| if (graph != rule.getLhs()) { |
| options.injective = true; |
| options.dangling = false; |
| options.deterministic = true; |
| } |
| |
| graphOptions.put(graph, options); |
| } |
| return options; |
| } |
| |
| /** |
| * An options map which clears cached rule options. |
| * |
| * @see #getOptions() |
| */ |
| private class EngineOptions extends HashMap<String, Object> { |
| private static final long serialVersionUID = 1L; |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.HashMap#put(java.lang.String, java.lang.Object) |
| */ |
| @Override |
| public Object put(String key, Object value) { |
| Object result = super.put(key, value); |
| clearCache(); |
| updateCachedOptions(); |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.HashMap#putAll(java.util.Map) |
| */ |
| @Override |
| public void putAll(Map<? extends String, ? extends Object> map) { |
| super.putAll(map); |
| clearCache(); |
| updateCachedOptions(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.HashMap#remove(java.lang.Object) |
| */ |
| @Override |
| public Object remove(Object key) { |
| Object result = super.remove(key); |
| clearCache(); |
| updateCachedOptions(); |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.HashMap#clear() |
| */ |
| @Override |
| public void clear() { |
| super.clear(); |
| clearCache(); |
| updateCachedOptions(); |
| } |
| |
| /** |
| * Update the cached options. |
| */ |
| private void updateCachedOptions() { |
| |
| // Update sort variables flag: |
| Boolean sort = (Boolean) get(OPTION_SORT_VARIABLES); |
| sortVariables = (sort != null) ? sort.booleanValue() : DEFAULT_SORT_VARIABLES; |
| |
| // Update inverse matching order flag: |
| Boolean inverse = (Boolean) get(OPTION_INVERSE_MATCHING_ORDER); |
| inverseMatchingOrder = (inverse != null) ? inverse.booleanValue() : DEFAULT_INVERSE_MATCHING_ORDER; |
| |
| // Update destroy matches flag: |
| Boolean destroy = (Boolean) get(OPTION_DESTROY_MATCHES); |
| destroyMatches = (destroy != null) ? destroy.booleanValue() : DEFAULT_DESTROY_MATCHES; |
| |
| // Update worker thread pool: |
| Number workerThreads = (Number) get(OPTION_WORKER_THREADS); |
| if (workerPool != null) { |
| workerPool.shutdownNow(); |
| workerPool = null; |
| } |
| if (workerThreads != null && workerThreads.intValue() > 0) { |
| workerPool = Executors.newFixedThreadPool(workerThreads.intValue(), PartitionThread.Factory.INSTANCE); |
| } |
| } |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.emf.henshin.interpreter.Engine#getScriptEngine() |
| */ |
| @Override |
| public ScriptEngine getScriptEngine() { |
| return scriptEngine.getEngine(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.emf.henshin.interpreter.Engine#shutdown() |
| */ |
| @Override |
| public void shutdown() { |
| if (workerPool != null) { |
| workerPool.shutdownNow(); |
| workerPool = null; |
| } |
| } |
| |
| /** |
| * Create user constraints for a node. |
| * |
| * @param node |
| * A node. |
| * @return The created user constraints. |
| */ |
| public UnaryConstraint createUserConstraints(Node node) { |
| return null; |
| } |
| |
| /** |
| * Create user constraints for an edge. |
| * |
| * @param edge |
| * An edge. |
| * @return The created user constraint. |
| */ |
| public BinaryConstraint createUserConstraints(Edge edge) { |
| return null; |
| } |
| |
| /** |
| * Create user constraints for an attribute. |
| * |
| * @param attribute |
| * An attribute. |
| * @return The created user constraint. |
| */ |
| public UnaryConstraint createUserConstraints(Attribute attribute) { |
| return null; |
| } |
| |
| } |