blob: 02617d23250ede55ebd450f68e92eb0ec9db11ea [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.interpreter.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.impl.DynamicEObjectImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.henshin.interpreter.ApplicationMonitor;
import org.eclipse.emf.henshin.interpreter.Assignment;
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.RuleApplication;
import org.eclipse.emf.henshin.interpreter.UnitApplication;
import org.eclipse.emf.henshin.interpreter.impl.AssignmentImpl;
import org.eclipse.emf.henshin.interpreter.impl.EGraphImpl;
import org.eclipse.emf.henshin.interpreter.impl.UnitApplicationImpl;
import org.eclipse.emf.henshin.model.Module;
import org.eclipse.emf.henshin.model.Node;
import org.eclipse.emf.henshin.model.Parameter;
import org.eclipse.emf.henshin.model.ParameterKind;
import org.eclipse.emf.henshin.model.Rule;
import org.eclipse.emf.henshin.model.Unit;
/**
* Common utility methods for the Henshin interpreter.
*
* @author Christian Krause, Svetlana Arifulina
*/
public class InterpreterUtil {
/**
* Class Loader implementation that can delegate the class loading to multiple additional class loaders. <br>
* <b>WARNING: This class loader delegates only the methods {@link #loadClass(String)}!</b>
*
* @author Christian Krause
*
*/
public static class DelegatingClassLoader extends ClassLoader {
/**
* Array of additional class loaders.
*/
private ClassLoader[] additionalClassLoaders;
/**
* Default constructor.
*
* @param parent Parent class loader.
* @param additionalClassLoaders Additional class loaders.
*/
public DelegatingClassLoader(ClassLoader parent, ClassLoader... additionalClassLoaders) {
super(parent);
this.additionalClassLoaders = additionalClassLoaders;
}
/*
* (non-Javadoc)
*
* @see java.lang.ClassLoader#loadClass(java.lang.String)
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz;
try {
clazz = super.loadClass(name);
} catch (ClassNotFoundException e) {
clazz = null;
}
if (clazz == null) {
for (ClassLoader other : additionalClassLoaders) {
try {
clazz = other.loadClass(name);
break;
} catch (ClassNotFoundException e) {
clazz = null;
}
}
}
if (clazz == null) {
throw new ClassNotFoundException();
}
return clazz;
}
}
/**
* Find all matches for a rule.
*
* @param engine Engine.
* @param rule Rule to be matched.
* @param graph Target graph.
* @param partialMatch Partial match or <code>null</code>.
* @return List of found matches.
*/
public static List<Match> findAllMatches(Engine engine, Rule rule, EGraph graph, Match partialMatch) {
List<Match> matches = new ArrayList<Match>();
for (Match match : engine.findMatches(rule, graph, partialMatch)) {
matches.add(match);
}
return matches;
}
/**
* Find all matches of all rules in a module. This does not consider submodules.
*
* @param engine Engine to be used.
* @param module Module to be used.
* @param graph Target graph.
* @return List of matches.
*/
public static List<Match> findAllMatches(Engine engine, Module module, EGraph graph) {
List<Match> matches = new ArrayList<Match>();
for (Unit unit : module.getUnits()) {
if (unit instanceof Rule) {
matches.addAll(findAllMatches(engine, (Rule) unit, graph, null));
}
}
return matches;
}
/**
* This method calculates a complete match for rules of the given model transformation with a given model. If a
* complete match does not exist, the method calculates a partial match, if it exists. For that a rule is reduced
* till a complete match for a reduced rule is found. For reduced rules having the same size, all matches are
* searched. If a match is found for a reduced rule, this rule is not reduced further.
*
* @param engine Engine to be used.
* @param module Module to be used.
* @param graph Target graph.
* @return List of matches (both complete and partial per rule)
*/
public static List<Match> findMaximalPartialMatches(Engine engine, Module module, EGraph graph) {
List<Match> matches = new ArrayList<Match>();
int reductionDepth = 2;
for (Unit unit : module.getUnits()) {
if (unit instanceof Rule) {
List<Match> newMatches = new ArrayList<Match>();
// Check for a complete match
newMatches = findAllMatches(engine, (Rule) unit, graph, null);
// If there are no complete matches, check for partial matches
if (newMatches.isEmpty()) {
newMatches = findPartialMatchesPerRule((Rule) unit, engine, graph, reductionDepth);
}
// If any partial matches were found then remove empty and
// duplicate matches, which arise during the rule reduction
if (!newMatches.isEmpty()) {
matches.addAll(removeEmptyAndDuplicatedMatches(newMatches));
}
}
}
return matches;
}
/**
* This method is similar to the method findMasimalPartialMatches but outputs a report instead of a list of matches.
*
* This method calculates a complete match for rules of the given model transformation with a given model. If a
* complete match does not exist, the method calculates a partial match, if it exists. For that a rule is reduced
* till a complete match for a reduced rule is found. For reduced rules having the same size, all matches are
* searched. If a match is found for a reduced rule, this rule is not reduced further. If partial matches are found,
* then a report object is created based on the list of found partial matches.
*
* @param engine Engine to be used.
* @param module Module to be used.
* @param graph Target graph.
* @return List of matches (both complete and partial per rule)
*/
public static PartialMatchReport findAndReportMaximalPartialMatches(Engine engine, Module module, EGraph graph) {
List<Match> matches = new ArrayList<Match>();
PartialMatchReport partialMatchReport = new PartialMatchReport(module, matches);
int reductionDepth = 2;
for (Unit unit : module.getUnits()) {
if (unit instanceof Rule) {
List<Match> newMatches = new ArrayList<Match>();
// Check for a complete match
newMatches = findAllMatches(engine, (Rule) unit, graph, null);
// Check whether disabling of the dangling edges check would
// lead to a match
if (newMatches.isEmpty()) {
// Check for partial matches
newMatches = findPartialMatchesPerRule((Rule) unit, engine, graph, reductionDepth);
}
if (!newMatches.isEmpty()) {
newMatches = removeEmptyAndDuplicatedMatches(newMatches);
}
partialMatchReport.collectPartialMatchInfos((Rule) unit, newMatches);
matches.addAll(newMatches);
}
}
return partialMatchReport;
}
/**
* Method for removing empty and duplicated matches, which appear during the rule reduction.
*
* @param matches Matches to reduce
* @return A reduced list of matches without duplicates and empty entries
*/
private static List<Match> removeEmptyAndDuplicatedMatches(List<Match> matches) {
List<Match> resultingMatches = new ArrayList<Match>();
// When computing partial matches, matches with no matching elements are
// sometimes added into the resulting list of matches. Here these
// matches are deleted from the list.
for (Match match : matches) {
if (!match.getNodeTargets().isEmpty()) {
boolean addMatch = true;
// Ignore duplicates
for (Match resultingMatch : resultingMatches) {
if (match.getNodeTargets().containsAll(resultingMatch.getNodeTargets())
& resultingMatch.getNodeTargets().containsAll(match.getNodeTargets())) {
addMatch = false;
break;
}
}
if (addMatch) {
resultingMatches.add(match);
}
}
}
return resultingMatches;
}
/**
* Establishes that the set of multi-matches for the given multi-rule
* is overlap-free, i.e., no object in the input model is a node target
* of more than one multi-match.
*
* @param match match with a list of multi-matches for the given multi-rule
* @param multiRule multi-rule
* @return A reduced list of matches without duplicate target nodes
*/
public static List<Match> removeOverlappingMultiMatches(Match match, Rule multiRule) {
List<Match> remainingMatches = new ArrayList<Match>();
for (Match m : match.getMultiMatches(multiRule)) {
boolean addMatch = true;
for (Match resultingMatch : remainingMatches) {
// Compare node targets
for (EObject eo : resultingMatch.getNodeTargets()) {
if (m.getNodeTargets().contains(eo)) {
addMatch = false;
break;
}
}
if (!addMatch) {
break;
}
}
if (addMatch) {
remainingMatches.add(m);
}
}
return remainingMatches;
}
/**
* This method finds a partial match per rule from the given module or for an already reduced rule.
*
* @param rule Rule to find partial matches for.
* @param engine Engine to be used.
* @param graph Target graph.
* @return The list of partial matches.
*/
private static List<Match> findPartialMatchesPerRule(Rule rule, Engine engine, EGraph graph, int reductionDepth) {
List<Match> matches = new ArrayList<Match>();
if (reductionDepth > 0) {
// Reduce the rule and get as a result a list of rules, each having 1 node less and the input rule
List<Rule> newRules = reduceRule(rule);
reductionDepth--;
// Find matches for the reduced rules
for (Rule newRule : newRules) {
matches.addAll(findAllMatches(engine, newRule, graph, null));
}
// If no matches for the reduced rules were found, call the method recursively for these reduced rules
// Always goes down to reduction depth 0
if (reductionDepth > 0 && matches.isEmpty()) {
for (Rule newRule : newRules) {
matches.addAll(findPartialMatchesPerRule(newRule, engine, graph, reductionDepth));
}
reductionDepth--;
}
}
// if a rule has only 1 node than no reduction possible and null is
// returned
return matches;
}
/**
* Reduce the input rule and output a list of rules, in which each rule is exactly 1 node smaller than the input
* rule. The size of the list equals to the number of nodes of the input rule.
*
* @param rule Rule to reduce.
* @return List of reduced rules.
*/
private static List<Rule> reduceRule(Rule rule) {
// If the input rule has N nodes in the LHS, then output a set of N
// rules, in which each rule is exactly 1 node less that the input rule
// has (the size of each rule in the set is N-1)
List<Rule> newRules = new ArrayList<Rule>();
EList<Node> nodes = rule.getLhs().getNodes();
// Copy all the nodes from the input rule in the N rules of the output
// set
for (int i = 0; i < nodes.size(); i++) {
// Create a new reduced rule by copying the original one
Rule newRule = EcoreUtil.copy(rule);
// Remove the current node from the reduced rule
newRule.getLhs().removeNode(newRule.getLhs().getNodes().get(i));
// Add the new rule to the list of new rules
newRules.add(newRule);
}
return newRules;
}
/**
* Execute the given unit application and throws an {@link AssertionError} if it could not be successfully applied
* (if {@link UnitApplication#execute(ApplicationMonitor)} returns <code>false</code>). This is just a convenience
* method.
*
* @param application A unit application.
*/
public static void executeOrDie(UnitApplication application) {
if (!application.execute(null)) {
if (application instanceof RuleApplication) {
throw new AssertionError("Error executing rule '" + application.getUnit().getName() + "'");
} else {
throw new AssertionError("Error executing unit '" + application.getUnit().getName() + "'");
}
}
}
/**
* Apply a unit to the contents of a resource. This automatically creates an {@link EGraph} and updates the contents
* of the resource.
*
* @param unit Unit to be applied.
* @param engine Engine to be used.
* @param resource Resource containing the model to be transformed.
* @return <code>true</code> if the unit was successfully applied.
*/
public static boolean applyToResource(Unit unit, Engine engine, Resource resource) {
return applyToResource(new AssignmentImpl(unit), engine, resource, null);
}
/**
* Apply a unit to the contents of a resource. This automatically creates an {@link EGraph} and updates the contents
* of the resource.
*
* @param assignment Assignment to be used.
* @param engine Engine to be used.
* @param resource Resource containing the model to be transformed.
* @return <code>true</code> if the unit was successfully applied.
*/
public static boolean applyToResource(Assignment assignment, Engine engine, Resource resource,
ApplicationMonitor monitor) {
// Create the graph and the unit application:
EGraph graph = new EGraphImpl(resource);
UnitApplication application = new UnitApplicationImpl(engine, graph, assignment.getUnit(), assignment);
// Remember the old root objects:
Set<EObject> oldRoots = new HashSet<EObject>();
oldRoots.addAll(graph.getRoots());
// Apply the unit:
boolean result = application.execute(monitor);
// Sync root objects:
List<EObject> roots = graph.getRoots();
Iterator<EObject> it = resource.getContents().iterator();
while (it.hasNext()) {
if (!roots.contains(it.next())) {
it.remove();
}
}
for (EObject root : roots) {
if (!oldRoots.contains(root)) {
resource.getContents().add(root);
}
}
return result;
}
/**
* Check whether two {@link EGraph}s are isomorphic.
*
* @param graph1 First graph.
* @param graph2 Second graph.
* @return <code>true</code> if they are isomorphic.
*/
public static boolean areIsomorphic(EGraph graph1, EGraph graph2) {
return new EGraphIsomorphyChecker(graph1, null).isIsomorphicTo(graph2, null);
}
/**
* Check whether the contents of two resources are isomorphic.
*
* @param resource1 First resource.
* @param resource2 Second resource.
* @return <code>true</code> if they are isomorphic.
*/
public static boolean areIsomorphic(Resource resource1, Resource resource2) {
return areIsomorphic(new EGraphImpl(resource1), new EGraphImpl(resource2));
}
/**
* Count the number of edges/links in a graph.
*
* @param graph An {@link EGraph}
* @return Number of edges/links in that graph.
*/
public static int countEdges(EGraph graph) {
int links = 0;
for (EObject object : graph) {
for (EReference ref : object.eClass().getEAllReferences()) {
if (ref.isMany()) {
links += ((EList<?>) object.eGet(ref)).size();
} else {
if (object.eGet(ref) != null)
links++;
}
}
}
return links;
}
/**
* Get a string representation of an object.
*
* @param object An object.
* @return A readable string representation.
*/
public static String objectToString(Object object) {
if (object instanceof String) {
return "'" + object + "'";
}
if (object instanceof DynamicEObjectImpl) {
EClass eclass = ((DynamicEObjectImpl) object).eClass();
if (eclass != null) {
String type = eclass.getName();
EPackage epackage = eclass.getEPackage();
while (epackage != null) {
type = epackage.getName() + "." + type;
epackage = epackage.getESuperPackage();
}
String args = "";
for (EAttribute att : eclass.getEAllAttributes()) {
args = args + ", " + att.getName() + "=" + objectToString(((DynamicEObjectImpl) object).eGet(att));
}
return type + "@" + Integer.toHexString(object.hashCode()) + " (dynamic" + args + ")";
}
}
return String.valueOf(object); // object could be null
}
/**
* Finds a single {@link Match} for a {@link Rule}.
*
* @param engine The {@link Engine}.
* @param rule Rule to be matched.
* @param graph Target graph.
* @param partialMatch Partial match or <code>null</code>.
* @return a {@link Match} or <code>null</code>.
*/
public Match findSingleMatch(Engine engine, Rule rule, EGraph graph, Match partialMatch){
Match match = null;
Iterable<Match> iterable = engine.findMatches(rule, graph, partialMatch);
try {
match = iterable.iterator().next();
} catch (NoSuchElementException e) {
// no matches.
}
return match;
}
/**
* Check whether the {@link Rule} is applicable on the {@link EGraph}.
*
* @param engine The {@link Engine}.
* @param rule Rule to be matched.
* @param graph Target graph.
* @param partialMatch Partial match or <code>null</code>.
* @return <code>true</code> if the <code>rule<code> is applicable on the <code>graph<code>.
*/
public boolean isApplicable(Engine engine, Rule rule, EGraph graph, Match partialMatch){
Match match = this.findSingleMatch(engine, rule, graph, partialMatch);
return (match != null) ? true : false;
}
/**
* Check whether all {@link Parameter} of {@link ParameterKind} IN and INOUT of the {@link Unit} are set.
*
* @param parameters All {@link Parameter} of the unit.
* @param unitName The name of the {@link Unit}.
* @param assignment The current {@link Assignment} of the {@link UnitApplication}.
*/
public static void areNecessaryParametersSet(Iterable<Parameter> parameters, String unitName,
Assignment assignment) throws IllegalStateException {
for (Parameter param : parameters) {
ParameterKind kind = param.getKind();
if((kind == ParameterKind.INOUT || kind == ParameterKind.IN) &&
(assignment == null || assignment.getParameterValue(param) == null)) {
throw new IllegalStateException(unitName + ": " + kind + " Parameter " + param.getName() + " not set");
}
}
}
}