package splar.core.fm;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import splar.core.constraints.BooleanVariableInterface;
import splar.core.constraints.CNFClause;
import splar.core.constraints.PropositionalFormula;
import splar.core.constraints.parsing.CNFClauseParseException;
import splar.core.constraints.parsing.CNFClauseParser;
public class XMLFeatureModel extends FeatureModel {
public static final int USE_VARIABLE_NAME_AS_ID = 10;
public static final int SET_ID_AUTOMATICALLY = 20;
private String fileName;
private int idCounter;
private int idCreationStrategy;
public XMLFeatureModel(String fileName) {
this(fileName, SET_ID_AUTOMATICALLY);
}
public XMLFeatureModel(String fileName, int idCreationStrategy) {
super();
this.fileName = fileName;
this.idCreationStrategy = idCreationStrategy;
}
public int getIDCreationStrategy() {
return idCreationStrategy;
}
@Override
protected FeatureTreeNode createNodes() throws FeatureModelException {
FeatureTreeNode rootNode = null;
idCounter = 0;
// if model is already loaded just return the feature model root node
if ( getRoot() != null ) {
rootNode = getRoot();
}
// otherwise, load it from the XML feature model file
else {
// get the factory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
//Using factory get an instance of document builder
DocumentBuilder db = dbf.newDocumentBuilder();
//parse using builder to get DOM representation of the XML file
Document doc = db.parse(fileName);
// get the root element
Element rootEle = doc.getDocumentElement();
String featureModelName = rootEle.getAttribute("name");
if ( featureModelName == null || featureModelName.equals("") ) {
throw new FeatureModelException("Missing mandatory feature model name.");
}
setName(featureModelName);
// parse metadata, if section exists
NodeList metalist = rootEle.getElementsByTagName("meta");
if ( metalist.getLength() > 0 ) {
// System.out.println("metadata section found!");
Element metaElement = (Element)metalist.item(0);
readMetaData(metaElement);
}
// else {
// System.out.println("No metadata section found!");
// }
// parse feature tree
NodeList list = rootEle.getElementsByTagName("feature_tree");
Element featureTreeEle = (Element)list.item(0);
rootNode = parseFeatureTree(featureTreeEle.getTextContent());
// parse constraints
list = rootEle.getElementsByTagName("constraints");
Element constraintsEle = (Element)list.item(0);
String constraintsText = constraintsEle.getTextContent();
parseConstraints(constraintsText);
}
catch(FeatureModelException e) {
throw e;
}
catch(ParserConfigurationException pce) {
throw new FeatureModelException("XML error parsing SXFM file: " + pce.getMessage(), pce);
}
catch(SAXException se) {
throw new FeatureModelException("XML SAX error parsing SXFM file: " + se.getMessage() , se);
}
catch(IOException ioe) {
throw new FeatureModelException("Error reading SXFM file: " + ioe.getMessage(), ioe);
}
}
return rootNode;
}
protected void readMetaData(Element metaElement) {
// System.out.println(metaElement.getNodeName());
NodeList nodeList = metaElement.getElementsByTagName("data");
for( int i = 0 ; i < nodeList.getLength() ; i++ ) {
Element element = (Element)nodeList.item(i);
String name = element.getAttribute("name");
String value = element.getTextContent();
// System.out.println(">> " + name + ": " + value);
addMetaData(name, value);
}
}
protected void parseConstraints(String constraints) throws FeatureModelException {
CNFClauseParser cnfClauseParser = new CNFClauseParser();
Scanner scanner = new Scanner(constraints);
String line = "";
while ( scanner.hasNextLine() ) {
line = scanner.nextLine().trim();
if ( line.trim().length() > 0 ) {
int index1 = line.indexOf(":");
if ( index1 != -1 ) {
String constraintName = line.substring(0,index1).trim();
String constraintFormula = line.substring(index1+1).trim();
try {
CNFClause cnfClause = cnfClauseParser.parse(constraintFormula);
for( BooleanVariableInterface var : cnfClause.getVariables() ) {
if ( getNodeByID(var.getID()) == null ) {
throw new FeatureModelException("Error parsing extra constraint labelled '" + constraintName + "' (variable id '" + var.getID() + "' used in the formula is not defined in the feature tree).");
}
}
addConstraint(new PropositionalFormula(constraintName, cnfClause.toString2()));
}
catch(CNFClauseParseException e1) {
throw new FeatureModelException("Error parsing extra constraint labelled '" + constraintName + "' (" + e1.getMessage() + "').", e1);
}
catch(FeatureModelException e2) {
throw e2;
}
catch(Exception e3) {
throw new FeatureModelException("Error parsing extra constraint labelled '" + constraintName + "' (Line: " + line + "').", e3);
}
}
}
}
}
protected FeatureTreeNode parseFeatureTree( String featureTree ) throws IOException, FeatureModelException {
FeatureTreeNode rootNode = null;
Stack<FeatureTreeNode> nodeStack = new Stack<FeatureTreeNode>();
//System.out.println(featureTree);
LineNumberReader reader = new LineNumberReader(new StringReader(featureTree));
String line = skipBlanks(reader);
List<FeatureTreeNode> processedNodes = new ArrayList<FeatureTreeNode>();
try {
while( line != null ) {
FeatureTreeNode node = parseNode(line);
while ( processedNodes.contains(node) ) {
node.setID("_"+node.getID());
}
processedNodes.add(node);
int level = countTabs(line);
// System.out.println(line);
//System.out.println("Tabs: " + level);
// root node goes to the Stack
if ( rootNode == null && level == 0 ) {
rootNode = node;
nodeStack.push(node);
}
else {
int curLevel = nodeStack.size()-1;
// child node found
if ( level > curLevel ) {
FeatureTreeNode parent = nodeStack.peek();
parent.add(node);
nodeStack.push(node);
// TODO: check if group cardinality is within boundaries
}
else if ( level == curLevel ) {
nodeStack.pop();
FeatureTreeNode parent = nodeStack.peek();
parent.add(node);
nodeStack.push(node);
}
// another branch found
else {
int countPops = curLevel - level +1;
for( int i = 0 ; i < countPops ; i++ ) {
nodeStack.pop();
}
FeatureTreeNode parent = nodeStack.peek();
parent.add(node);
nodeStack.push(node);
}
}
nodesMap.put(node.getID(), node);
line = skipBlanks(reader);
}
}
catch( FeatureModelException e1 ) {
throw e1;
}
catch( Exception e2 ) {
throw new FeatureModelException("Error parsing Feature Tree on line '" + line.trim() + "': " + e2.toString());
}
return rootNode;
}
private int countTabs(String line) {
int count = 0;
int index = line.indexOf('\t');
while ( index != -1 ) {
count++;
index = line.indexOf('\t', index+1);
}
return count;
}
private FeatureTreeNode parseNode(String line) throws FeatureModelException {
FeatureTreeNode node = null;
int index = line.indexOf(':');
// if it's a root, mandatory or optional feature
if ( index != -1 ) {
int findex1 = line.indexOf("(");
int findex2 = line.indexOf(")");
int findex3 = line.indexOf("[");
String nodeType = line.substring(index+1,index+2).trim().toUpperCase();
if ( index== -1 || ( nodeType.length()>0 && !nodeType.equals("R") && !nodeType.equals("O") && !nodeType.equals("M") && !nodeType.equals("G")) ) {
throw new FeatureModelException("Error parsing Feature Tree on line '" + line.trim() + "' (invalid node type). Valid node types are :r (root), :m (mandatory), :o (optional), :g (group), and : (grouped)");
}
String featureName = "";
String featureID = "";
// has ID
if ( findex1 != -1 ) {
featureID = line.substring(findex1+1, findex2);
featureName = line.substring(index+2,findex1).trim();
// has ID, doesn't have name: name = ID
if ( featureName.length() == 0 ) {
featureName = featureID;
}
// has ID, has name, ok.
}
// does not have ID, is a group
else if ( findex3 != -1 ) {
featureName = line.substring(index+2,findex3).trim();
// does not have ID, doesn't have name: name = ID = automatic
if ( featureName.length() == 0 ) {
featureName = "_id_" + idCounter++;
featureID = featureName.replace(' ', '_');
}
// does not have ID, has name: id = name or id = automatic
else {
if ( idCreationStrategy == USE_VARIABLE_NAME_AS_ID ) {
featureID = featureName.replace(' ', '_');
}
else {
featureID = "_id_" + idCounter++;
}
}
}
// does not have ID, is not a group
else {
featureName = line.substring(index+2).trim();
// does not have ID, doesn't have name: name = ID = automatic
if ( featureName.length() == 0 ) {
featureName = "_id_" + idCounter++;
featureID = featureName.replace(' ', '_');
}
// does not have ID, has name: id = name or id = automatic
else {
if ( idCreationStrategy == USE_VARIABLE_NAME_AS_ID ) {
featureID = featureName.replace(' ', '_');
}
else {
featureID = "_id_" + idCounter++;
}
}
}
// It's the ROOT feature (concept)
if ( nodeType.compareToIgnoreCase("R") == 0 ) {
node = new RootNode( featureID, featureName, TreeNodeRendererFactory.createRootRenderer() );
}
// It's a MANDATORY feature
else if ( nodeType.compareToIgnoreCase("M") == 0 ) {
node = new SolitaireFeature( false, featureID, featureName, TreeNodeRendererFactory.createMandatoryRenderer() );
}
// It's a OPTIONAL feature
else if ( nodeType.compareToIgnoreCase("O") == 0 ) {
node = new SolitaireFeature( true, featureID, featureName, TreeNodeRendererFactory.createOptionalRenderer() );
}
// It's a FEATURE GROUP
else if ( nodeType.compareToIgnoreCase("G") == 0 ) {
int index1 = line.indexOf('[', index);
int index2 = line.indexOf(',', index1);
int index3 = line.indexOf(']', index2 );
int min = 1;
int max = -1;
// cardinality lower bound
try {
min = Integer.parseInt(line.substring(index1+1,index2).trim());
}
catch( NumberFormatException ex ) {
throw new FeatureModelException("Error parsing Feature Tree on line '" + line.trim() + "' (invalid cardinality lower bound value). It must be a valid integer.");
}
// cardinality upper bound
try {
String upperBound = line.substring(index2+1,index3).trim();
if (upperBound.equals("*")) {
max = -1;
}
else {
max = Integer.parseInt(upperBound);
}
}
catch( NumberFormatException ex ) {
throw new FeatureModelException("Error parsing Feature Tree on line '" + line.trim() + "' (invalid cardinality upper bound value). It must be a valid integer.");
}
//System.out.println("["+min+","+max+"]");
node = new FeatureGroup( featureID, featureName, min,max,TreeNodeRendererFactory.createFeatureGroupRenderer() );
}
// It's assumed a GROUPED feature
else {
node = new GroupedFeature( featureID, featureName, TreeNodeRendererFactory.createGroupedRenderer() );
}
}
return node;
}
private String skipBlanks(LineNumberReader reader) throws IOException {
String line = reader.readLine();
while( line != null && line.length() == 0) {
line = reader.readLine();
}
return line;
}
protected void saveNodes() {
}
}