blob: cba00bf85ee0422f6e6ce318ebc94f4ac30de118 [file] [log] [blame]
/**
* <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.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.henshin.HenshinModelPlugin;
import org.eclipse.emf.henshin.model.Attribute;
import org.eclipse.emf.henshin.model.BinaryFormula;
import org.eclipse.emf.henshin.model.ConditionalUnit;
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.HenshinFactory;
import org.eclipse.emf.henshin.model.IteratedUnit;
import org.eclipse.emf.henshin.model.Mapping;
import org.eclipse.emf.henshin.model.MappingList;
import org.eclipse.emf.henshin.model.Module;
import org.eclipse.emf.henshin.model.NestedCondition;
import org.eclipse.emf.henshin.model.Node;
import org.eclipse.emf.henshin.model.Parameter;
import org.eclipse.emf.henshin.model.ParameterMapping;
import org.eclipse.emf.henshin.model.Rule;
import org.eclipse.emf.henshin.model.UnaryFormula;
import org.eclipse.emf.henshin.model.UnaryUnit;
import org.eclipse.emf.henshin.model.Unit;
import org.eclipse.emf.henshin.model.actions.MultiRuleMapEditor;
/**
* Utilities methods for cleaning Henshin models.
* @author Christian Krause
*/
public class HenshinModelCleaner {
/**
* Clean a module. This cleans all rules and transformation
* units inside of the module.
* @param module Module to be cleaned.
*/
public static void cleanModule(Module module) {
// Clean all units and remove invalid ones:
List<Unit> remove = new ArrayList<Unit>();
do {
remove.clear();
for (Unit unit : module.getUnits()) {
if (cleanUnit(unit)==null) {
remove.add(unit);
}
}
for (Unit unit : remove) {
module.getUnits().remove(unit);
debug("removed invalid " + unit);
}
} while (!remove.isEmpty());
// Remove superfluous meta-model imports and make sure all used packages are imported:
Set<EPackage> requiredImports = new HashSet<>();
module.eAllContents().forEachRemaining(element -> {
if (element instanceof Node) {
requiredImports.add(((Node) element).getType().getEPackage());
}
});
if (!requiredImports.isEmpty()) {
module.getImports().clear();
module.getImports().addAll(requiredImports);
}
}
/**
* Clean a transformation unit.
* @param unit Unit to be cleaned.
*/
public static Unit cleanUnit(Unit unit) {
// Clean the unit itself:
if (unit instanceof Rule) {
cleanRule((Rule) unit);
}
if (unit instanceof UnaryUnit) {
Unit subUnit = ((UnaryUnit) unit).getSubUnit();
if (subUnit==null) {
return null;
}
}
if (unit instanceof ConditionalUnit) {
ConditionalUnit cond = (ConditionalUnit) unit;
if (cond.getIf()==null || cond.getThen()==null || cond.getElse()==null) {
return null;
}
}
if (unit instanceof IteratedUnit) {
String iterations = ((IteratedUnit) unit).getIterations();
if (iterations==null || iterations.trim().length()==0) {
((IteratedUnit) unit).setIterations("1");
debug("set iterations to 1 for " + unit);
}
}
// Clean the parameter mappings:
cleanParameterMappings(unit);
// Everything ok:
return unit;
}
/**
* Clean a rule. This cleans all graphs and multi-rules in the rule including
* their mappings, formulas etc.
* @param rule Rule to be cleaned.
*/
public static void cleanRule(Rule rule) {
// Make sure the Lhs and Rhs are there:
if (rule.getLhs()==null) {
rule.setLhs(HenshinFactory.eINSTANCE.createGraph("Lhs"));
debug("added missing Lhs for " + rule);
}
if (rule.getRhs()==null) {
rule.setRhs(HenshinFactory.eINSTANCE.createGraph("Rhs"));
debug("added missing Rhs for " + rule);
}
// RHS graph should have no formula:
if (rule.getRhs().getFormula()!=null) {
rule.getRhs().setFormula(null);
debug("removed formula for Rhs of " + rule);
}
// Clean LHS and RHS:
cleanGraph(rule.getLhs());
cleanGraph(rule.getRhs());
// Clean the LHS-RHS mappings:
cleanMappingList(rule.getMappings(), rule.getLhs(), rule.getRhs());
// Clean the multi-mappings:
if (rule.isMultiRule()) {
Rule kernel = rule.getKernelRule();
cleanMappingList(rule.getMultiMappings(), kernel.getLhs(), rule.getLhs(), kernel.getRhs(), rule.getRhs());
}
else if (!rule.getMultiMappings().isEmpty()) {
rule.getMultiMappings().clear();
debug("removed unused multi-mappings of " + rule);
}
synchronizeShadowEdgesInMultiRules(rule);
// Recursively clean the multi-rules:
for (Rule multi : rule.getMultiRules()) {
cleanRule(multi);
}
// Remove unnecessary nested conditions:
for (NestedCondition cond : rule.getLhs().getNestedConditions()) {
if (cond.isTrue() || cond.isFalse()) {
rule.getLhs().removeNestedCondition(cond);
debug("removed trivial nested condition " + cond);
}
}
// Remove unnecessary multi-rules:
Iterator<Rule> iterator = rule.getMultiRules().iterator();
while (iterator.hasNext()) {
Rule multi = iterator.next();
if (canRemoveMultiRule(multi)) {
iterator.remove();
debug("removed unnecessary Multi-" + multi);
}
}
// Synchronize parameters in multi-rules:
synchronizeRuleParameters(rule);
}
private static void synchronizeShadowEdgesInMultiRules(Rule rule) {
for (Edge edge : rule.getLhs().getEdges()) {
Edge edgeRhs = rule.getMappings().getImage(edge, rule.getRhs());
if (edgeRhs == null) {
for (Rule multiRule : rule.getMultiRules()) {
Edge counterpartLhs = multiRule.getMultiMappings().getImage(edge, multiRule.getLhs());
if (counterpartLhs != null) {
Edge counterpartRhs = multiRule.getMappings().getImage(counterpartLhs, multiRule.getRhs());
if (counterpartRhs != null) {
multiRule.getRhs().removeEdge(counterpartRhs);
debug("removed superflouus edge in multi-rule " + multiRule.getName());
}
}
}
} else if (edgeRhs != null) {
for (Rule multiRule : rule.getMultiRules()) {
Edge counterpartLhs = multiRule.getMultiMappings().getImage(edge, multiRule.getLhs());
if (counterpartLhs != null) {
Edge counterpartRhs = multiRule.getMappings().getImage(counterpartLhs, multiRule.getRhs());
if (counterpartRhs == null) {
Node sourceRhs = multiRule.getMappings().getImage(counterpartLhs.getSource(),
multiRule.getRhs());
Node targetRhs = multiRule.getMappings().getImage(counterpartLhs.getTarget(),
multiRule.getRhs());
if (sourceRhs != null && targetRhs != null) {
HenshinFactory.eINSTANCE.createEdge(sourceRhs, targetRhs, counterpartLhs.getType());
debug("added missing edge in multi-rule " + multiRule.getName());
}
}
}
}
}
}
for (Edge edge : rule.getRhs().getEdges()) {
Edge edgeLhs = rule.getMappings().getOrigin(edge);
if (edgeLhs == null) {
for (Rule multiRule : rule.getMultiRules()) {
Edge counterpartRhs = multiRule.getMultiMappings().getImage(edge, multiRule.getRhs());
if (counterpartRhs != null) {
Edge counterpartLhs = multiRule.getMappings().getOrigin(counterpartRhs);
if (counterpartLhs != null) {
multiRule.getLhs().removeEdge(counterpartLhs);
debug("removed superflouus edge in multi-rule " + multiRule.getName());
}
}
}
} else if (edgeLhs != null) {
for (Rule multiRule : rule.getMultiRules()) {
Edge counterpartRhs = multiRule.getMultiMappings().getImage(edge, multiRule.getRhs());
if (counterpartRhs != null) {
Edge counterpartLhs = multiRule.getMappings().getOrigin(counterpartRhs);
if (counterpartLhs == null) {
Node sourceLhs = multiRule.getMappings().getOrigin(counterpartRhs.getSource());
Node targetLhs = multiRule.getMappings().getOrigin(counterpartRhs.getTarget());
if (sourceLhs != null && targetLhs != null) {
HenshinFactory.eINSTANCE.createEdge(sourceLhs, targetLhs, counterpartRhs.getType());
debug("added missing edge in multi-rule " + multiRule.getName());
}
}
}
}
}
}
}
public static void cleanParameters(Rule rule) {
Set<String> names = new HashSet<>();
// Collect parameter names:
rule.eAllContents().forEachRemaining(element -> {
if (element instanceof Node) {
names.add(((Node) element).getName());
}
else if (element instanceof Attribute) {
names.add(((Attribute) element).getValue());
}
else if (element instanceof Edge) {
names.add(((Edge) element).getIndex());
}
});
// Remove unknown parameters:
List<EObject> unknownParamerters = new ArrayList<>();
rule.eAllContents().forEachRemaining(element -> {
if (element instanceof Parameter) {
if (!names.contains(((Parameter) element).getName())) {
unknownParamerters.add(element);
}
}
});
for (EObject parameter : unknownParamerters) {
EcoreUtil.remove(parameter);
}
}
/**
* Clean a graph. This cleans the contents of the graph and its formula.
* It removes invalid nodes and edges and tries to simplify the formula
* is that is possible.
* @param graph Graph to be cleaned.
*/
public static void cleanGraph(Graph graph) {
// Check the nodes and whether there are illegal edges:
Iterator<Edge> edges;
Iterator<Node> nodes = graph.getNodes().iterator();
while (nodes.hasNext()) {
Node node = nodes.next();
if (node.getType()==null) {
node.setType(EcorePackage.eINSTANCE.getEObject());
debug("setting EObject node type for " + node);
}
edges = node.getOutgoing().iterator();
while (edges.hasNext()) {
Edge edge = edges.next();
if (edge.getGraph()!=graph) {
edges.remove();
debug("removed invalid " + edge);
}
}
edges = node.getIncoming().iterator();
while (edges.hasNext()) {
Edge edge = edges.next();
if (edge.getGraph()!=graph) {
edges.remove();
debug("removed invalid " + edge);
}
}
}
// Remove invalid edges:
edges = graph.getEdges().iterator();
while (edges.hasNext()) {
Edge edge = edges.next();
if (edge.getSource()==null || edge.getTarget()==null ||
edge.getSource().getGraph()!=graph || edge.getTarget().getGraph()!=graph ||
edge.getType()==null || !edge.getSource().getType().getEAllReferences().contains(edge.getType())) {
edges.remove();
debug("removed invalid " + edge);
}
}
// Clean the graph formula:
graph.setFormula(cleanFormula(graph.getFormula()));
}
/**
* Recursively clean a formula. Cleans all nested conditions and tries
* to simplify the formula.
* @param formula Formula to be cleaned.
* @return The cleaned formula (can be <code>null</code>).
*/
public static Formula cleanFormula(Formula formula) {
if (formula==null) {
return null;
}
if (formula instanceof UnaryFormula) {
Formula child = cleanFormula(((UnaryFormula) formula).getChild());
if (child==null) return null;
return formula;
}
if (formula instanceof BinaryFormula) {
BinaryFormula binary = (BinaryFormula) formula;
Formula left = cleanFormula(binary.getLeft());
Formula right = cleanFormula(binary.getRight());
if (left==null) return right;
if (right==null) return left;
return binary;
}
if (formula instanceof NestedCondition) {
NestedCondition condition = (NestedCondition) formula;
cleanNestedCondition(condition);
if (condition.isTrue()) {
return null;
}
return condition;
}
return formula;
}
/**
* Clean a nested condition. This clean the conclusion graph and the mappings.
* @param condition Nested condition to be cleaned.
*/
public static void cleanNestedCondition(NestedCondition condition) {
// Make sure the conclusion is set:
Graph conclusion = condition.getConclusion();
if (conclusion==null) {
condition.setConclusion(conclusion = HenshinFactory.eINSTANCE.createGraph());
debug("created missing conclusion graph for " + condition);
}
// Clean the conclusion graph:
cleanGraph(conclusion);
// All mappings must go from the host to the conclusion:
Graph host = condition.getHost();
if (host!=null) {
cleanMappingList(condition.getMappings(), host, conclusion);
}
}
/**
* Clean a mapping list. Removes invalid mappings.
* @param mappings Mapping list to be cleaned.
* @param signatures Signatures of the functions that the mapping list stands for.
*/
public static void cleanMappingList(MappingList mappings, Graph... graphs) {
// Build the signatures:
Map<Graph,Graph> signatures = new HashMap<Graph,Graph>();
for (int i=0; i<graphs.length; i=i+2) {
signatures.put(graphs[i], graphs[i+1]);
}
// Check which mappings are invalid:
Iterator<Mapping> contents = mappings.iterator();
while (contents.hasNext()) {
Mapping mapping = contents.next();
String msg = "removed invalid mapping " + mapping;
// Origin and image must be set:
if (mapping.getOrigin()==null || mapping.getImage()==null) {
contents.remove(); debug(msg); continue;
}
// Find out from where to where this mapping goes to:
Graph from = mapping.getOrigin().getGraph();
Graph to = mapping.getImage().getGraph();
if (from==null || to==null) {
contents.remove(); debug(msg); continue;
}
// Rule must be either same rule or multi- and kernel rule:
Rule r1 = from.getRule();
Rule r2 = to.getRule();
if (r1==null || r2==null || (r1!=r2 && r1.getKernelRule()!=r2 && r1!=r2.getKernelRule())) {
contents.remove(); debug(msg); continue;
}
// Make sure it is compliant with the signature:
if (signatures.get(from)!=to) {
contents.remove(); debug(msg); continue;
}
// Check whether the origin has a unique image in the target graph:
boolean unique = true;
for (Mapping other : mappings) {
Node image = other.getImage();
if (other!=mapping && other.getOrigin()==mapping.getOrigin() && image!=null && image.getGraph()==to) {
unique = false; break;
}
}
if (!unique) {
contents.remove(); debug(msg); continue;
}
}
}
/**
* Clean the parameter mappings of a unit. This removes all invalid parameter mappings.
* @param unit The unit to be cleaned.
*/
public static void cleanParameterMappings(Unit unit) {
// Get a list of units that the parameter mapping are allowed to use:
ArrayList<Unit> validUnits = new ArrayList<Unit>();
validUnits.add(unit);
validUnits.addAll(unit.getSubUnits(false));
// Check every single parameter mapping:
Iterator<ParameterMapping> mappings = unit.getParameterMappings().iterator();
while (mappings.hasNext()) {
ParameterMapping pm = mappings.next();
String msg = "removed invalid parameter mapping " + pm;
// Source and target must be set!
if (pm.getSource()==null || pm.getTarget()==null) {
mappings.remove(); debug(msg); continue;
}
// Units of the source and targets must be set!
if (pm.getSource().getUnit()==null || pm.getTarget().getUnit()==null) {
mappings.remove(); debug(msg); continue;
}
// The referenced units must be either the base units, or direct children:
if (!validUnits.contains(pm.getSource().getUnit()) || !validUnits.contains(pm.getTarget().getUnit())) {
mappings.remove(); debug(msg); continue;
}
}
}
/**
* Complete all multi-rules in a module. This invokes {@link #completeMultiRules(Rule)} on all
* rules directly or indirectly contained by the argument module.
* @param module A module.
*/
public static void completeMultiRules(Module module) {
for (Unit unit : module.getUnits()) {
if (unit instanceof Rule) {
completeMultiRules((Rule) unit);
}
}
for (Module subModule : module.getSubModules()) {
completeMultiRules(subModule);
}
}
/**
* Complete a multi-rule and all its directly and indirectly contained multi-rules.
* This method can be used to ensure that the multi-mappings are complete.
* @param rule Rule to be completed.
*/
public static void completeMultiRules(Rule rule) {
Rule kernel = rule.getKernelRule();
if (kernel!=null) {
MultiRuleMapEditor editor = new MultiRuleMapEditor(kernel, rule);
editor.ensureCompleteness();
}
for (Rule multiRule : rule.getMultiRules()) {
completeMultiRules(multiRule);
}
}
/*
* Check whether a multi-rule can be safely removed.
*/
private static boolean canRemoveMultiRule(Rule rule) {
// It must be a multi-rule:
if (!rule.isMultiRule()) return false;
// It must be also ok to remove its children:
for (Rule multiRule : rule.getMultiRules()) {
if (!canRemoveMultiRule(multiRule)) return false;
}
// The multi-map must be onto (surjective):
if (!rule.getMultiMappings().isOnto(rule.getLhs()) || !rule.getMultiMappings().isOnto(rule.getRhs())) {
return false;
}
// Non-trivial nested conditions?
for (NestedCondition nestedCond : rule.getLhs().getNestedConditions()) {
if (!nestedCond.isTrue()) return false;
}
// Safe to remove it:
return true;
}
/*
* Print debug messages.
*/
private static void debug(String message) {
HenshinModelPlugin.INSTANCE.logInfo("CleanUp: " + message);
}
public static void synchronizeRuleParameters(Rule rule) {
Map<String, Parameter> parameters = new LinkedHashMap<String,Parameter>();
collectRuleParameters(rule, parameters);
updateRuleParameters(rule, parameters);
}
private static void collectRuleParameters(Rule rule, Map<String,Parameter> parameters) {
for (Parameter param : rule.getParameters()) {
if (param.getName() != null) {
parameters.put(param.getName(), param);
}
}
for (Rule multiRule : rule.getMultiRules()) {
collectRuleParameters(multiRule, parameters);
}
}
private static void updateRuleParameters(Rule rule, Map<String,Parameter> parameters) {
List<Parameter> paramList = new ArrayList<Parameter>(parameters.values());
while (rule.getParameters().size() < paramList.size()) {
rule.getParameters().add(HenshinFactory.eINSTANCE.createParameter());
}
while (rule.getParameters().size() > paramList.size()) {
rule.getParameters().remove(rule.getParameters().size()-1);
}
for (int i=0; i<paramList.size(); i++) {
Parameter s = paramList.get(i);
Parameter t = rule.getParameters().get(i);
t.setName(s.getName());
t.setType(s.getType());
t.setDescription(s.getDescription());
}
for (Rule multiRule : rule.getMultiRules()) {
updateRuleParameters(multiRule, parameters);
}
}
}