/** * <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.model.actions; import static org.eclipse.emf.henshin.model.Action.Type.CREATE; import static org.eclipse.emf.henshin.model.Action.Type.DELETE; import static org.eclipse.emf.henshin.model.Action.Type.FORBID; import static org.eclipse.emf.henshin.model.Action.Type.PRESERVE; import static org.eclipse.emf.henshin.model.Action.Type.REQUIRE; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.henshin.HenshinModelPlugin; import org.eclipse.emf.henshin.model.Action; import org.eclipse.emf.henshin.model.Action.Type; import org.eclipse.emf.henshin.model.Attribute; import org.eclipse.emf.henshin.model.Graph; import org.eclipse.emf.henshin.model.GraphElement; import org.eclipse.emf.henshin.model.HenshinFactory; import org.eclipse.emf.henshin.model.MappingList; import org.eclipse.emf.henshin.model.NestedCondition; import org.eclipse.emf.henshin.model.Rule; import org.eclipse.emf.henshin.model.util.HenshinModelCleaner; /** * Generic action helper class. * @author Christian Krause */ public abstract class GenericActionHelper<E extends GraphElement,C extends EObject> implements ActionHelper<E,C> { /* * (non-Javadoc) * @see org.eclipse.emf.henshin.diagram.edit.actions.ActionHelper#getAction(java.lang.Object) */ public Action getAction(E element) { // Get the graph and the rule: Graph graph = element.getGraph(); if (graph==null) { return null; } Rule rule = graph.getRule(); if (rule==null) { return null; } // Get the kernel rule, if existing: Rule kernel = rule.getKernelRule(); // Check if the element is amalgamated: boolean isMulti = isMulti(element); // Get the path: String[] multiPath = isMulti ? getMultiPath(element, rule) : null; // If the rule is a multi-rule, but the action is not // a multi-action, the element is not an action element. if (kernel!=null && !isMulti) { return null; } // Map editor. MapEditor<E> editor; // LHS element? if (graph==rule.getLhs()) { // Try to get the image in the RHS: editor = getMapEditor(rule.getRhs()); E image = editor.getOpposite(element); // Check if it is mapped to the RHS: if (image!=null) { return new Action(PRESERVE, isMulti, multiPath); } else { return new Action(DELETE, isMulti, multiPath); } } // RHS element? else if (graph==rule.getRhs()) { // Try to get the origin in the LHS: editor = getMapEditor(rule.getRhs()); E origin = editor.getOpposite(element); // If it has an origin in the LHS, it is a CREATE-action: if (origin==null) { return new Action(CREATE, isMulti, multiPath); } } // PAC/NAC element? else if (graph.eContainer() instanceof NestedCondition) { // Find out whether it is a PAC, a NAC or something else: NestedCondition nc = (NestedCondition) graph.eContainer(); Type type = null; if (nc.isPAC()) { type = REQUIRE; } else if (nc.isNAC()) { type = FORBID; } // If we know the type, we can continue: if (type!=null) { // Try to get the origin in the LHS: editor = getMapEditor(graph); E origin = editor.getOpposite(element); // If it has an origin in the LHS, it is a PAC/NAC-action: if (origin==null) { return new Action(type, isMulti, multiPath, graph.getName()); } } } // At this point we know it is not considered as an action element. return null; } /* * (non-Javadoc) * @see org.eclipse.emf.henshin.diagram.edit.actions.ActionHelper#setAction(java.lang.Object, org.eclipse.emf.henshin.diagram.edit.actions.Action) */ public void setAction(E element, Action newAction) { // Check the current action. Action oldAction = getAction(element); if (oldAction==null) return; // illegal if (newAction.equals(oldAction)) return; // nothing to do Type oldType = oldAction.getType(); Type newType = newAction.getType(); // Get the container graph and rule. Graph graph = element.getGraph(); Rule rule = graph.getRule(); // Map editor. MapEditor<E> editor; // Current action type = PRESERVE? if (oldType==PRESERVE) { // We know that the element is contained in the LHS and that it is mapped to a node in the RHS. editor = getMapEditor(rule.getRhs()); E image = editor.getOpposite(element); // For DELETE actions, delete the image in the RHS: if (newType==DELETE) { editor.remove(image); } // For CREATE actions, replace the image in the RHS by the origin: else if (newType==CREATE) { editor.replace(image); } // For REQUIRE / FORBID actions, delete the image in the RHS and move the node to the AC: else if (newType==REQUIRE || newType==FORBID) { // Remove the image in the RHS: editor.remove(image); // Move the node to the AC: NestedCondition ac = getOrCreateAC(newAction, rule); editor = getMapEditor(ac.getConclusion()); editor.move(element); } } // Current action type = CREATE? else if (oldType==CREATE) { // We know that the element is contained in the RHS and that it is not an image of a mapping. editor = getMapEditor(rule.getRhs()); // We move the element to the LHS if the action type has changed: if (newType!=CREATE) { editor.move(element); } // For NONE actions, create a copy of the element in the RHS and map to it: if (newType==PRESERVE) { editor.copy(element); } // For REQUIRE / FORBID actions, move the element further to the AC: else if (newType==REQUIRE || newType==FORBID) { NestedCondition ac = getOrCreateAC(newAction, rule); editor = getMapEditor(ac.getConclusion()); editor.move(element); } } // Current action type = DELETE? else if (oldType==DELETE) { // We know that the element is contained in the LHS and that it has no image in the RHS. editor = getMapEditor(rule.getRhs()); // For PRESERVE actions, create a copy of the element in the RHS and map to it: if (newType==PRESERVE) { editor.copy(element); } // For CREATE actions, move the element to the RHS: else if (newType==CREATE) { editor.move(element); } // For FORBID actions, move the element to the NAC: else if (newType==REQUIRE || newType==FORBID) { NestedCondition ac = getOrCreateAC(newAction, rule); editor = getMapEditor(ac.getConclusion()); editor.move(element); } } // Current action type = REQUIRE or FORBID? else if ((oldType==REQUIRE || oldType==FORBID) && (oldType!=newType || !oldAction.hasSameFragment(newAction))) { // We know that the element is contained in a AC and that it has no origin in the LHS. NestedCondition ac = (NestedCondition) graph.eContainer(); editor = getMapEditor(ac.getConclusion()); // We move the element to the LHS in any case: editor.move(element); // For PRESERVE actions, create a copy in the RHS as well: if (newType==PRESERVE) { editor = getMapEditor(rule.getRhs()); editor.copy(element); } // For CREATE actions, move the element to the RHS: else if (newType==CREATE) { editor = getMapEditor(rule.getRhs()); editor.move(element); } // For REQUIRE and FORBID actions, move the element to the new AC: else if (newType==REQUIRE || newType==FORBID) { NestedCondition newAc = getOrCreateAC(newAction, rule); editor = getMapEditor(newAc.getConclusion()); editor.move(element); } } // THE ACTION TYPE AND THE FRAGMENT ARE CORRECT NOW. // Update the current action: oldAction = getAction(element); // Is the old action a multi-action? if (oldAction.isMulti()) { // If the new one is not a multi-action, move the element up to the root rule: if (!newAction.isMulti()) { moveMultiElement(rule, rule.getRootRule(), newAction, element); } // Does the new action have a different path? (it IS a multi-action) else if (!oldAction.hasSamePath(newAction)) { // Find the common sub-path: String[] common = getCommonPath(oldAction, newAction); // If they are completely different, move it up to the root rule: if (common.length==0) { moveMultiElement(rule, rule.getRootRule(), newAction, element); } // Otherwise move it to the common parent rule: else { Action action = new Action(oldAction.getType(), true, common); Rule multi = getOrCreateMultiRule(rule.getRootRule(), action); moveMultiElement(rule, multi, newAction, element); } } } // Update the current action: oldAction = getAction(element); // Still not the same? if (oldAction!=null && !oldAction.equals(newAction)) { // Then find the new target multi-rule and move the element there: Rule multi = getOrCreateMultiRule(rule.getRootRule(), newAction); moveMultiElement(element.getGraph().getRule(), multi, newAction, element); } // NOW EVERYTHING SHOULD BE CORRECT. if (!newAction.equals(getAction(element))) { HenshinModelPlugin.INSTANCE.logWarning("Failed to set action for " + element + " (got " + getAction(element) + " instead of " + newAction, null); } // CLEAN UP: HenshinModelCleaner.cleanRule(rule.getRootRule()); } /* * Get the common start of the path of two actions. */ private static String[] getCommonPath(Action a1, Action a2) { List<String> path = new ArrayList<String>(); String[] p1 = a1.getPath(); String[] p2 = a2.getPath(); int max = Math.min(p1.length, p2.length); for (int i=0; i<max; i++) { if (p1[i].equals(p2[i])) { path.add(p1[i]); } else break; } return path.toArray(new String[0]); } /* * Move an element either from a (multi-) rule to another (multi-) rule. */ private void moveMultiElement(Rule rule1, Rule rule2, Action action, E element) { // Nothing to do? if (rule1==rule2) return; if (EcoreUtil.isAncestor(rule2, rule1)) { moveMultiElement(rule2, rule1, action, element); return; } // Now we know that rule2 is a direct or indirect child of rule1. // Build the rule chain (from rule1 to rule2): List<Rule> ruleChain = new ArrayList<Rule>(); Rule rule = rule2; ruleChain.add(rule); while (rule!=rule1 && rule!=null) { rule = rule.getKernelRule(); if (rule!=null) { ruleChain.add(0, rule); } } // Find out from where to where we need to move the element: if (element.getGraph().getRule()==rule1) { // correct order already } else if (element.getGraph().getRule()==rule2) { Collections.reverse(ruleChain); // reverse the order } else { return; // something is wrong, so we stop } // The element is in the first rule of the rule chain. // Now move the element: Type actionType = action.getType(); for (int i=1; i<ruleChain.size(); i++) { // The two 'adjacent' rules: Rule r1 = ruleChain.get(i-1); Rule r2 = ruleChain.get(i); // Which one is the kernel rule, which the multi-rule? Rule kernel, multi; if (r2.getKernelRule()==r1) { kernel = r1; multi = r2; } else { kernel = r2; multi = r1; } // Decide what and how to move the element: if (actionType==CREATE) { getMapEditor(kernel.getRhs(), multi.getRhs(), multi.getMultiMappings()).move(element); } else if (actionType==DELETE) { getMapEditor(kernel.getLhs(), multi.getLhs(), multi.getMultiMappings()).move(element); } else if (actionType==PRESERVE) { new MultiRuleMapEditor(kernel, multi).moveMappedElement(element); } else if (actionType==FORBID || actionType==REQUIRE) { NestedCondition kernelAC = getOrCreateAC(kernel, action.getFragment(), actionType==REQUIRE); NestedCondition currentAC = getOrCreateAC(multi, action.getFragment(), actionType==REQUIRE); new ConditionElemMapEditor(kernelAC, currentAC).moveConditionElement(element); } } } /* private void replaceNodeInMappings(Node oldNode, Node newNode) { Iterator<EObject> it = newNode.getGraph().getRule().getRootRule().eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof Mapping) { Mapping m = (Mapping) obj; if (m.getOrigin()==oldNode) { m.setOrigin(newNode); } else if (m.getImage()==oldNode) { m.setImage(newNode); } } } } */ /* * Create a new map editor for a given target graph. */ protected abstract MapEditor<E> getMapEditor(Graph target); /* * Create a new map editor for a given source, target graph and mappings. */ protected abstract MapEditor<E> getMapEditor(Graph source, Graph target, MappingList mappings); /* * Returns a list of all elements of <code>elements</code>, which are * associated with the given <code>action</code>. If <code>action</code> is * null, the returned list contains all elements of the given list. */ protected List<E> filterElementsByAction(List<E> elements, Action action) { // Collect all matching elements: List<E> result = new ArrayList<E>(); for (E element : elements) { // Check if the current action is ok and add it: Action current = getAction(element); if (current!=null && (action==null || action.equals(current))) { result.add(element); } } return result; } /* * Helper method for checking whether the action of an element * is a multi-action. */ private boolean isMulti(E element) { GraphElement elem; if (element instanceof Attribute) { elem = ((Attribute) element).getNode(); } else if (element instanceof GraphElement) { elem = (GraphElement) element; } else { return false; } Graph graph = elem.getGraph(); if (graph==null) { return false; } Rule rule = graph.getRule(); if (rule==null || rule.getKernelRule()==null) { return false; } if (rule.getMultiMappings().getOrigin(element)!=null) { return false; } return true; } /* * If an element has a multi-action, this method * returns the proper path for the multi-action. */ private String[] getMultiPath(E element, Rule multiRule) { if (!isMulti(element)) { return null; } List<String> path = new ArrayList<String>(); while (multiRule.isMultiRule()) { String name = multiRule.getName(); path.add(name==null ? "" : name.trim()); multiRule = multiRule.getKernelRule(); } if (path.size()==1 && path.get(0).length()==0) { return new String[] {}; } Collections.reverse(path); return path.toArray(new String[0]); } private Rule getOrCreateMultiRule(Rule root, Action action) { // Must be a multi-action: if (!action.isMulti()) { return null; } // Get the names of the multi-rules (must be a modifiable list): List<String> path = new ArrayList<String>(Arrays.asList(action.getPath())); if (path.isEmpty()) { path.add(null); } // Find or create the multi-rules: Rule rule = root.getRootRule(); // really make sure we start with the root rule for (String name : path) { Rule multi = rule.getMultiRule(name); if (multi==null) { multi = HenshinFactory.eINSTANCE.createRule(name); if (name==null || name.trim().length()==0) { rule.getMultiRules().add(0, multi); } else { rule.getMultiRules().add(multi); } } // Ensure completeness: new MultiRuleMapEditor(rule, multi).ensureCompleteness(); rule = multi; } return rule; } /** * Find or create a positive or a negative application condition. * @param action FORBID/REQUIRE action * @param rule Rule * @return the application condition. */ protected NestedCondition getOrCreateAC(Action action, Rule rule) { // Check if the action type is ok: if (action.getType() != FORBID && action.getType() != REQUIRE) { throw new IllegalArgumentException("Application conditions can be created only for REQUIRE/FORBID actions"); } // Get the name of the application condition: String name = action.getFragment(); // Find or create the application condition: return getOrCreateAC(rule, name, action.getType()==REQUIRE); } protected NestedCondition getOrCreateAC(Rule rule, String name, boolean isPAC) { NestedCondition ac = isPAC ? rule.getLhs().getPAC(name) : rule.getLhs().getNAC(name); if (ac==null) { ac = isPAC ? rule.getLhs().createPAC(name) : rule.getLhs().createNAC(name); } return ac; } }