/* FeatureIDE - An IDE to support feature-oriented software development
* Copyright (C) 2005-2009 FeatureIDE Team, University of Magdeburg
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* See http://www.fosd.de/featureide/ for further information.
*/
package featureide.fm.core.editing;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import org.prop4j.And;
import org.prop4j.Literal;
import org.prop4j.Node;
import org.prop4j.Not;
import org.prop4j.Or;
import org.prop4j.SatSolver;
import org.sat4j.specs.TimeoutException;
import featureide.fm.core.Feature;
import featureide.fm.core.FeatureModel;
import featureide.fm.core.configuration.Configuration;
public class ModelComparator {
private long timeout;
private enum Strategy {WithoutIdenticalRules, SingleTesting, SingleTestingAborted};
private Set<Strategy> strategy = new HashSet<Strategy>();
private FeatureModel oldModel;
private FeatureModel newModel;
private Set<String> addedFeatures;
private Set<String> deletedFeatures;
private Set<String> replaceFeatures;
private Node oldRoot;
private Node newRoot;
private Node oldRootUpdated;
private Node newRootUpdated;
private Boolean implies;
private Boolean isImplied;
private Comparison result;
private ExampleCalculator addedProducts;
private ExampleCalculator removedProducts;
public ModelComparator(long timeout) {
this(timeout, 3);
}
public ModelComparator(long timeout, int strategyIndex) {
this.timeout = timeout;
if (strategyIndex > 0)
strategy.add(Strategy.WithoutIdenticalRules);
if (strategyIndex > 1)
strategy.add(Strategy.SingleTesting);
if (strategyIndex > 2)
strategy.add(Strategy.SingleTestingAborted);
}
public Comparison compare(FeatureModel oldModel, FeatureModel newModel) {
this.oldModel = oldModel;
this.newModel = newModel;
try {
addedFeatures = calculateAddedFeatures(oldModel, newModel);
deletedFeatures = calculateAddedFeatures(newModel, oldModel);
replaceFeatures = calculateReplaceFeatures(oldModel, deletedFeatures, newModel, addedFeatures);
oldRoot = createNodes(oldModel, replaceFeatures);
newRoot = createNodes(newModel, replaceFeatures);
oldRoot = createFalseStatementForConcreteVariables(addedFeatures, oldRoot);
newRoot = createFalseStatementForConcreteVariables(deletedFeatures, newRoot);
oldRootUpdated = removeIdenticalNodes(oldRoot, newRoot);
newRootUpdated = removeIdenticalNodes(newRoot, oldRoot);
removedProducts = new ExampleCalculator(oldModel, timeout);
implies = implies(oldRoot, newRootUpdated, removedProducts);
addedProducts = new ExampleCalculator(newModel, timeout);
isImplied = implies(newRoot, oldRootUpdated, addedProducts);
if (implies)
if (isImplied)
result = Comparison.REFACTORING;
else
result = Comparison.GENERALIZATION;
else
if (isImplied)
result = Comparison.SPECIALIZATION;
else
result = Comparison.ARBITRARY;
} catch (OutOfMemoryError e) {
result = Comparison.OUTOFMEMORY;
} catch (TimeoutException e) {
result = Comparison.TIMEOUT;
} catch (Exception e) {
e.printStackTrace();
result = Comparison.ERROR;
}
return result;
}
private Node createNodes(FeatureModel featureModel, Set<String> replaceFeatures) {
HashMap<Object, Node> replacingMap = calculateReplacingMap(replaceFeatures, featureModel);
return NodeCreator.createNodes(featureModel, replacingMap);
}
private Set<String> calculateAddedFeatures(FeatureModel oldModel,
FeatureModel newModel) {
Set<String> addedFeatures = new HashSet<String>();
for (Feature feature : newModel.getFeatures())
if (feature.isConcrete()) {
String name = newModel.getOldName(feature.getName());
Feature associatedFeature = oldModel.getFeature(oldModel.getNewName(name));
if (associatedFeature == null || associatedFeature.isAbstract())
addedFeatures.add(name);
}
return addedFeatures;
}
private Set<String> calculateReplaceFeatures(FeatureModel oldModel,
Set<String> deletedFeatures, FeatureModel newModel,
Set<String> addedFeatures) {
Set<String> replaceFeatures = new HashSet<String>();
addReplaceFeatures(oldModel, newModel, replaceFeatures);
addReplaceFeatures(newModel, oldModel, replaceFeatures);
return replaceFeatures;
}
private void addReplaceFeatures(FeatureModel oldModel,
FeatureModel newModel, Set<String> replaceFeatures) {
if (oldModel.hasAbstractFeatures())
for (Feature feature : oldModel.getFeatures()) {
String name = oldModel.getOldName(feature.getName());
Feature otherFeature = newModel.getFeature(newModel.getNewName(name));
if (feature.isAbstract() && !replaceFeatures.contains(name)) {
if (otherFeature == null || otherFeature.isLayer())
replaceFeatures.add(name);
else {
Node a = calculateReplacing(name, oldModel);
Node b = calculateReplacing(name, newModel);
if (!a.equals(b))
replaceFeatures.add(name);
}
}
}
}
private Node createFalseStatementForConcreteVariables(Set<String> addedFeatures, Node node) {
LinkedList<Node> children = new LinkedList<Node>();
for (Object var : addedFeatures)
children.add(new Literal((String) var, false));
return new And(node, new And(children));
}
/**
* Removes all child nodes that are contained in the reference node.
*
* @param node the node to copy and remove from
* @param referenceNode node that specifies what do remove
* @return a copy of the node where some child nodes are not existent
*/
private Node removeIdenticalNodes(Node node, Node referenceNode) {
if (!strategy.contains(Strategy.WithoutIdenticalRules))
return node;
LinkedList<Node> updatedNodes = new LinkedList<Node>();
for (Node child : node.getChildren())
if (!containedIn(child, referenceNode.getChildren()))
updatedNodes.add(child);
return updatedNodes.isEmpty() ? null : new And(updatedNodes);
}
private boolean implies(Node a, Node b, ExampleCalculator example) throws TimeoutException {
if (b == null)
return true;
if (!strategy.contains(Strategy.SingleTesting)) {
Node node = new And(a.clone(), new Not(b.clone()));
SatSolver solver = new SatSolver(node, timeout);
boolean valid = !solver.isSatisfiable();
return valid;
}
example.setLeft(a);
example.setRight(b);
return !example.findSatisfiable(strategy.contains(Strategy.SingleTestingAborted));
}
private boolean containedIn(Node node, Node[] nodes) {
for (Node child : nodes)
if (node.equals(child))
return true;
return false;
}
private HashMap<Object, Node> calculateReplacingMap(Set<String> replaceFeatures, FeatureModel featureModel) {
HashMap<Object, Node> map = new HashMap<Object, Node>();
for (Object var : replaceFeatures) {
Feature feature = getFeature(var, featureModel);
if (feature != null) {
Node replacing = calculateReplacing(var, featureModel);
replacing = NodeCreator.eliminateAbstractVariables(replacing, map);
updateMap(map, var, replacing);
}
}
return map;
}
/**
* Replaces all occurrences of the given variable in values of the map.
*/
private static void updateMap(HashMap<Object, Node> map, Object var, Node replacing) {
for (Object key : map.keySet()) {
Node value = map.get(key);
HashMap<Object, Node> tempMap = new HashMap<Object, Node>();
tempMap.put(var, replacing);
value = NodeCreator.eliminateAbstractVariables(value, tempMap);
map.put(key, value);
}
map.put(var, replacing);
}
public static Node calculateReplacing(Object var, FeatureModel featureModel) {
Feature feature = getFeature(var, featureModel);
return calculateReplacing(featureModel, feature);
}
private static Node calculateReplacing(FeatureModel featureModel,
Feature feature) {
LinkedList<Node> children = new LinkedList<Node>();
for (Feature child : feature.getChildren()) {
String var2 = featureModel.getOldName(child.getName());
children.add(new Literal(var2));
}
if (children.size() == 1)
return children.getFirst();
return new Or(children);
}
private static Feature getFeature(Object var, FeatureModel featureModel) {
String currentName = featureModel.getNewName((String) var);
return featureModel.getFeature(currentName);
}
public Configuration calculateExample(boolean added) throws TimeoutException {
return added ? addedProducts.nextExample() : removedProducts.nextExample();
}
public Set<Strategy> getStrategy() {
return strategy;
}
public FeatureModel getOldModel() {
return oldModel;
}
public FeatureModel getNewModel() {
return newModel;
}
public Set<String> getAddedFeatures() {
return addedFeatures;
}
public Set<String> getDeletedFeatures() {
return deletedFeatures;
}
public Set<String> getReplaceFeatures() {
return replaceFeatures;
}
public Node getOldRoot() {
return oldRoot;
}
public Node getNewRoot() {
return newRoot;
}
public Node getOldRootUpdated() {
return oldRootUpdated;
}
public Node getNewRootUpdated() {
return newRootUpdated;
}
public boolean isImplies() {
return implies;
}
public boolean isImplied() {
return isImplied;
}
public Comparison getResult() {
return result;
}
}