package splar.core.fm.configuration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import splar.core.fm.FeatureModel;
import splar.core.fm.FeatureTreeNode;
import splar.core.fm.XMLFeatureModel;
public abstract class ConfigurationEngine {
protected FeatureModel model;
protected List<ConfigurationStep> steps;
public ConfigurationEngine(String featureModelURL) throws ConfigurationEngineException {
this.steps = new LinkedList<ConfigurationStep>();
this.model = loadFeatureModelFromURL(featureModelURL);
}
public ConfigurationEngine(FeatureModel model) throws ConfigurationEngineException {
this.steps = new LinkedList<ConfigurationStep>();
this.model = model;
}
protected FeatureModel loadFeatureModelFromURL(String featureModelURL) throws ConfigurationEngineException {
FeatureModel model = null;
try {
model = new XMLFeatureModel(featureModelURL, XMLFeatureModel.SET_ID_AUTOMATICALLY);
model.loadModel();
} catch (Exception e) {
throw new ConfigurationEngineException("Problems loading model. Please check if model follows SXFM specification");
}
model.saveState("initial_state");
return model;
}
public synchronized List<ConfigurationStep> getSteps() {
return steps;
}
public synchronized FeatureModel getModel() {
return model;
}
public String toString() {
String toString = "Model: " + model.getName() + "\r\n";
for( ConfigurationStep step : steps ) {
toString += " Step " + step.toString() + "\r\n";
}
return toString;
}
/****************************************************************************************************** *
* Returns last configuration step (most recent) from the list of steps
*******************************************************************************************************/
public synchronized ConfigurationStep getLastStep() {
return steps.get(steps.size()-1);
}
/******************************************************************************************************
* ISDONE
*******************************************************************************************************/
public synchronized boolean isDone() {
return model.getUninstantiatedNodes().size() == 0;
}
/******************************************************************************************************
* RESET
*******************************************************************************************************/
public synchronized ConfigurationStep reset() throws ConfigurationEngineException {
try {
steps.clear();
model.restoreState("initial_state", false);
}
catch (Exception e) {
e.printStackTrace();
throw new ConfigurationEngineException("Problems reseting configuration", e);
}
return resetConfiguration();
}
/******************************************************************************************************
* RESET CONFIGURATION
*******************************************************************************************************/
protected abstract ConfigurationStep resetConfiguration() throws ConfigurationEngineException;
/******************************************************************************************************
* UNDO - 1
*******************************************************************************************************/
public ConfigurationStep undoLastStep() throws ConfigurationEngineException {
List<ConfigurationStep> undoneSteps = this.undo(steps.size());
return undoneSteps.get(0);
}
/******************************************************************************************************
* UNDO - 2
*******************************************************************************************************/
public List<ConfigurationStep> undo(ConfigurationStep step) throws ConfigurationEngineException {
return undo(steps.indexOf(step)+1);
}
/******************************************************************************************************
* UNDO - 3
*******************************************************************************************************/
public List<ConfigurationStep> undo(int undoStep) throws ConfigurationEngineException {
List<ConfigurationStep> undoneSteps = new LinkedList<ConfigurationStep>();
try {
if ( undoStep > 1 && undoStep <= steps.size() ) {
while( steps.size() >= undoStep ) {
ConfigurationStep undoStepObj = steps.get(undoStep-1);
undoneSteps.add(undoStepObj);
steps.remove(undoStep-1);
}
}
else {
throw new ConfigurationEngineException("Cannot undo specified configuration step");
}
// restore feature model state to a previous state (at undo step)
model.restoreState("state_step" + undoStep, true);
}
catch (Exception e) {
e.printStackTrace();
throw new ConfigurationEngineException("Problems undoing configuration step", e);
}
return undoneSteps;
}
/******************************************************************************************************
* COMPUTE VALID DOMAINS
*******************************************************************************************************/
protected abstract Map<String,Boolean[]> computeValidDomains() throws ConfigurationEngineException;
/******************************************************************************************************
* GET VARIABLE NAME
*******************************************************************************************************/
protected abstract String getVariableName(int varIndex);
/******************************************************************************************************
* GET VARIABLE INDEX
*******************************************************************************************************/
protected abstract int getVariableIndex(String varName);
/******************************************************************************************************
* Utility method: Create a configuration step for a given <feature,decision> pair
* Returns the valid domains table for the configuration step just created
*******************************************************************************************************/
protected Map<String,Boolean[]> createConfigurationStep( String featureId, int featureValue, String decisionType ) throws Exception {
ConfigurationStep newConfStep = null;
Map<String,Boolean[]> domainTable = null;
int curConfStep = steps.size()+1;
try {
// save feature model state prior to performing the configuration step
model.saveState("state_step" + curConfStep);
FeatureTreeNode currentDecidedFeature = model.getNodeByID(featureId);
if ( currentDecidedFeature == null ) {
throw new ConfigurationEngineException("Feature Id not found in the feature model: " + featureId);
}
if ( currentDecidedFeature.isInstantiated() && currentDecidedFeature.getValue() != featureValue ) {
throw new ConfigurationEngineException("Feature configuration value conflicts with current assignment");
}
currentDecidedFeature.assignValue(featureValue);
currentDecidedFeature.setProperty("decisionStep", "" + curConfStep);
currentDecidedFeature.setProperty("decisionType", decisionType);
// addClauseToSolver(reasoner, currentDecidedFeature.getID(), featureValue);
// addClauseToSolver(reasoner, featureId, featureValue);
// Compute valid domains
domainTable = computeValidDomains();
// Create new configuration step
newConfStep = new ConfigurationStep(""+curConfStep);
newConfStep.addManualDecisionFeature(currentDecidedFeature);
int index = 0;
for( String featureIdentifier : domainTable.keySet() ) {
FeatureTreeNode featureNode = model.getNodeByID(featureIdentifier);
// If feature has been instantiated at this step
if ( !featureNode.isInstantiated() ) {
Boolean[] domain = domainTable.get(featureIdentifier);
// If feature has a single possible value (either true or false)
if ( domain.length == 1 ) {
model.assignValue(featureNode, (domain[0] ? 1 : 0));
featureNode.setProperty("decisionStep", "" + curConfStep);
featureNode.setProperty("decisionType", "propagated");
newConfStep.addPropagatedFeature(featureNode);
}
}
index++;
}
// Update feature models by instantiating variables (features)
// int index = 0;
// for( byte[] entry : domainTable ) {
// FeatureTreeNode featureNode = model.getNodeByID(getVariableName(index));
// // If feature has been instantiated at this step
// if ( !featureNode.isInstantiated() ) {
// // If feature has a single possible value (either true or false)
// if ( (entry[0] == FTReasoningWithSAT.YES && entry[1] == FTReasoningWithSAT.NO)
// || (entry[1] == FTReasoningWithSAT.YES && entry[0] == FTReasoningWithSAT.NO) ) {
// model.assignValue(featureNode, entry[0] == FTReasoningWithSAT.YES ? 0 : 1);
// featureNode.setProperty("decisionStep", "" + curConfStep);
// featureNode.setProperty("decisionType", "propagated");
// newConfStep.addPropagatedFeature(featureNode);
// }
// }
// index++;
// }
// Compute step attributes
ConfigurationStep.computeStepAttributes(newConfStep, steps, model);
// add step to list of steps
steps.add(newConfStep);
} catch (Exception e) {
model.restoreState("state_step" + curConfStep);
throw e;
// TODO: handle exception
}
return domainTable;
}
/******************************************************************************************************
* AUTO COMPLETE
*******************************************************************************************************/
public abstract ConfigurationStep autoComplete(boolean valueOrder) throws ConfigurationEngineException;
/******************************************************************************************************
* CONFIGURE
*******************************************************************************************************/
public synchronized ConfigurationStep configure(String featureId, int decision) throws ConfigurationEngineException {
try {
createConfigurationStep(featureId, decision, "manual");
}
catch (Exception e) {
e.printStackTrace();
throw new ConfigurationEngineException("Problems configuring model: ", e);
}
return getLastStep();
}
/******************************************************************************************************
* DETECT CONFLICTS
*******************************************************************************************************/
public abstract List<FeatureTreeNode> detectConflicts(String featureId) throws ConfigurationEngineException;
/******************************************************************************************************
* TOGGLE DECISION
*******************************************************************************************************/
public abstract List<ConfigurationStep> toggleDecision(String featureId) throws ConfigurationEngineException;
}