package soot.spl.ifds; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import net.sf.javabdd.BDD; import net.sf.javabdd.BDDFactory; import org.prop4j.And; import org.prop4j.Equals; import org.prop4j.Implies; import org.prop4j.Literal; import org.prop4j.Node; import org.prop4j.Not; import org.prop4j.Or; import soot.G; import soot.toolkits.scalar.Pair; import soot.util.NumberedString; import soot.util.StringNumberer; import config.gen.CombinationFactory; import config.gen.CombinationFilter; import config.gen.Configurations; import featureide.fm.core.Feature; import featureide.fm.core.FeatureModel; import featureide.fm.core.io.UnsupportedModelException; import featureide.fm.core.io.guidsl.FeatureModelReader; public class FMConstraintFactory { protected BDDFactory f; protected String projectName; protected StringNumberer featureNumberer; /** * Builds a Constraint that represents the feature model. * * @param fmContext * the feature model context information * @param performTest * flag indicating the execution of the test * @return the Constraint representing the feature model */ public Constraint<String> computeFeatureConstraint(FeatureModelContext fmContext) { Set<NumberedString> usedFeatures = new HashSet<NumberedString>(); for (NumberedString ns : fmContext.getFeatureNumberer()) { usedFeatures.add(ns); } projectName = fmContext.getJavaProject().getElementName(); f = Constraint.FACTORY; featureNumberer = fmContext.getFeatureNumberer(); // TODO cache the resulting BDD BDD fmBDD = null; File modelFile = new File(fmContext.getJavaProject().getResource().getLocation() + File.separator + "model.m"); FeatureModel featureModel = new FeatureModel(); FeatureModelReader reader = new FeatureModelReader(featureModel); try { reader.readFromFile(modelFile); } catch (UnsupportedModelException e) { throw new RuntimeException("There was a problem parsing the feature model " + modelFile); } catch (FileNotFoundException e) { G.v().out.println("No feature model found"); return Constraint.trueValue(); } /* * XXX the following line raises the exception: * net.sf.javabdd.JFactory$JavaBDDException: Trying to decrease the number of variables */ // Collection<Feature> features = featureModel.getFeatures(); // f.setVarNum(features.size() + 1); fmBDD = buildBDD(featureModel.getRoot(), featureModel.getConstraints()); return Constraint.make(fmBDD); } /** * Builds a BDD representation from the feature model by iterating over the * tree (@{Code Feature root}) and the Constraints and then joining them * together. * * @param list * * @param root * the root feature of the feature model. * @return the BDD representing the feature model. */ private BDD buildBDD(Feature root, List<featureide.fm.core.Constraint> list) { assert (root != null); // handle FM constraints BDD constraintsBDD = buildBDDFromConstraints(list); // handle FM tree constraints BDD treeBDD = buildBDDFromTree(root); /* * handle case (i) of FM transformation (see internalBuildBDD method javadoc) and * delegates the rest of the cases to the buildBDDFromTree method. */ BDD rootBDD = bdd(root.getName()); if (treeBDD == null) treeBDD = rootBDD; else treeBDD.andWith(rootBDD); if (constraintsBDD != null) treeBDD.andWith(constraintsBDD); return treeBDD; } /** * Builds a BDD based on the list of constraints. * * Returns null if the constraint list is empty. * * @param constraints * @return the BDD representing the list of constraints, null if there are no constraints. */ private BDD buildBDDFromConstraints(List<featureide.fm.core.Constraint> constraints) { BDD constraintBDD = null; for (featureide.fm.core.Constraint constraint : constraints) { Node node = constraint.getNode(); if (constraintBDD == null) { constraintBDD = transform(node); } else { constraintBDD.andWith(transform(node)); } } return constraintBDD; } /** * Recursively navigate though every node and builds a BDD from the @{code * node}. * * Only supports operators of types {@code And}, {@code Implies}, * {@code Equals}, {@code Not} and {@code Or} and literal of type * {@code Literal}. * * @param node * the representation of a constraint * @return the BDD representing the constraint, null if one of operators is * not supported. */ private BDD transform(Node node) { // When adding new handlers to other types, don't forget to update the javadoc. if (node instanceof Literal) { return bdd(((Literal) node).var.toString()); } else if (node instanceof And) { return transform(node.getChildren()[0]).andWith(transform(node.getChildren()[1])); } else if (node instanceof Implies) { return transform(node.getChildren()[0]).impWith(transform(node.getChildren()[1])); } else if (node instanceof Not) { return bdd(node.getChildren()[0].toString()).not(); } else if (node instanceof Or) { return transform(node.getChildren()[0]).orWith(transform(node.getChildren()[1])); } else if (node instanceof Equals) { return transform(node.getChildren()[0]).biimpWith(transform(node.getChildren()[1])); } else { throw new UnsupportedOperationException("Operation for node type " + node.getClass() + " not implemented"); } } /** * Recursively applies the transformation rules from FM to BDD, starting * from the node {@code feature}. * * The transformation is [1]: * * "conjunction of (i) the root feature, (ii) an implication from each child * feature to its parent, (iii) an implication from each feature with a * mandatory child to that child, (iv) an implication from a parent with an * inclusive-OR (exclusive-OR) group to a disjunction (pairwise mutual * exclusion) of the group members. * * This method does not handle case (i). * * Mendonca et. al, Efficient Compilation Techniques for Large Scale Feature * Models. * * @param feature * the starting point for the transformation. * @return the BDD for the FM starting from {@code feature}, {@code null} if * {@code parent} has no children */ private BDD buildBDDFromTree(Feature parent) { if (parent.hasChildren()) { String parentName = parent.getName(); // assume nothing about the current Feature parent BDD featureBDD = f.one(); LinkedList<Feature> children = parent.getChildren(); /* * case (iv), exclusive-OR * if parent is alternative then the children are alternative among themselves (pairwise). * parent -> (child_1 xor ... xor child_n) */ if (parent.isAlternative()) { /* * handle for special case: when parent is alternative and has only 1 child, then: * * parent -> child */ if (children.size() == 1) { featureBDD.andWith(bdd(parentName).imp(bdd(children.get(0).getName()))); } else { Iterator<Pair<Feature, Feature>> pairsIterator = buildPairs(children); BDD pairWiseMutualExclusion = null; while (pairsIterator.hasNext()) { Pair<Feature,Feature> pair = pairsIterator.next(); if (pairWiseMutualExclusion == null) { pairWiseMutualExclusion = bdd(pair.getO1().getName()).imp(bdd(pair.getO2().getName()).not()); } else { pairWiseMutualExclusion.andWith(bdd(pair.getO1().getName()).imp(bdd(pair.getO2().getName()).not())); } } BDD childrenDisjunction = null; for (Feature child : children) { if (childrenDisjunction == null) { childrenDisjunction = bdd(child.getName()); } else { childrenDisjunction.orWith(bdd(child.getName())); } } pairWiseMutualExclusion.andWith(childrenDisjunction); // careful with the imp/impWith here assert (!featureBDD.imp(pairWiseMutualExclusion).isZero()); featureBDD = bdd(parentName).imp(pairWiseMutualExclusion); } } else /* * case (iv), inclusive-OR * parent -> (child_1 or ... or child_n) */ if (parent.isOr()) { /* * handle for special case: when parent is alternative and has only 1 child, then: * * parent -> child */ if (children.size() == 1) { featureBDD.andWith(bdd(parentName).imp(bdd(children.get(0).getName()))); } else { BDD childrenDisjunction = null; for (Feature child : children) { if (childrenDisjunction == null) { childrenDisjunction = bdd(child.getName()); } else { childrenDisjunction.orWith(bdd(child.getName())); } } // same here; careful with the imp/impWith assert (!featureBDD.imp(childrenDisjunction).isZero()); featureBDD = bdd(parentName).imp(childrenDisjunction); } } for (Feature child : children) { /* * case (ii) * child -> parent relationship */ assert (!bdd(child.getName()).imp(bdd(parentName)).isZero()); featureBDD.andWith(bdd(child.getName()).imp(bdd(parentName))); /* * case (iii) * if child is mandatory then parent -> child */ /* * XXX for some reason, a feature can be part of an alternative (or) group, and still be mandatory. * This means that a parent feature p whose children, {a, b}, are alternative can lead to something * like: * * ... (p -> (a xor b)) and (p -> a) and (p -> b)... * * which boils down to false. Thus the guard !(parent.isAlternative() || parent.isOr()) is needed. */ if (child.isMandatory() && !(parent.isAlternative() || parent.isOr())) { assert (!bdd(parentName).imp(bdd(child.getName())).isZero()); featureBDD.andWith(bdd(parentName).imp(bdd(child.getName()))); } // recurse for every child BDD childBDD = buildBDDFromTree(child); if (childBDD != null) { assert (!childBDD.isZero()); featureBDD.andWith(childBDD); } } return featureBDD; } // return null when the feature has no children return null; } @SuppressWarnings({ "unchecked", "serial" }) private Iterator<Pair<Feature, Feature>> buildPairs(List<Feature> children) { Configurations<Feature, ?, Pair<Feature, Feature>> combinate = Configurations.combinate( new CombinationFactory<Pair<Feature,Feature>, Feature>() { @Override public Pair<Feature, Feature> makeNew(List<List<Feature>> space, List<Integer> value) { return new Pair<Feature,Feature>(space.get(0).get(value.get(0)),space.get(1).get(value.get(1))); } }, new ArrayList<CombinationFilter<Feature>>() { { add(new CombinationFilter<Feature>() { @Override public boolean accept(List<List<Feature>> space, List<Integer> value) { if (space.get(0).get(value.get(0)).equals(space.get(1).get(value.get(1)))) return false; return true; } }); } }, children, children); return combinate.iterator(); } /** * Enumerates the {@code featureName} String if it has not been enumerated yet and returns the BDD such that: * * <b>one</b> <i>and</it> <b>numberOf(</b>{@code featureName}<b>)</b> * * @param featureName * @return the BDD representing the feature {@code featureName} */ private BDD bdd(String featureName) { int featureNumber = featureNumberer.find(featureName).getNumber(); BDD featureBDD = f.one().andWith(f.ithVar(featureNumber)); return featureBDD; } /** * Singleton instance. */ private static FMConstraintFactory instance; /** * Returns an instance of FMConstraintFactory. * @return */ public static FMConstraintFactory v() { if(instance==null) instance = new FMConstraintFactory(); return instance; } /** * Defeats instantiation. */ private FMConstraintFactory() { } }