/* 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.guidsl; import java.io.InputStream; import java.util.LinkedList; import org.prop4j.And; import org.prop4j.Choose; import org.prop4j.Equals; import org.prop4j.Implies; import org.prop4j.Literal; import org.prop4j.Node; import org.prop4j.Not; import org.prop4j.Or; import org.prop4j.SatSolver; import featureide.fm.core.Feature; import featureide.fm.core.FeatureModel; import featureide.fm.core.io.AbstractFeatureModelReader; import featureide.fm.core.io.ModelWarning; import featureide.fm.core.io.UnsupportedModelException; import guidsl.AstListNode; import guidsl.AstNode; import guidsl.AstOptNode; import guidsl.AstToken; import guidsl.AvarList; import guidsl.BAnd; import guidsl.BChoose1; import guidsl.BIff; import guidsl.BImplies; import guidsl.BNot; import guidsl.BOr; import guidsl.Bvar; import guidsl.ConsStmt; import guidsl.ESList; import guidsl.EStmt; import guidsl.Expr; import guidsl.ExprList; import guidsl.GPattern; import guidsl.GProduction; import guidsl.GTerm; import guidsl.MainModel; import guidsl.Model; import guidsl.OptTerm; import guidsl.Paren; import guidsl.ParseException; import guidsl.Parser; import guidsl.Pat; import guidsl.Pats; import guidsl.PlusTerm; import guidsl.Prods; import guidsl.SimplePattern; import guidsl.StarTerm; import guidsl.TermList; import guidsl.TermName; import guidsl.Var; import guidsl.VarStmt; /** * Parses the feature models in the GUIDSL format (grammar). * * @author Thomas Thuem */ public class FeatureModelReader extends AbstractFeatureModelReader { /** * Needed because the GUIDSL parser uses static variables and should not * be used by different threads at the same time. */ private static Object lock = new Object(); /** * Creates a new reader and sets the feature model to store the data. * * @param featureModel the structure to fill */ public FeatureModelReader(FeatureModel featureModel) { setFeatureModel(featureModel); } @Override protected void parseInputStream(InputStream inputStream) throws UnsupportedModelException { warnings.clear(); try { synchronized (lock) { Parser myParser = Parser.getInstance(inputStream); Model root = (Model) myParser.parseAll(); readModelData(root); } } catch (ParseException e) { int line = e.currentToken.next.beginLine; throw new UnsupportedModelException(e.getMessage(), line); } } private void readModelData(Model root) throws UnsupportedModelException { //print("", root); featureModel.reset(); featureModel.hasAbstractFeatures(!root.toString().startsWith("//NoAbstractFeatures")); Prods prods = ((MainModel) root).getProds(); AstListNode astListNode = (AstListNode) prods.arg[0]; do { readGProduction((GProduction) astListNode.arg[0]); astListNode = (AstListNode) astListNode.right; } while (astListNode != null); AstOptNode consOptNode = (AstOptNode) prods.right; if (consOptNode.arg.length > 0 && consOptNode.arg[0] != null) readConsStmt((ConsStmt) consOptNode.arg[0]); AstOptNode varOptNode = (AstOptNode) consOptNode.right; if (varOptNode.arg.length > 0 && varOptNode.arg[0] != null) readVarStmt((VarStmt) varOptNode.arg[0]); featureModel.handleModelDataLoaded(); } private void readGProduction(GProduction gProduction) throws UnsupportedModelException { String name = gProduction.getIDENTIFIER().name; Feature feature = featureModel.getFeature(name); if (feature == null) throw new UnsupportedModelException("The compound feature '" + name + "' have to occur on a right side of a rule before using it on a left side!", gProduction.getIDENTIFIER().lineNum()); feature.setAND(false); Pats pats = gProduction.getPats(); AstListNode astListNode = (AstListNode) pats.arg[0]; do { Feature child = readPat((Pat) astListNode.arg[0]); feature.addChild(child); astListNode = (AstListNode) astListNode.right; } while (astListNode != null); simplify(feature); } private Feature readPat(Pat pat) throws UnsupportedModelException { if (pat instanceof GPattern) return readGPattern((GPattern) pat); SimplePattern simplePattern = (SimplePattern) pat; AstToken token = simplePattern.getIDENTIFIER(); return createFeature(token); } private Feature readGPattern(GPattern gPattern) throws UnsupportedModelException { AstToken token = gPattern.getIDENTIFIER(); Feature feature = createFeature(token); feature.setAND(true); TermList termList = gPattern.getTermList(); AstListNode astListNode = (AstListNode) termList.arg[0]; do { Feature child = readGTerm((GTerm) astListNode.arg[0]); feature.addChild(child); astListNode = (AstListNode) astListNode.right; } while (astListNode != null); return simplify(feature); } private Feature readGTerm(GTerm term) throws UnsupportedModelException { AstToken token; if (term instanceof PlusTerm) token = ((PlusTerm) term).getIDENTIFIER(); else if (term instanceof StarTerm) token = ((StarTerm) term).getIDENTIFIER(); else if (term instanceof TermName) token = ((TermName) term).getIDENTIFIER(); else token = ((OptTerm) term).getIDENTIFIER(); Feature feature = createFeature(token); feature.setMandatory(term instanceof PlusTerm || term instanceof TermName); feature.setMultiple(term instanceof PlusTerm || term instanceof StarTerm); return feature; } private Feature createFeature(AstToken token) throws UnsupportedModelException { Feature feature = new Feature(featureModel, token.name); if (!featureModel.addFeature(feature)) throw new UnsupportedModelException("The feature '" + feature.getName() + "' occurs again on a right side of a rule and that's not allowed!", token.lineNum()); return feature; } private Feature simplify(Feature feature) { if (feature.getChildrenCount() == 1) { Feature child = feature.getFirstChild(); if (child.getName().equals("_" + feature.getName())) { feature.removeChild(child); feature.setChildren(child.getChildren()); feature.setAND(child.isAnd()); featureModel.deleteFeatureFromTable(child); } else if (feature.getName().equals(child.getName() + "_")) { feature.removeChild(child); if (feature == featureModel.getRoot()) featureModel.replaceRoot(child); else featureModel.deleteFeatureFromTable(feature); feature = child; } else if (feature != featureModel.getRoot() && feature.getName().equals("_" + child.getName())) { feature.removeChild(child); featureModel.deleteFeatureFromTable(feature); feature = child; } } return feature; } private int line; private void readConsStmt(ConsStmt consStmt) throws UnsupportedModelException { ESList eSList = consStmt.getESList(); AstListNode astListNode = (AstListNode) eSList.arg[0]; do { line = 0; Node node = exprToNode(((EStmt) astListNode.arg[0]).getExpr()); try { if (!new SatSolver(new Not(node.clone()), 250).isSatisfiable()) warnings.add(new ModelWarning("Constraint is a tautology.", line)); if (!new SatSolver(node.clone(), 250).isSatisfiable()) warnings.add(new ModelWarning("Constraint is not satisfiable.", line)); } catch (Exception e) { } featureModel.addPropositionalNode(node); astListNode = (AstListNode) astListNode.right; } while (astListNode != null); } private Node exprToNode(Expr expr) throws UnsupportedModelException { if (expr instanceof Bvar) { AstToken token = ((Bvar) expr).getIDENTIFIER(); line = token.lineNum(); String var = token.name; if (featureModel.getFeature(var) == null) throw new UnsupportedModelException("The feature '" + var + "' does not occur in the grammar!", token.lineNum()); return new Literal(var); } if (expr instanceof Paren) return exprToNode(((Paren) expr).getExpr()); if (expr instanceof BNot) return new Not(exprToNode(((BNot) expr).getNExpr())); if (expr instanceof BAnd) return new And(exprToNode(((BAnd) expr).getNExpr()), exprToNode(((BAnd) expr).getAExpr())); if (expr instanceof BOr) return new Or(exprToNode(((BOr) expr).getAExpr()), exprToNode(((BOr) expr).getOExpr())); if (expr instanceof BImplies) return new Implies(exprToNode(((BImplies) expr).getOExpr()), exprToNode(((BImplies) expr).getIExpr())); if (expr instanceof BIff) return new Equals(exprToNode(((BIff) expr).getIExpr()), exprToNode(((BIff) expr).getEExpr())); if (expr instanceof BChoose1) { ExprList exprList = ((BChoose1) expr).getExprList(); AstListNode astListNode = (AstListNode) exprList.arg[0]; LinkedList<Node> nodes = new LinkedList<Node>(); do { nodes.add(exprToNode((Expr) astListNode.arg[0])); astListNode = (AstListNode) astListNode.right; } while (astListNode != null); return new Choose(1, nodes); } throw new RuntimeException("unsupported type in guidsl grammar"); } private void readVarStmt(VarStmt varStmt) { featureModel.setAnnotations(varStmt.toString().trim()); AvarList avarList = varStmt.getAvarList(); AstListNode astListNode = (AstListNode) avarList.arg[0]; do { readVar((Var) astListNode.arg[0]); astListNode = (AstListNode) astListNode.right; } while (astListNode != null); } private void readVar(Var var) { //TODO #31: reading annotations not yet implemented } @SuppressWarnings("unused") private void print(String tab, AstNode node) { if (node != null) { if (node instanceof AstListNode) System.out.println(tab + "AstListNode"); else System.out.println(tab + node.className() + ": " + node.toString().trim().replaceAll("\\s+", " ")); if (node.arg.length > 0) print(tab + "\t", node.arg[0]); print(tab, node.right); } } }