blob: fe7cb524b02b212d3484bdd859875de60882c0b4 [file] [log] [blame]
package org.eclipse.emf.henshin.variability.matcher;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.henshin.interpreter.EGraph;
import org.eclipse.emf.henshin.interpreter.Match;
import org.eclipse.emf.henshin.interpreter.impl.EngineImpl;
import org.eclipse.emf.henshin.model.Attribute;
import org.eclipse.emf.henshin.model.Edge;
import org.eclipse.emf.henshin.model.GraphElement;
import org.eclipse.emf.henshin.model.Mapping;
import org.eclipse.emf.henshin.model.Node;
import org.eclipse.emf.henshin.model.Rule;
import org.eclipse.emf.henshin.variability.InconsistentRuleException;
import org.eclipse.emf.henshin.variability.util.RuleUtil;
import org.eclipse.emf.henshin.variability.wrapper.VariabilityFactory;
import org.eclipse.emf.henshin.variability.wrapper.VariabilityGraphElement;
import org.eclipse.emf.henshin.variability.wrapper.VariabilityNode;
import org.eclipse.emf.henshin.variability.wrapper.VariabilityRule;
import aima.core.logic.propositional.parsing.ast.Sentence;
/**
* Applies the algorithm described in [1] to determine all variability-aware
* matches.
*
* [1] <a href=
* "https://www.uni-marburg.de/fb12/swt/forschung/publikationen/2015/SRCT15.pdf"
* >Str�ber, Julia , Chechik, Taentzer (2015): A Variability-Based Approach to
* Reusable and Efficient Model Transformations</a>.
*
* @author Daniel Str�ber
*
*/
public class VariabilityAwareEngine {
protected Rule rule;
protected EGraph graph;
protected EngineImpl engine;
protected Map<String, Sentence> expressions;
protected RuleInfo ruleInfo;
protected RulePreparator rulePreparator;
protected List<String> initiallyTrueFeatures;
protected List<String> initiallyFalseFeatures;
/**
* Variability-based matching needs to create a new matching engine for each
* base match. Hence, if the number of base matches is too great, performance
* will suffer due to the initialization effort.
*/
protected int THRESHOLD_MAXIMUM_BASE_MATCHES = 10;
private static Map<Rule, RuleInfo> ruleInfoRegistry = new HashMap<Rule, RuleInfo>();
/**
* Creates a new engine for the execution of a rule on a graph
*
* @param rule The rule to be executed
* @param graph The graph on which the rule should be executed
* @throws InconsistentRuleException If the rule is inconsistent
*/
public VariabilityAwareEngine(Rule rule, EGraph graph) throws InconsistentRuleException {
this(rule, graph, new ArrayList<String>(), new ArrayList<String>());
}
/**
* Creates a new engine for the execution of a rule on a graph
*
* @param rule The rule to be executed
* @param graph The graph on which the rule should be executed
* @param initiallyTrue All features set to 'true'
* @param initiallyFalse All features set to 'false'
* @throws InconsistentRuleException If the rule is inconsistent
*/
public VariabilityAwareEngine(Rule rule, EGraph graph, List<String> initiallyTrue, List<String> initiallyFalse) throws InconsistentRuleException {
super();
fixInconsistencies(rule);
if(!RuleUtil.checkRule(rule)) {
throw new InconsistentRuleException();
}
this.rule = rule;
this.graph = graph;
this.engine = new EngineImpl();
this.rulePreparator = new RulePreparator(rule);
this.initiallyTrueFeatures = initiallyTrue;
this.initiallyFalseFeatures = initiallyFalse;
if (!ruleInfoRegistry.containsKey(rule))
ruleInfoRegistry.put(rule, new RuleInfo(rule));
this.ruleInfo = ruleInfoRegistry.get(rule);
populateExpressionMap();
}
private void fixInconsistencies(Rule rule) {
// Per definition, mapped nodes must have the same presence condition
// in the LHS and the RHS.
for (Mapping mapping : rule.getMappings()) {
VariabilityNode origin = VariabilityFactory.INSTANCE.createVariabilityNode(mapping.getOrigin());
VariabilityNode image = VariabilityFactory.INSTANCE.createVariabilityNode(mapping.getImage());
if (!origin.getPresenceCondition().equals(image.getPresenceCondition())) {
image.setPresenceCondition(origin.getPresenceCondition());
}
}
}
private void populateExpressionMap() {
if (ruleInfoRegistry.containsKey(rule)) {
expressions = ruleInfo.getExpressions();
}
}
public Set<VariabilityAwareMatch> findMatches() {
List<Sentence> conditions = new LinkedList<Sentence>();
conditions.addAll(expressions.values());
MatchingInfo mo = new MatchingInfo(conditions, ruleInfo, initiallyTrueFeatures, initiallyFalseFeatures);
// Remove everything except for the base rule
Set<Sentence> nonTauotologies = getNonTautologies(mo);
rulePreparator.prepare(ruleInfo, nonTauotologies, rule.isInjectiveMatching(), true);
Set<Match> baseMatches = new HashSet<Match>();
Iterator<Match> it = engine.findMatches(rule, graph, null).iterator();
while (it.hasNext()) {
if (baseMatches.size() < THRESHOLD_MAXIMUM_BASE_MATCHES) {
baseMatches.add(it.next());
} else {
baseMatches.clear();
baseMatches.add(null);
System.out.println("Too many base matches:" + rule);
break;
}
}
rulePreparator.undo();
// System.err.println(System.currentTimeMillis()-time);
Set<VariabilityAwareMatch> matches = new HashSet<VariabilityAwareMatch>();
if (!baseMatches.isEmpty()) {
mo.set(ruleInfo.getFeatureModel(), null, true);
findMatches(rule, mo, baseMatches, matches);
mo.set(ruleInfo.getFeatureModel(), true, null);
}
return matches;
}
private Set<Sentence> getNonTautologies(MatchingInfo matchingInfo) {
Set<Sentence> newImplicated = getNewImplicated(matchingInfo);
matchingInfo.setAll(newImplicated, null, true);
Set<Sentence> result = new HashSet<Sentence>(matchingInfo.getNeutrals());
result.addAll(matchingInfo.getAssumedFalse());
return result;
}
private Set<VariabilityAwareMatch> findMatches(Rule rule, MatchingInfo matchingInfo, Set<Match> baseMatches,
Set<VariabilityAwareMatch> matches) {
Sentence current = getFirstNeutral(matchingInfo);
if (current == null) {
findMatchInner(rule, matchingInfo, baseMatches, matches);
} else {
matchingInfo.set(current, null, true);
findMatchInner(rule, matchingInfo, baseMatches, matches);
matchingInfo.set(current, true, false);
findMatchInner(rule, matchingInfo, baseMatches, matches);
matchingInfo.set(current, false, null);
}
return matches;
}
private Sentence getFirstNeutral(MatchingInfo matchingInfo) {
for (Sentence e : matchingInfo.getInfo().keySet()) {
if (matchingInfo.getInfo().get(e) == null)
return e;
}
return null;
}
private Set<VariabilityAwareMatch> findMatchInner(Rule rule, MatchingInfo matchingInfo, Set<Match> baseMatches,
Set<VariabilityAwareMatch> matches) {
Set<Sentence> newContradictory = getNewContradictory(matchingInfo);
matchingInfo.setAll(newContradictory, null, false);
Set<Sentence> newImplicated = getNewImplicated(matchingInfo);
matchingInfo.setAll(newImplicated, null, true);
// If there are no presence conditions contradicting or implied by the
// current assignment (= neutral is empty), calculate the matches
// classically.
if (matchingInfo.getNeutrals().isEmpty()) {
BitSet reducedRule = rulePreparator.prepare(ruleInfo, matchingInfo.getAssumedFalse(),
determineInjectiveMatching(matchingInfo), false);
// The following check ensures that we will not match the same
// sub-rule twice.
if (!matchingInfo.getMatchedSubrules().contains(reducedRule)) {
for (Match bm : baseMatches) {
Iterator<Match> classicMatches = engine.findMatches(rule, graph, bm).iterator();
RulePreparator prep = rulePreparator.getSnapShot();
while (classicMatches.hasNext()) {
Match next = classicMatches.next();
matches.add(new VariabilityAwareMatch(next, matchingInfo.getAssumedTrue(), rule, prep));
}
}
matchingInfo.getMatchedSubrules().add(reducedRule);
}
rulePreparator.undo();
}
// Otherwise, analyse all of the remaining presence conditions,
else {
findMatches(rule, matchingInfo, baseMatches, matches);
}
// clean up
matchingInfo.setAll(newImplicated, true, null);
matchingInfo.setAll(newContradictory, false, null);
return matches;
}
private boolean determineInjectiveMatching(MatchingInfo matchingInfo) {
return (FeatureExpression.contradicts(ruleInfo.getInjectiveMatching(), getKnowledgeBase(matchingInfo)));
}
private Set<Sentence> getNewContradictory(MatchingInfo mo) {
Set<Sentence> result = new HashSet<Sentence>();
Sentence knowledge = getKnowledgeBase(mo);
for (Sentence e : mo.getNeutrals())
if (FeatureExpression.contradicts(knowledge, e))
result.add(e);
return result;
}
private Set<Sentence> getNewImplicated(MatchingInfo mo) {
Set<Sentence> result = new HashSet<Sentence>();
Sentence knowledge = getKnowledgeBase(mo);
for (Sentence e : mo.getNeutrals())
if (FeatureExpression.implies(knowledge, e))
result.add(e);
return result;
}
private Sentence getKnowledgeBase(MatchingInfo mo) {
Sentence fe = FeatureExpression.TRUE;
for (Sentence t : mo.getAssumedTrue()) {
fe = FeatureExpression.and(fe, t);
}
for (Sentence f : mo.getAssumedFalse()) {
fe = FeatureExpression.andNot(fe, f);
}
return fe;
}
private static boolean presenceConditionEmpty(GraphElement elem) {
String presenceCondition = VariabilityFactory.INSTANCE.createVariabilityGraphElement(elem).getPresenceCondition();
return (presenceCondition == null) || presenceCondition.isEmpty();
}
public static class RuleInfo {
VariabilityRule rule;
Map<String, Sentence> usedExpressions;
Map<Sentence, Set<GraphElement>> pc2elem;
Map<Node, Set<Mapping>> node2Mapping;
Sentence featureModel;
Sentence injectiveMatching;
public RuleInfo(Rule rule) {
this.rule = VariabilityFactory.INSTANCE.createVariabilityRule(rule);
this.featureModel = FeatureExpression.getExpr(this.rule.getFeatureModel());
String injective = this.rule.getInjectiveMatchingPresenceCondition();
if (injective == null)
injective = rule.isInjectiveMatching() + "";
this.injectiveMatching = FeatureExpression.getExpr(injective);
populateMaps();
}
public RuleInfo(VariabilityRule rule) {
this.rule = rule;
this.featureModel = FeatureExpression.getExpr(this.rule.getFeatureModel());
String injective = this.rule.getInjectiveMatchingPresenceCondition();
if (injective == null)
injective = rule.isInjectiveMatching() + "";
this.injectiveMatching = FeatureExpression.getExpr(injective);
populateMaps();
}
public Map<Sentence, Set<GraphElement>> getPc2Elem() {
return pc2elem;
}
public Map<String, Sentence> getExpressions() {
return usedExpressions;
}
public Sentence getFeatureModel() {
return featureModel;
}
public void populateMaps() {
usedExpressions = new HashMap<String, Sentence>();
node2Mapping = new HashMap<Node, Set<Mapping>>();
pc2elem = new HashMap<Sentence, Set<GraphElement>>();
TreeIterator<EObject> it = rule.eAllContents();
while (it.hasNext()) {
EObject o = it.next();
if (o instanceof Node || o instanceof Edge || o instanceof Attribute) {
VariabilityGraphElement g = VariabilityFactory.INSTANCE.createVariabilityGraphElement((GraphElement) o);
if (!presenceConditionEmpty(g)) {
String pc = g.getPresenceCondition();
Sentence expr = FeatureExpression.getExpr(pc);
usedExpressions.put(pc, expr);
if (!pc2elem.containsKey(expr))
pc2elem.put(expr, new HashSet<GraphElement>());
pc2elem.get(expr).add((GraphElement) o);
}
}
if (o instanceof Mapping) {
Mapping m = (Mapping) o;
Node image = m.getImage();
Set<Mapping> set = node2Mapping.get(image);
if (set == null) {
set = new HashSet<Mapping>();
node2Mapping.put(image, set);
}
set.add(m);
Node origin = m.getOrigin();
set = node2Mapping.get(origin);
if (set == null) {
set = new HashSet<Mapping>();
node2Mapping.put(origin, set);
}
set.add(m);
}
}
if (featureModel != null && !featureModel.equals("")) {
if (!pc2elem.containsKey(featureModel))
pc2elem.put(featureModel, new HashSet<GraphElement>());
}
}
public Map<Node, Set<Mapping>> getNode2Mapping() {
return node2Mapping;
}
public Sentence getInjectiveMatching() {
return injectiveMatching;
}
}
public static class MatchingInfo {
private Map<Sentence, Boolean> info = new LinkedHashMap<Sentence, Boolean>();
private Set<Sentence> assumedTrue = new HashSet<Sentence>();
private Set<Sentence> assumedFalse = new HashSet<Sentence>();
private Set<Sentence> neutrals = new HashSet<Sentence>();
private Set<BitSet> matchedSubRules = new HashSet<BitSet>();
public MatchingInfo(List<Sentence> conditions, RuleInfo ruleInfo, List<String> initiallyTrue, List<String> initiallyFalse) {
for (Sentence expr : conditions) {
info.put(expr, null);
}
assumedTrue.add(ruleInfo.getFeatureModel());
initiallyTrue.forEach(f -> assumedTrue.add(FeatureExpression.getExpr(f)));
initiallyFalse.forEach(f -> assumedFalse.add(FeatureExpression.getExpr(f)));
neutrals.addAll(conditions);
}
public Set<BitSet> getMatchedSubrules() {
return matchedSubRules;
}
public void setAll(Collection<Sentence> exprs, Boolean old, Boolean new_) {
for (Sentence expr : exprs) {
set(expr, old, new_);
}
}
public void set(Sentence expr, Boolean old, Boolean new_) {
if (old == null) {
neutrals.remove(expr);
} else {
if (old)
assumedTrue.remove(expr);
else
assumedFalse.remove(expr);
}
if (new_ == null) {
neutrals.add(expr);
} else {
if (new_)
assumedTrue.add(expr);
else
assumedFalse.add(expr);
}
info.put(expr, new_);
}
public Set<Sentence> getAssumedTrue() {
return assumedTrue;
}
public Set<Sentence> getAssumedFalse() {
return assumedFalse;
}
public Map<Sentence, Boolean> getInfo() {
return info;
}
public Set<Sentence> getNeutrals() {
return neutrals;
}
}
}