/* FeatureIDE - An IDE to support feature-oriented software development
* Copyright (C) 2005-2009 FeatureIDE Team, University of Magdeburg
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* See http://www.fosd.de/featureide/ for further information.
*/
package featureide.fm.core.io.xml;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.Scanner;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.prop4j.And;
import org.prop4j.Equals;
import org.prop4j.Implies;
import org.prop4j.Literal;
import org.prop4j.Not;
import org.prop4j.Or;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import featureide.fm.core.Feature;
import featureide.fm.core.FeatureModel;
import featureide.fm.core.io.AbstractFeatureModelReader;
import featureide.fm.core.io.UnsupportedModelException;
/**
* TODO description
*
* @author Fabian Wielgorz
*/
public class XmlFeatureModelReader extends AbstractFeatureModelReader {
/**
* Creates a new reader and sets the feature model to store the data.
*
* @param featureModel the structure to fill
*/
public XmlFeatureModelReader(FeatureModel featureModel) {
setFeatureModel(featureModel);
}
/**
* Searches the Document tree for the first ancestor that is either the
* Xml-Tag MFeature or Feature
* @param n Startnode
* @return Node containing parent feature or null if there isn't one
* @throws UnsupportedModelException
*/
private Node findParentFeature(Node n) throws UnsupportedModelException {
Node wNode = n.getParentNode();
while (wNode != null) {
if (wNode.getNodeType() == Node.ELEMENT_NODE) {
String tag = wNode.getNodeName();
if (tag.equals("MFeature") || tag.equals("Feature")) {
return wNode;
} else if (tag.equals("FeatureModel")) return null;
}
wNode = wNode.getParentNode();
}
return null;
}
/**
* Finds the Name, contained in a Name-Tag for a given Feature
* @param n Node that is either the Xml-Tag MFeature or Feature
* @return Name string
* @throws UnsupportedModelException
*/
private String getFeatureName(Node n) throws UnsupportedModelException {
String result = null;
Node wNode;
NodeList children = n.getChildNodes();
NodeList grandchildren;
for (int i=0; i < children.getLength(); i++) {
wNode = children.item(i);
if ((wNode.getNodeType() == Node.ELEMENT_NODE)
&& wNode.getNodeName().equals("Name")) {
grandchildren = wNode.getChildNodes();
for (int j=0; j < grandchildren.getLength(); j++) {
result = grandchildren.item(j).getNodeValue();
if (result != null) return result;
}
}
}
throw new UnsupportedModelException("No Name-Tag found" +
" for this Feature", 0);
}
@Override
protected void parseInputStream(InputStream inputStream)
throws UnsupportedModelException {
warnings.clear();
//Parse the XML-File to a DOM-Document
boolean ignoreWhitespace = true;
boolean ignoreComments = true;
boolean putCDATAIntoText = true;
boolean createEntityRefs = false;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setIgnoringComments(ignoreComments);
dbf.setIgnoringElementContentWhitespace(ignoreWhitespace);
dbf.setCoalescing(putCDATAIntoText);
dbf.setExpandEntityReferences(!createEntityRefs);
DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException pce) {
System.err.println(pce);
System.exit(1);
}
Document doc = null;
try {
doc = db.parse(inputStream);
} catch (SAXException se) {
System.err.println(se.getMessage());
System.exit(1);
} catch (IOException ioe) {
System.err.println(ioe);
System.exit(1);
}
// Create the Feature Model from the DOM-Document
buildFeatureModel(doc);
}
/**
* Handles the Xml-Tags concerning the type of connection between Features
* @param n
* @throws UnsupportedModelException
*/
private void handleConnectionTags(Node n) throws UnsupportedModelException {
String tag = n.getNodeName();
Feature feat;
Node wNode;
if (tag.equals("And")) {
wNode = findParentFeature(n);
feat = featureModel.getFeature(getFeatureName(wNode));
if (feat == null) throw new UnsupportedModelException("Connection" +
" does not have a parent", 0);
feat.changeToAnd();
} else if (tag.equals("Or")) {
wNode = findParentFeature(n);
feat = featureModel.getFeature(getFeatureName(wNode));
if (feat == null) throw new UnsupportedModelException("Connection" +
" does not have a parent", 0);
feat.changeToOr();
} else if (tag.equals("Alternative")) {
wNode = findParentFeature(n);
feat = featureModel.getFeature(getFeatureName(wNode));
if (feat == null) throw new UnsupportedModelException("Connection" +
" does not have a parent", 0);
feat.changeToAlternative();
} else if (tag.equals("PropositionalConstraints")) {
} else if (tag.equals("PConstraint")) {
handlePropConst(n);
} else if (tag.equals("Annotations")) {
handleAnnotations(n);
} else if (tag.equals("Name")) {
return;
} else if (tag.equals("FeatureModel")) {
return;
} else {
throw new UnsupportedModelException("Unknown Xml-Tag found", 0);
}
}
/**
* Handles the Propositional Constraint found in a PConstraint xml-tag
* @param n
*/
private void handlePropConst(Node n) {
NodeList nodelist = n.getChildNodes();
for (int i=0; i<nodelist.getLength();i++) {
Node node = nodelist.item(i);
if (node.getNodeType() == Node.TEXT_NODE) {
Scanner scan = new Scanner(node.getNodeValue());
String str;
LinkedList<String> elements = new LinkedList<String>();
while (scan.hasNext()) {
str = scan.next();
if (str.startsWith("(")) {
while (str.startsWith("(")) {
elements.addLast("(");
str = str.substring(1);
}
elements.addLast(str);
} else if (str.contains(")")) {
elements.addLast(str.substring(0, str.indexOf(")")));
str = str.substring(str.indexOf(")"));
while (str.contains(")")) {
elements.addLast(")");
str = str.substring(1);
}
} else {
elements.addLast(str);
}
}
// Shouldn't be needed
//elements = insertBrackets(elements);
try {
org.prop4j.Node propNode = buildPropNode(elements);
featureModel.addPropositionalNode(propNode);
} catch (UnsupportedModelException e) {
e.printStackTrace();
}
}
}
}
private void handleAnnotations(Node n) {
String result = "";
Node wNode;
NodeList children = n.getChildNodes();
for (int i=0; i < children.getLength(); i++) {
wNode = children.item(i);
if (wNode.getNodeType() == Node.TEXT_NODE) {
result = result + wNode.getNodeValue();
}
}
featureModel.setAnnotations(result);
}
/**
* Inserts missing brackets in left-associative way
* @param list Boolean expression
* @return Boolean expression with the full set of brackets
*/
@SuppressWarnings("unused")
private LinkedList<String> insertBrackets(LinkedList<String> list) {
LinkedList<String> result = new LinkedList<String>();
LinkedList<Integer> bracketList = new LinkedList<Integer>();
bracketList.addFirst(0);
int indexCount = 0;
int countBoolOps = 0;
String element;
while (!list.isEmpty()) {
element = list.remove();
result.add(element);
if (element.equals("(")) {
countBoolOps = 0;
bracketList.addFirst(indexCount);
}
if (element.equals(")")) {
countBoolOps = 0;
bracketList.removeFirst();
}
if ((element.equals("and")) || (element.equals("or")) ||
(element.equals("implies")) || (element.equals("iff"))) {
if (countBoolOps == 0) {
countBoolOps++;
} else {
if ((!list.isEmpty()) && (!list.getFirst().equals(")"))) {
result.add(bracketList.getFirst(), "(");
result.add(result.size() - 1, ")");
}
}
}
indexCount++;
}
return result;
}
/**
* Builds a Propositional Node from a propositional formula
* @param list
* @return
* @throws UnsupportedModelException
*/
private org.prop4j.Node buildPropNode(LinkedList<String> list)
throws UnsupportedModelException {
LinkedList<String> left = new LinkedList<String>();
org.prop4j.Node leftResult, rightResult;
int bracketCount = 0;
String element;
while (!list.isEmpty()) {
element = list.removeFirst();
if (element.equals("(")) bracketCount++;
if (element.equals(")")) bracketCount--;
if ((element.equals("not")) && (list.getFirst().equals("("))
&& (list.getLast().equals(")"))) {
list.removeFirst();
list.removeLast();
return new Not(buildPropNode(list));
}
if ((element.equals("and")) || (element.equals("or")) ||
(element.equals("implies")) || (element.equals("iff")) ) {
if (bracketCount == 0) {
if ((left.getFirst().equals("(")) &&
(left.getLast().equals(")"))) {
left.removeFirst();
left.removeLast();
}
leftResult = buildPropNode(left);
if ((list.getFirst().equals("(")) &&
(list.getLast().equals(")"))) {
list.removeFirst();
list.removeLast();
}
rightResult = buildPropNode(list);
if (element.equals("and"))
return new And(leftResult, rightResult);
if (element.equals("or"))
return new Or(leftResult, rightResult);
if (element.equals("implies"))
return new Implies(leftResult, rightResult);
if (element.equals("iff"))
return new Equals(leftResult, rightResult);
}
}
left.add(element);
}
return buildLeafNodes(left);
}
private org.prop4j.Node buildLeafNodes(LinkedList<String> list)
throws UnsupportedModelException {
String element;
if (list.isEmpty())
throw new UnsupportedModelException("Fehlendes Element", 0);
element = list.removeFirst();
if (element.equals("not")) {
return new Not(buildPropNode(list));
} else {
if (featureModel.getFeature(element) == null)
throw new UnsupportedModelException("The feature '" + element +
"' does not occur in the grammar!", 0);
return new Literal(element);
}
}
/**
* Processes a single Xml-Tag.
* @param n
* @throws UnsupportedModelException
*/
private void buildFModelStep(Node n) throws UnsupportedModelException {
Feature feat;
if (n.getNodeType() != Node.ELEMENT_NODE) return;
String tag = n.getNodeName();
if (tag.equals("MFeature")) {
feat = new Feature(featureModel);
feat.setMandatory(true);
} else if (tag.equals("Feature")) {
feat = new Feature(featureModel);
feat.setMandatory(false);
} else {
handleConnectionTags(n);
return;
}
feat.setName(getFeatureName(n));
Node wNode = findParentFeature(n);
if (wNode != null) {
Feature tmpFeat = featureModel.getFeature(getFeatureName(wNode));
feat.setParent(tmpFeat);
tmpFeat.addChild(feat);
} else featureModel.setRoot(feat);
featureModel.addFeature(feat);
}
/**
* Initialize the build process.
* @param doc Document from which the Feature Model is build
* @throws UnsupportedModelException
*/
private void buildFeatureModel(Document doc)
throws UnsupportedModelException {
featureModel.reset();
featureModel.hasAbstractFeatures(false);
buildFModelRec(doc);
}
/**
* Recursively traverses the Document structure
* @param n
* @throws UnsupportedModelException
*/
private void buildFModelRec(Node n) throws UnsupportedModelException {
buildFModelStep(n);
for (Node child = n.getFirstChild(); child != null;
child = child.getNextSibling()) {
buildFModelRec(child);
}
}
}