package soot.spl.ifds;
import static soot.spl.ifds.Constraint.FeatureModelMode.NO_SINGLETON;
import java.util.BitSet;
import java.util.Collection;
import java.util.Set;
import net.sf.javabdd.BDD;
import net.sf.javabdd.BDDFactory;
import soot.util.NumberedString;
import soot.util.StringNumberer;
public class Constraint<T> implements Cloneable {
public static BDDFactory FACTORY;
public enum FeatureModelMode{
NONE, //do not consider the feature model at all
ALL, //consider all feature constraints
NO_SINGLETON //consider all feature constraints but singleton constraints of the form "A" or "!A"
};
public static FeatureModelMode fmMode = NO_SINGLETON;
@SuppressWarnings({ "rawtypes" })
private final static Constraint FALSE = new Constraint(null) {
public Constraint and(Constraint other) {
//false && other = false
return this;
}
public Constraint or(Constraint other) {
//false || other == other
return other;
}
public String toString() {
return "false";
}
public String toString(StringNumberer featureNumberer) {
return toString();
}
public int hashCode() {
return -436534;
}
public boolean equals(Object obj) {
return obj==this;
}
protected Constraint exists(NumberedString varToQuantify) {
return this;
}
public Constraint simplify(Iterable allFeatures, Collection usedFeatures) {
return this;
}
public int size() {
return 0;
}
};
@SuppressWarnings({ "rawtypes" })
private final static Constraint TRUE = new Constraint(null) {
public Constraint and(Constraint other) {
//true && other == other
return other;
}
public Constraint or(Constraint other) {
//true || other == true
return this;
}
public String toString() {
return "true";
}
public String toString(StringNumberer featureNumberer) {
return toString();
}
public int hashCode() {
return -23214;
}
public boolean equals(Object obj) {
return obj==this;
}
protected Constraint exists(NumberedString varToQuantify) {
return this;
}
public Constraint simplify(Iterable allFeatures, Collection usedFeatures) {
return this;
}
public int size() {
return 0;
}
};
protected final BDD bdd;
private Constraint(BitSet elems, boolean positive) {
synchronized (FACTORY) {
BDD curr = FACTORY.one();
if(!elems.isEmpty()) {
for(int i=elems.nextSetBit(0); i>=0; i=elems.nextSetBit(i+1)) {
BDD ithVar = FACTORY.ithVar(i);
curr = curr.andWith(ithVar);
}
} else
curr = curr.not(); //no elements provided; assume the "FALSE" constraint
if(positive)
bdd = curr;
else
bdd = curr.not();
}
}
private Constraint(BitSet elems, Set<NumberedString> featureDomain) {
synchronized (FACTORY) {
BDD curr = FACTORY.one();
for(NumberedString feature: featureDomain) {
int i = feature.getNumber();
BDD ithVar = FACTORY.ithVar(i);
if(!elems.get(i)) {
ithVar = ithVar.not();
}
curr = curr.andWith(ithVar);
}
bdd = curr;
}
}
/**
* Constructs a <i>full</i> constraint in the sense that all variables mentioned in
* featureDomain but not mentioned in elems will be automatically negated.
* If the domain is {a,b,c} and elems is {b} then this will construct the
* constraint !a && b && !c.
*/
public static <T> Constraint<T> make(BitSet elems, Set<NumberedString> featureDomain) {
return new Constraint<T>(elems,featureDomain);
}
/**
* If positive is true then for elems={a,b} this constructs a constraint
* a && b. Otherwise, this constructs a constraint !(a && b).
* A constraint of the form a && b does not say anything at all about variables
* not mentioned. In particular, a && b is not the same as a && b && !c.
*/
public static <T> Constraint<T> make(BitSet elems, boolean positive) {
if(elems.isEmpty()) throw new RuntimeException("empty constraint!");
return new Constraint<T>(elems,positive);
}
public synchronized static <T> Constraint<T> make(BDD bdd) {
synchronized (FACTORY) {
if(bdd.isOne())
return Constraint.trueValue();
else if(bdd.isZero())
return Constraint.falseValue();
else return new Constraint<T>(bdd);
}
}
private Constraint(BDD bdd) {
this.bdd = bdd;
}
/**
* Computes the constraint representing this OR other.
* The constraint is automatically reduced such that
* a || !a results in true.
* @see Constraint#trueValue()
*/
public Constraint<T> or(Constraint<T> other) {
synchronized (FACTORY) {
if(other==trueValue()) return other;
if(other==falseValue()) return this;
BDD disjunction = bdd.or(other.bdd);
if(disjunction.isOne())
return trueValue();
else
return new Constraint<T>(disjunction);
}
}
/**
* Computes the constraint representing this AND other.
* The constraint is automatically reduced such that
* a && !a results in false.
* @see Constraint#falseValue()
*/
public Constraint<T> and(Constraint<T> other) {
synchronized (FACTORY) {
if(other==trueValue()) return this;
if(other==falseValue()) return other;
BDD conjunction = bdd.and(other.bdd);
if(conjunction.isZero())
return falseValue();
else
return new Constraint<T>(conjunction);
}
}
@Override
public String toString() {
return bdd.toString();
}
public String toString(StringNumberer featureNumberer) {
synchronized (FACTORY) {
StringBuilder sb = new StringBuilder();
int[] set = new int[FACTORY.varNum()];
toStringRecurse(FACTORY, sb, bdd, set, featureNumberer);
return sb.toString();
}
}
private static void toStringRecurse(BDDFactory f, StringBuilder sb, BDD r, int[] set,
StringNumberer featureNumberer) {
synchronized (FACTORY) {
int n;
boolean first;
if (r.isZero())
return;
else if (r.isOne()) {
sb.append("{");
first = true;
for (n = 0; n < set.length; n++) {
if (set[n] > 0) {
if (!first)
sb.append(" ^ ");
first = false;
if (set[n] != 2) {
sb.append("!");
}
sb.append(featureNumberer.get((long) f.level2Var(n)));
}
}
sb.append("} ");
} else {
set[f.var2Level(r.var())] = 1;
BDD rl = r.low();
toStringRecurse(f, sb, rl, set, featureNumberer);
rl.free();
set[f.var2Level(r.var())] = 2;
BDD rh = r.high();
toStringRecurse(f, sb, rh, set, featureNumberer);
rh.free();
set[f.var2Level(r.var())] = 0;
}
}
}
@SuppressWarnings("unchecked")
public static <T> Constraint<T> trueValue() {
return TRUE;
}
@SuppressWarnings("unchecked")
public static <T> Constraint<T> falseValue() {
return FALSE;
}
@Override
public int hashCode() {
synchronized (FACTORY) {
return bdd.hashCode();
}
}
@Override
public boolean equals(Object obj) {
synchronized (FACTORY) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("rawtypes")
Constraint other = (Constraint) obj;
if (bdd == null) {
if (other.bdd != null)
return false;
} else if (!bdd.equals(other.bdd))
return false;
return true;
}
}
protected Constraint<T> exists(NumberedString varToQuantify) {
synchronized (FACTORY) {
return make(bdd.exist(FACTORY.one().andWith(FACTORY.ithVar(varToQuantify.getNumber()))));
}
}
public Constraint<T> simplify(Iterable<NumberedString> allFeatures, Collection<NumberedString> usedFeatures) {
Constraint<T> fmConstraint = this;
for (NumberedString feature : allFeatures) {
if(!usedFeatures.contains(feature)) {
fmConstraint = fmConstraint.exists(feature);
}
}
return fmConstraint;
}
public int size() {
synchronized (FACTORY) {
return bdd.nodeCount();
}
}
}