package splar.plugins.configuration.bdd.javabdd;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.javabdd.BDD;
import net.sf.javabdd.BDDFactory;
import splar.core.fm.FeatureModel;
import splar.core.fm.FeatureTreeNode;
import splar.core.fm.configuration.ConfigurationEngine;
import splar.core.fm.configuration.ConfigurationEngineException;
import splar.core.fm.configuration.ConfigurationStep;
import splar.core.heuristics.FTPreOrderTraversalHeuristic;
import splar.core.heuristics.VariableOrderingHeuristic;
import splar.core.heuristics.VariableOrderingHeuristicsManager;
import splar.plugins.configuration.bdd.javabdd.catalog.Product;
import splar.plugins.configuration.bdd.javabdd.catalog.ProductCatalog;
import splar.plugins.configuration.bdd.javabdd.catalog.ProductComponent;
import splar.plugins.reasoners.bdd.javabdd.FMReasoningWithBDD;
import au.com.bytecode.opencsv.CSVReader;
public class BDDConfigurationEngine extends ConfigurationEngine {
protected ProductCatalog productCatalog = null;
protected FMReasoningWithBDD reasoner = null;
protected int bddNodeNum, bddCacheSize;
protected String loadFromFilePath = null;
protected String loadFromFileFileName = null;
/******************************************************************************************************
* CONSTRUCTOR
*******************************************************************************************************/
public BDDConfigurationEngine(String featureModelURL, String loadFromFilePath, String loadFromFileFileName)
throws ConfigurationEngineException {
super(featureModelURL);
initParameters();
this.loadFromFilePath = loadFromFilePath;
this.loadFromFileFileName = loadFromFileFileName;
}
/******************************************************************************************************
* CONSTRUCTOR
*******************************************************************************************************/
public BDDConfigurationEngine(String featureModelURL)
throws ConfigurationEngineException {
super(featureModelURL);
initParameters();
}
/******************************************************************************************************
* CONSTRUCTOR
*******************************************************************************************************/
public BDDConfigurationEngine(String featureModelURL, int bddNodeNum, int bddCacheSize)
throws ConfigurationEngineException {
super(featureModelURL);
this.bddNodeNum = bddNodeNum;
this.bddCacheSize = bddCacheSize;
}
/******************************************************************************************************
* CONSTRUCTOR
*******************************************************************************************************/
public BDDConfigurationEngine(FeatureModel model)
throws ConfigurationEngineException {
super(model);
initParameters();
}
protected void initParameters() {
bddNodeNum = model.countFeatures() * 100;
bddCacheSize = bddNodeNum;
}
/******************************************************************************************************
* RETURNS PRODUCT CATALOG
*******************************************************************************************************/
public ProductCatalog getCatalog() {
return productCatalog;
}
/******************************************************************************************************
* Restricts the Feature Model's by adding new constraints so that only products
* in the specified PRODUCT CATALOG can be derived
*******************************************************************************************************/
public void addProductCatalog(String csvCatalogFilePath) throws ConfigurationEngineException {
try {
FeatureModel featureModel = getModel();
productCatalog = new ProductCatalog(featureModel);
CSVReader reader = new CSVReader(new FileReader(csvCatalogFilePath));
// How header is interpreted
// 0 = product id
// 1 = product name
// starts with $ = product attribute
// starts with ( = ignore
// anything else = feature
String [] header = reader.readNext();
// check if header matches catalog's components
for( int i = 2 ; i < header.length ; i++ ) {
if ( !header[i].startsWith("(") && !header[i].startsWith("$")) {
if ( !productCatalog.containsComponent(header[i].trim())) {
throw new ConfigurationEngineException("Error: Feature model '" + featureModel.getName() + "' does not contain component: '" + header[i] + "' referenced by a product in file '" + csvCatalogFilePath + "'");
}
}
}
String [] nextLine = reader.readNext();
while ( nextLine != null) {
Product product = new Product(productCatalog, createProductId(nextLine[0].trim()), nextLine[1].trim());
for( int i = 2 ; i < nextLine.length ; i++ ) {
// it's an attribute...
if ( header[i].startsWith("$") ) {
product.addAttribute(header[i].substring(1).trim(), nextLine[i].trim());
}
// it's a feature
else if ( !header[i].startsWith("(") ) {
String componentType = nextLine[i].trim();
if ( componentType.length() > 0 ) {
product.addComponent(header[i], genID(header[i].trim(),componentType));
}
else {
product.addComponent(header[i].trim(), "");
}
}
}
productCatalog.addProduct(product);
nextLine = reader.readNext();
}
reader.close();
} catch (IOException e2) {
throw new ConfigurationEngineException("Error reading CSV file for creating BDD configuration engine", e2);
} catch (Exception e3) {
throw new ConfigurationEngineException("Error creating BDD configuration engine.", e3);
}
}
private String createProductId(String productId) {
String suffix = "abcdefghijklmnopqrsvwxyz";
String id = productId;
int counter = 0;
while ( productCatalog.containsProduct(id) ) {
id = productId + "-" + suffix.substring(counter,counter+1);
counter++;
}
return id;
}
public static String genID(String header, String name) {
String lex = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
for( int i = 0 ; i < name.length() ; i++ ) {
if (lex.indexOf(name.charAt(i)) == -1 ) {
name = name.replace(name.charAt(i), '_');
}
}
return ("_" + header + "_" + name).toLowerCase();
}
/******************************************************************************************************
* Returns reasoner object
*******************************************************************************************************/
public FMReasoningWithBDD getReasoner() {
return reasoner;
}
/******************************************************************************************************
* CREATE REASONER
*******************************************************************************************************/
protected void createReasoner() throws ConfigurationEngineException {
try {
// new FTPreOrderSortedECTraversalHeuristic("_BDDVarOrderHeuristic1", getModel(), FTPreOrderSortedECTraversalHeuristic.SIZE_SORT);
new FTPreOrderTraversalHeuristic("_BDDVarOrderHeuristic2", getModel());
VariableOrderingHeuristic heuristic = VariableOrderingHeuristicsManager.createHeuristicsManager().getHeuristic("_BDDVarOrderHeuristic2");
// if reasoner is to be created from file - reads associated files
if ( loadFromFilePath != null ) {
reasoner = new FMReasoningWithBDD(getModel(), heuristic, bddNodeNum, bddCacheSize, 60000, "pre-order");
reasoner.init(loadFromFilePath, loadFromFileFileName);
}
else {
reasoner = new FMReasoningWithBDD(getModel(), heuristic, bddNodeNum, bddCacheSize, 60000, "pre-order") {
protected BDD createBDD(BDDFactory bddFactory, String orderingFormulasStrategy) throws Exception {
BDD bdd = super.createBDD(bddFactory, orderingFormulasStrategy);
if ( productCatalog != null && productCatalog.getProducts().size() > 0 ) {
// add product catalog constraints to BDD
BDD catalogBDD = bddFactory.zero();
// iterate over catalog products
for( Product product : productCatalog.getProducts().values() ) {
// for each product, iterate over its components
BDD productBDD = bddFactory.one();
for( String productComponentId : product.getComponents().keySet() ) {
String productComponentType = product.getComponent(productComponentId);
ProductComponent concreteComponent = productCatalog.getComponent(productComponentId);
// for each component iterate over its types in the catalog
for( String componentType : concreteComponent.getTypes() ) {
// if catalog component's type differs from the product's component type
if ( componentType.compareToIgnoreCase(productComponentType) == 0 ) {
// System.out.println("YES Component:Type = " + productComponentId + ":" + componentType );
productBDD.andWith(bddFactory.ithVar(super.getVariableIndex(componentType)));
}
else {
// System.out.println("NO Component:Type = " + productComponentId + ":" + componentType );
productBDD.andWith(bddFactory.nithVar(super.getVariableIndex(componentType)));
}
}
}
catalogBDD.orWith(productBDD);
}
return bdd.andWith(catalogBDD);
}
else {
return bdd;
}
}
};
reasoner.init();
}
}
catch (Exception e) {
e.printStackTrace();
throw new ConfigurationEngineException("Problems creating BDD reasoner for interactive configuration", e);
}
}
/******************************************************************************************************
* GET VARIABLE INDEX
*******************************************************************************************************/
@Override
protected int getVariableIndex(String varName) {
return reasoner.getVariableIndex(varName);
}
/******************************************************************************************************
* GET VARIABLE NAME
*******************************************************************************************************/
@Override
protected String getVariableName(int varIndex) {
return reasoner.getVariableName(varIndex);
}
/******************************************************************************************************
* RESET CONFIGURATION
*******************************************************************************************************/
@Override
protected ConfigurationStep resetConfiguration()
throws ConfigurationEngineException {
try {
createReasoner();
createConfigurationStep(model.getRoot().getID(), 1, "propagated");
}
catch (Exception e) {
e.printStackTrace();
throw new ConfigurationEngineException("Problems reseting configuration", e);
}
return getLastStep();
}
/******************************************************************************************************
* Utility method: Create a configuration step for a given <feature,decision> pair
* Returns the valid domains table for the created configuration step
*******************************************************************************************************/
@Override
protected Map<String,Boolean[]> createConfigurationStep( String featureId, int featureValue, String decisionType ) throws Exception {
Map<String,Boolean[]> domainTable = null;
try {
long start = System.currentTimeMillis();
String stateName = "bdd_state_" + (steps.size()+1);
// System.out.println("saving BDD state 'bdd_state_" + (steps.size()+1) + "'");
reasoner.saveState(stateName);
reasoner.restrict(featureId, featureValue == 1);
domainTable = super.createConfigurationStep(featureId, featureValue, decisionType);
ConfigurationStep newConfStep = getLastStep();
newConfStep.addAttribute("step_Stat", ""+reasoner.getBDD().nodeCount());
newConfStep.addAttribute("step_runTime", "" + (System.currentTimeMillis()-start));
} catch (Exception e) {
throw e;
// TODO: handle exception
}
return domainTable;
}
/******************************************************************************************************
* AUTO COMPLETE
*******************************************************************************************************/
@Override
public ConfigurationStep autoComplete(boolean valueOrder)
throws ConfigurationEngineException {
return null;
}
/******************************************************************************************************
* COMPUTE VALID DOMAINS
*******************************************************************************************************/
@Override
protected Map<String,Boolean[]> computeValidDomains() throws ConfigurationEngineException {
// BDDValidDomainComputation validDomains = new BDDValidDomainComputation(reasoner);
// Map<String,Boolean[]> domainTable = validDomains.computeValidDomains();
// validDomains.debugDomainTable(true);
try {
return reasoner.allValidDomains(new HashMap<String, String>());
} catch (Exception e) {
throw new ConfigurationEngineException(e);
}
}
/******************************************************************************************************
* DETECT CONFLICTS
*******************************************************************************************************/
@Override
public List<FeatureTreeNode> detectConflicts(String featureId)
throws ConfigurationEngineException {
return null;
}
/******************************************************************************************************
* TOGGLE DECISION
*******************************************************************************************************/
@Override
public List<ConfigurationStep> toggleDecision(String featureId)
throws ConfigurationEngineException {
// TODO Auto-generated method stub
return null;
}
/******************************************************************************************************
* UNDO CONFIGURATION
*******************************************************************************************************/
@Override
public List<ConfigurationStep> undo(int undoStep) throws ConfigurationEngineException {
try {
if ( undoStep > 1 && undoStep <= steps.size() ) {
for( int i = undoStep+1 ; i <= steps.size() ; i++ ) {
// System.out.println("discarding BDD state 'bdd_state_" + i + "'");
reasoner.discardState("bdd_state_"+i);
}
// System.out.println("restoring BDD state 'bdd_state_" + undoStep + "'");
reasoner.restoreState("bdd_state_" + undoStep);
}
}
catch (Exception e) {
e.printStackTrace();
throw new ConfigurationEngineException("Problems undoing configuration step " + undoStep, e);
}
return super.undo(undoStep);
}
}