package org.js.model.feature.csp;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.js.model.feature.Attribute;
import org.js.model.feature.AttributeConstraint;
import org.js.model.feature.AttributeOperand;
import org.js.model.feature.AttributeReference;
import org.js.model.feature.Feature;
import org.js.model.feature.FeatureConstraint;
import org.js.model.feature.FeatureModel;
import org.js.model.feature.Group;
import org.js.model.feature.edit.FeatureModelHelper;
import org.js.model.feature.edit.FeatureModelUtil;
import choco.cp.solver.CPSolver;
import choco.kernel.common.util.iterators.DisposableIterator;
import choco.kernel.model.Model;
import choco.kernel.solver.variables.integer.IntDomainVar;
/**
* This class analyzes a given featuremodel and cashes the analyzed values.
*
* @author <a href="mailto:info@juliaschroeter.de">Julia Schroeter</a>
*
*/
public class FeatureModelAnalyzer {
private static Logger log = Logger.getLogger(FeatureModelAnalyzer.class);
int numberDerivableVariants = -1;
Set<Feature> mandatoryFeatures;
Set<Feature> coreFeatures;
Set<Feature> variableFeatures;
Model cspModel;
private boolean findAll = true;
private FeatureModelHelper featureModelHelper;
private FeatureModel model;
private boolean persistVariants = true;
private int numberOfVariantsToDerive = -1;
private boolean keepVariants = false;
Set<FeatureVariant> variants;
/**
* default constructor.
*
* @param model must not be null.
*/
public FeatureModelAnalyzer(FeatureModel model) {
this.model = model;
featureModelHelper = new FeatureModelHelper(model);
initSets();
this.persistVariants = false;
}
private void initSets() {
variants = new HashSet<FeatureVariant>();
}
public FeatureModelAnalyzer(FeatureModel featureModel, boolean persistAllVariants) {
this(featureModel);
this.persistVariants = persistAllVariants;
}
public String getFeatureModelName() {
return (model != null) ? model.getName() : "";
}
public int getFeatureConstraintCoverage() {
Set<Feature> consFeatures = new HashSet<Feature>();
for (FeatureConstraint featureCon : featureModelHelper.getAllFeatureConstraints()) {
consFeatures.add(featureCon.getLeftOperand());
consFeatures.add(featureCon.getRightOperand());
}
int constrained = consFeatures.size();
int features = featureModelHelper.getAllFeatures().size();
int percentage = (constrained * 100) / features;
return percentage;
}
public void setPersistVariants(boolean persistVariants) {
this.persistVariants = persistVariants;
}
/**
* get the coverage of attributes in constraints.
*
* @return
*/
public int getAttributeConstraintCoverage() {
Set<Attribute> consAttribute = new HashSet<Attribute>();
Set<AttributeConstraint> allAttributeConstraints = featureModelHelper.getAllAttributeConstraints();
for (AttributeConstraint attributeCon : allAttributeConstraints) {
AttributeOperand leftOperand = attributeCon.getLeftOperand();
addAttribute(leftOperand, consAttribute);
AttributeOperand rightOperand = attributeCon.getRightOperand();
addAttribute(rightOperand, consAttribute);
}
int constrained = consAttribute.size();
int attributes = featureModelHelper.getAllAttributes().size();
int percentage = attributes == 0 ? 0 : (constrained * 100) / attributes;
return percentage;
}
private void addAttribute(AttributeOperand attOperand, Set<Attribute> consAttribute) {
Attribute attribute = getConstrainedAttribute(attOperand);
if (attribute != null && !consAttribute.contains(attribute)) {
consAttribute.add(attribute);
}
}
private Attribute getConstrainedAttribute(AttributeOperand operand) {
Attribute result = null;
if (operand instanceof AttributeReference) {
AttributeReference reference = (AttributeReference) operand;
result = reference.getAttribute();
}
return result;
}
/**
* get the total number of all CSP-constraints.
*
* @return
*/
public int getNumberOfAllCSPConstraints() {
int attributeConstraints = featureModelHelper.getAllAttributeConstraints().size();
int featureConstraints = featureModelHelper.getAllFeatureConstraints().size();
return attributeConstraints + featureConstraints;
}
/**
* get the total number of all contained features.
*
* @return
*/
public int getNumberOfAllFeatures() {
return featureModelHelper.getAllFeatures().size();
}
/**
* get all mandatory features.
*
* @return
*/
public Set<Feature> getMandatoryFeatures() {
if (mandatoryFeatures == null) {
mandatoryFeatures = new HashSet<Feature>(2);
Set<Feature> features = featureModelHelper.getAllFeatures();
for (Feature feature : features) {
EObject container = feature.eContainer();
if (container != null) {
Group group = (Group) container;
EList<Feature> childFeatures = group.getChildFeatures();
if (childFeatures.size() == group.getMinCardinality()) {
mandatoryFeatures.add(feature);
}
}
}
}
return mandatoryFeatures;
}
/**
* get the number of all mandatory features.
*
* @return
*/
public int getNumberOfMandatoryFeatures() {
return getMandatoryFeatures().size();
}
/**
* Try to find a solution. If one solution was found, return true.
*
* @return
*/
public boolean isSatisfiable() {
CPSolver solver = new CPSolver();
Model problemModel = getCSPModel();
solver.read(problemModel);
solver.solve();
boolean isSolvable = solver.isFeasible();
log.debug("Featuremodel is solvable: " + isSolvable);
return isSolvable;
}
private boolean isFeature(String featureId) {
return getFeature(featureId) != null;
}
private boolean isAttribute(String attributeId) {
return featureModelHelper.getAttribute(attributeId) != null;
}
private Model getCSPModel() {
if (cspModel == null) {
TranslateFM2CSP modelBuilder = new TranslateFM2CSP();
cspModel = modelBuilder.getCSPModel(model);
}
return cspModel;
}
private void solveModel() {
long start = System.currentTimeMillis();
CPSolver solver = new CPSolver();
Model problemModel = getCSPModel();
solver.read(problemModel);
log.debug("------------------------------------------");
int j = 0;
if (solver.solve()) {
do {
j++;
if (numberOfVariantsToDerive != -1 && j > numberOfVariantsToDerive) {
persistVariants = false;
}
if (keepVariants || persistVariants) {
FeatureVariant variant = createVariant(solver);
if (persistVariants) {
save(variant, j);
}
if (keepVariants) {
variants.add(variant);
}
log.debug(j + ". variant found: '" + variant.toString() + "'");
} else {
log.debug(j + ". variant found.");
}
log.debug("------------------------------------------");
}
while (isFindAll() && solver.nextSolution());
long end = System.currentTimeMillis();
log.info("Check derivable variants took " + (end - start) + " ms.");
numberDerivableVariants = j;
// log.info(numberDerivableVariant + " derivable variants found.");
}
}
private void save(FeatureVariant variant, int number) {
FeatureModel featureModelVariant = variant.getModel();
String featureModelProject = FeatureModelUtil.getProjectName(model);
FeatureModelUtil.persistModel(featureModelVariant, featureModelVariant.getName() + number, "eft", "variants", featureModelProject);
}
private FeatureVariant createVariant(CPSolver solver) {
DisposableIterator<IntDomainVar> variables = solver.getIntVarIterator();
IntDomainVar variable;
FeatureVariant variant = new FeatureVariant(EcoreUtil.copy(model));
while (variables.hasNext()) {
variable = variables.next();
// Skip temporary variables
if (variable.getName().startsWith("TMP_")) continue;
String id = variable.getName();
if (isFeature(id)) {
handleFeatureVariable(variable, variant);
} else if (isAttribute(id)) {
handleAttributeVariable(variable, variant);
}
log.debug(variable.getName() + ":" + variable.getVal());
}
return variant;
}
private void handleAttributeVariable(IntDomainVar variable, FeatureVariant variant) {
int value = variable.getVal();
String id = variable.getName();
// attribute enablement
if (id.startsWith(TranslateFM2CSP.attributeEnablement)) {
if (value == 0) {
// attribute is disabled
} else if (value == 1) {
// attribute is enabled
}
}
// attribute value
else if (id.startsWith(TranslateFM2CSP.attributeValue)) {
variant.setAttributeValue(value, id);
}
}
private void handleFeatureVariable(IntDomainVar variable, FeatureVariant variant) {
int value = variable.getVal();
String id = variable.getName();
// 1 is selected
if (value == 1) {
variant.selectFeature(id);
} else {
// 0 is not selected
variant.deselectFeature(id);
}
}
private Feature getFeature(String id) {
Feature foundFeature = null;
Set<Feature> features = featureModelHelper.getAllFeatures();
for (Feature feature : features) {
String featureId = EcoreUtil.getID(feature);
if (id.equals(featureId)) {
foundFeature = feature;
break;
}
}
return foundFeature;
}
public int getNumberOfDerivableVariants() {
if (numberDerivableVariants == -1) {
solveModel();
}
return numberDerivableVariants;
}
public Set<Feature> getAllFeatures() {
return featureModelHelper.getAllFeatures();
}
public int getNumberOfUnboundFeatures() {
return featureModelHelper.getUnboundFeatures().size();
}
public Set<Attribute> getAllAttributes() {
return featureModelHelper.getAllAttributes();
}
/**
* get the total number of all contained features.
*
* @return
*/
public int getNumberOfAllAttributes() {
return getAllAttributes().size();
}
public void setNumberOfVariantsToDerive(int numberOfVariants) {
this.numberOfVariantsToDerive = numberOfVariants;
}
public boolean isFindAll() {
return findAll;
}
public void setFindAll(boolean findAll) {
this.findAll = findAll;
}
public int getNumberOfSelectedFeatures() {
return featureModelHelper.getSelectedFeatures().size();
}
public int getNumberOfDeselectedFeatures() {
return featureModelHelper.getDeselectedFeatures().size();
}
public int getNumberOfAssignedAttributes() {
return featureModelHelper.getAssignedAttributes().size();
}
public FeatureVariant getOneVariant() {
numberOfVariantsToDerive = 1;
keepVariants = true;
setFindAll(false);
solveModel();
FeatureVariant aVariant = null;
for (FeatureVariant variant : variants) {
aVariant = variant;
break;
}
return aVariant;
}
}