package splar.core.fm; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import splar.core.constraints.Assignment; import splar.core.constraints.BooleanVariable; import splar.core.constraints.BooleanVariableInterface; import splar.core.constraints.CNFClause; import splar.core.constraints.CNFFormula; import splar.core.constraints.CNFLiteral; import splar.core.constraints.PropositionalFormula; import splar.core.fm.clustering.FTCluster; public abstract class FeatureModel extends DefaultTreeModel implements FeatureModelListener { private String name = ""; private Map<String,String> metadata; private FeatureTreeNode root = null; protected Map<String,FeatureTreeNode> nodesMap = new LinkedHashMap<String,FeatureTreeNode>(); private Map<String,PropositionalFormula> constraints = null; protected List<FeatureTreeNode> lastPropagatedNodes = null; private int countNodes = 0; private List<FeatureModelListener> listeners; private HashMap<String,FeatureModelState> states; // protected HashMap<FeatureTreeNode,Long> instantiatedNodes = null; public FeatureModel() { super(null, true); metadata = new LinkedHashMap<String, String>(); constraints = new LinkedHashMap<String,PropositionalFormula>(); states = new HashMap<String,FeatureModelState>(); lastPropagatedNodes = new Vector<FeatureTreeNode>(); listeners = new ArrayList<FeatureModelListener>(); } public void addMetaData(String name, String value) { metadata.put(name, value); } public String getMetaData(String name) { String value = metadata.get(name); return value==null ? "" : value; } public Set<String> getMetaDataKeys() { return metadata.keySet(); } // event handler for feature model "on instantiating feature" events public void onInstantiatingFeature(FeatureTreeNode node, boolean value) { } public void resetInstantiatedNodesCounter() { } public Set<FeatureTreeNode> getUninstantiatedNodes() { Set<FeatureTreeNode> nodes = new HashSet<FeatureTreeNode>(); for( FeatureTreeNode node : getNodes() ) { if (!(node instanceof FeatureGroup)) { if ( !node.isInstantiated() ) { nodes.add(node); } } } return nodes; } public Set<FeatureTreeNode> getInstantiatedNodes() { Set<FeatureTreeNode> nodes = new HashSet<FeatureTreeNode>(); for( FeatureTreeNode node : getNodes() ) { if (!(node instanceof FeatureGroup)) { if ( node.isInstantiated() ) { nodes.add(node); } } } return nodes; } public void addListener(FeatureModelListener listener) { listeners.add(listener); } private void dispatchOnInstantiatingFeatureEvent(FeatureTreeNode node, boolean value) { for( FeatureModelListener listener : listeners ) { listener.onInstantiatingFeature(node, value); } } public void setName(String name) { this.name = name; } public String getName() { return name; } private FeatureTreeNode subTreeRootNode= null; public FeatureModel getFeatureModelSubTree(FeatureTreeNode node) { subTreeRootNode = node; return new FeatureModel() { protected FeatureTreeNode createNodes() throws FeatureModelException { return new SolitaireFeature(true, subTreeRootNode.getID(), subTreeRootNode.getName(), null); } protected void saveNodes() { } }; } // safely remove all mandatory features from feature model public void shrink() { shrink(getRoot(),getRoot()); } // safely remove all mandatory features from feature model from the subtree rooted by node public void shrink(FeatureTreeNode node, FeatureTreeNode startNode) { int countChild = node.getChildCount(); FeatureTreeNode childNodes[] = new FeatureTreeNode[countChild]; for( int i = 0 ; i < countChild ; i++ ) { childNodes[countChild-i-1] = (FeatureTreeNode)node.getChildAt(i); } if ( isMandatory(node) ) { FeatureTreeNode parent = (FeatureTreeNode )node.getParent(); // remove node from parent parent.remove(node); // point parent to node children for( FeatureTreeNode childNode : childNodes ) { parent.add(childNode); } // update constraints: use parent's ID instead for( PropositionalFormula formula : getConstraints() ) { formula.replaceVariable(node.getID(), parent.getID()); } nodesMap.remove(node.getID()); } // recursive call for( FeatureTreeNode childNode : childNodes ) { shrink(childNode, startNode); } if ( node == startNode ) { countNodes(startNode); lastPropagatedNodes = new Vector<FeatureTreeNode>(); states.clear(); } } public List<FeatureTreeNode> getNodesAtLevel(int level) { int curLevel = 0; FeatureTreeNode rootNode = getRoot(); ArrayList<FeatureTreeNode> nodes = new ArrayList<FeatureTreeNode>(); nodes.add(rootNode); int levelCounter = 1; int nextLevelCounter = 0; while ( nodes.size() > 0 && curLevel < level) { FeatureTreeNode curNode = nodes.get(0); if ( curLevel < level ) { nodes.remove(0); int count = curNode.getChildCount(); for( int i = 0 ; i < count ; i++ ) { FeatureTreeNode childNode = (FeatureTreeNode)curNode.getChildAt(i); // if ( !(childNode instanceof FeatureGroup) ) { childNode.attachData(new Integer(curLevel+1)); nodes.add(childNode); // } nextLevelCounter++; } } if ( --levelCounter == 0 ) { levelCounter = nextLevelCounter; nextLevelCounter = 0; curLevel++; } } return nodes; } public void getSubtreeNodes(FeatureTreeNode subtreeRootNode, List<FeatureTreeNode> nodes) { if ( subtreeRootNode != null ) { int count = subtreeRootNode.getChildCount(); for( int i = 0 ; i < count ; i++ ) { FeatureTreeNode childNode = (FeatureTreeNode)subtreeRootNode.getChildAt(i); if ( !(childNode instanceof FeatureGroup ) ) { nodes.add(childNode); } getSubtreeNodes(childNode, nodes); } } } public int getNodeLevel(String nodeID) { FeatureTreeNode node = getNodeByID(nodeID); if ( node != null ) { int level = 0; TreeNode parent = node; do { parent = parent.getParent(); // parent = (parent instanceof FeatureGroup) ? parent.getParent() : parent; if ( parent != null ) { level++; } } while( parent != null); return level; } return -1; } public void resetNodesAttachedData() { Collection<FeatureTreeNode> allNodes = new Vector<FeatureTreeNode>(); getAllNodes(getRoot(), allNodes); for( FeatureTreeNode node : allNodes ) { node.resetAttachedData(); } } public Assignment getInstantiatedVariables() { Assignment a = new Assignment(); for( Iterator<FeatureTreeNode> it = getNodes().iterator() ; it.hasNext() ; ) { FeatureTreeNode node = it.next(); if ( node.isInstantiated() ) { if ( !(node instanceof FeatureGroup) ) { a.add(node); } } } return a; } public PropositionalFormula getConstraintsAsPropositionalFormula() { StringBuffer combinedFormulas = new StringBuffer(100); for( Iterator<PropositionalFormula> it = constraints.values().iterator() ; it.hasNext(); ) { combinedFormulas.append("("+it.next().getFormula()+")"); if ( it.hasNext()) { combinedFormulas.append(" AND "); } } PropositionalFormula pf = null; try { pf = new PropositionalFormula("", combinedFormulas.toString()); } catch(Exception e ) { e.printStackTrace(); } return pf; } public void saveState(String stateID) { FeatureModelState state = new FeatureModelState(stateID, this); state.save(); states.put(stateID, state); } public void restoreState(String stateID) { restoreState(stateID, true); } public void restoreState(String stateID, boolean discardState) { FeatureModelState state = states.get(stateID); if ( state != null ) { state.restore(); if ( discardState) { discardState(stateID); } } } public void discardState(String stateID) { FeatureModelState state = states.get(stateID); state.discard(); states.remove(stateID); } public boolean isExtraConstraintVariable(FeatureTreeNode node) { return getConstraintsVariablesSet().contains(node); } /*public boolean isInstantiated() { return (instantiatedNodesList.size() != 0); }*/ public void setValue(String featureID, int value) throws FeatureValueAssignmentException { assignValue(nodesMap.get(featureID), value); } public void assignValue(FeatureTreeNode node, int value) throws FeatureValueAssignmentException { if ( node != null ) { if ( !node.isImmutable() ) { if ( value == FeatureTreeNode.UNKNOWN || node.getValue() == FeatureTreeNode.UNKNOWN ) { node.assignValue(value); if (value != FeatureTreeNode.UNKNOWN ) { dispatchOnInstantiatingFeatureEvent(node, (value==0 ? false : true)); } } else if ( node.getValue() != value ) { String nodeName = node.getName(); if ( node instanceof FeatureGroup ) { nodeName = "Feature group of node " + ((FeatureTreeNode)node.getParent()).getName(); } throw new FeatureValueAssignmentException("FT assignment conflict: " + nodeName + "(current="+node.getValue()+","+"new="+value+")"); } } else { if ( node.getValue() != value ) { String nodeName = node.getName(); if ( node instanceof FeatureGroup ) { nodeName = "Feature group of node " + ((FeatureTreeNode)node.getParent()).getName(); } throw new FeatureValueAssignmentException("FT assignment conflict: " + nodeName + " is IMMUTABLE (current="+node.getValue()+","+"new="+value+")"); } } } } public void assignValue(FeatureTreeNode node, int value, BooleanVariableInterface propagationVar) throws FeatureValueAssignmentException { if ( node != null ) { if ( !node.isImmutable() ) { node.assignValue(value, propagationVar); if (value != FeatureTreeNode.UNKNOWN ) { dispatchOnInstantiatingFeatureEvent(node, (value==0 ? false : true)); } } } } public int getValue(String featureID) { FeatureTreeNode node = nodesMap.get(featureID); if ( node != null ) { return node.getValue(); } return -2; } /* public void resetValues() { for( Iterator<FeatureTreeNode> it = instantiatedNodesList.iterator() ; it.hasNext() ; ) { FeatureTreeNode node = it.next(); try { node.assignValue(FeatureTreeNode.UNKNOWN); } catch( Exception e ) { e.printStackTrace(); } it.remove(); } } */ public List<FeatureTreeNode> getLastPropagatedNodes() { return lastPropagatedNodes; } public static int countGroupedNodes(FeatureGroup featureGroupNode, int valueToCount, Collection<FeatureTreeNode> unassignedGroupedNodes ) { int countNodes = 0; int groupedNodesCount = featureGroupNode.getChildCount(); for( int i = 0 ; i < groupedNodesCount ; i++ ) { FeatureTreeNode curGroupedNode = (FeatureTreeNode)featureGroupNode.getChildAt(i); int nodeValue = curGroupedNode.getValue(); if ( nodeValue == valueToCount ) { countNodes++; } else if ( nodeValue == FeatureTreeNode.UNKNOWN ) { unassignedGroupedNodes.add(curGroupedNode); } } return countNodes; } public void loadModel() throws FeatureModelException { root = createNodes(); // resetInstantiatedNodesCounter(); // try { // propTrueDown(root,null,true); // } // catch( FeatureValueAssignmentException e ) { // e.printStackTrace(); // } countNodes(root); } public void saveModel() { saveNodes(); } public int countNodes() { countNodes = countNodes(getRoot()); return countNodes; } // count features based on their instantiation state public int countFeatures(boolean instantiated) { int counter = 0; for( FeatureTreeNode node : getNodes() ) { if (!( node instanceof FeatureGroup )) { if ( node.isInstantiated() == instantiated ) { counter++; } } } return counter; } public int countFeatures() { int counter = 0; for( FeatureTreeNode node : getNodes() ) { if ( !(node instanceof FeatureGroup) ) { counter++; } } return counter; } public double getAverageDepth() { int sumDepths = 0; Collection<FeatureTreeNode> leaves = getLeaves(); for( FeatureTreeNode node : leaves ) { if ( node.getChildCount() == 0 ) { sumDepths += depth(node); } } return (1.0*sumDepths)/leaves.size(); } public double getDepthStandardDeviation() { double squareSum = 0; Collection<FeatureTreeNode> leaves = getLeaves(); for( FeatureTreeNode node : leaves ) { if ( node.getChildCount() == 0 ) { int d = depth(node); squareSum += (d*d); } } double avgDepth = getAverageDepth(); return Math.sqrt((squareSum/leaves.size())-(avgDepth*avgDepth)); } // the closest to zero the better the balance of the tree branches public double getDepthDeviationCoeficient() { double squareSum = 0; Collection<FeatureTreeNode> leaves = getLeaves(); for( FeatureTreeNode node : leaves ) { if ( node.getChildCount() == 0 ) { int d = depth(node); squareSum += (d*d); } } double avgDepth = getAverageDepth(); return Math.sqrt(((squareSum/leaves.size())-(avgDepth*avgDepth)))/avgDepth; } // different from getNodeLevel(node) - here groups are not accounted while there they are public int depth(FeatureTreeNode node) { int depth = -1; if ( node != null ) { depth = 0; FeatureTreeNode parent = node; parent = (FeatureTreeNode)parent.getParent(); parent = (parent != null && parent instanceof FeatureGroup) ? (FeatureTreeNode)parent.getParent() : parent; while ( parent != null ) { depth++; parent = (FeatureTreeNode)parent.getParent(); parent = (parent != null && parent instanceof FeatureGroup) ? (FeatureTreeNode)parent.getParent() : parent; } } return depth; } public int getLevel(FeatureTreeNode node) { if (node == getRoot()) { return 0; } int level = 0; FeatureTreeNode parent = node; do { level++; parent = (FeatureTreeNode)(parent.getParent()); parent = (FeatureTreeNode)((parent instanceof FeatureGroup) ? parent.getParent() : parent ); } while( parent != getRoot() ); return level; } public int depthFeatures() { return depth(getRoot(),0, false); } public int depth() { return depth(getRoot(),0, true); } private int depth(FeatureTreeNode node, int depth, boolean countGroups) { if ( node == null ) { return depth; } int count = node.getChildCount(); if ( count == 0 ) return depth; int maxDepth = depth; for( int i = 0 ; i < count ; i++ ) { int d = 0; if ( countGroups || (!countGroups && !(node instanceof FeatureGroup))) { d = depth((FeatureTreeNode)node.getChildAt(i), depth+1, countGroups); } else { d = depth((FeatureTreeNode)node.getChildAt(i), depth, countGroups); } maxDepth = Math.max(d, maxDepth); } return maxDepth; } public void removeAllConstraints() { constraints.clear(); } public void addConstraint(PropositionalFormula constraint) { constraints.put(constraint.getName(),constraint); } public PropositionalFormula getConstraintByName(String name) { return constraints.get(name); } public Collection<PropositionalFormula> getConstraints() { return constraints.values(); } public int countConstraints() { return constraints.size(); } public int countConstraintsVariables() { return getConstraintsVariablesSet().size(); } public Set<BooleanVariableInterface> getConstraintsVariablesSet() { Set<BooleanVariableInterface> variables = new LinkedHashSet<BooleanVariableInterface>(); if ( constraints != null ) { for(Iterator<PropositionalFormula> it = constraints.values().iterator() ; it.hasNext() ; ) { PropositionalFormula c = it.next(); for(Iterator<BooleanVariable> it2 = c.getVariables().iterator() ; it2.hasNext() ; ) { BooleanVariableInterface var = it2.next(); variables.add(var); } } } return variables; } public Collection<FeatureTreeNode> getNodes() { return nodesMap.values(); } public List<FeatureTreeNode> getNodes(FeatureTreeNode node) { List<FeatureTreeNode> nodeList = new ArrayList<FeatureTreeNode>(); getNodes(nodeList,node); return nodeList; } private void getNodes(List<FeatureTreeNode> nodeList, FeatureTreeNode node) { if ( node != null ) { nodeList.add(node); int count = node.getChildCount(); for( int i = 0 ; i < count; i++ ) { getNodes(nodeList, (FeatureTreeNode)node.getChildAt(i)); } } } public int countNodes(FeatureTreeNode node) { countNodes = 0; if ( node != null ) { if ( !(node instanceof FeatureGroup) ) { countNodes++; } nodesMap.put(node.getID(), node); int count = node.getChildCount(); for( int i = 0 ; i < count ; i++ ) { countNodes += countNodes((FeatureTreeNode)node.getChildAt(i)); } } return countNodes; } public Collection<FeatureTreeNode> getLeaves() { Collection<FeatureTreeNode> leaves = new ArrayList<FeatureTreeNode>(); for( FeatureTreeNode node : getNodes() ) { if ( node.getChildCount() == 0 ) { leaves.add(node); } } return leaves; } public FeatureTreeNode getNodeByID(String nodeName) { return nodesMap.get(nodeName); } public FeatureTreeNode getRoot() { return root; } public boolean isRoot(FeatureTreeNode node) { return node.equals(getRoot()); } /* Descendants of a node - short name convenience method */ public Collection<FeatureTreeNode> DES(FeatureTreeNode node) { return descendants(node); } /* Ancestors of a node - short name convenience method */ public Collection<FeatureTreeNode> ANC(FeatureTreeNode node) { return ancestors(node); } /* Siblings of a grouped node - short name convenience method */ public Collection<FeatureTreeNode> GSI(FeatureTreeNode node) { return groupSiblingNodes(node); } public boolean isMandatory( FeatureTreeNode node ) { if ( node != null ) { if ( (node instanceof SolitaireFeature) ) { SolitaireFeature auxNode = ((SolitaireFeature)node); return !auxNode.isOptional(); } } return false; } public boolean isOptional( FeatureTreeNode node ) { if ( node != null ) { if ( (node instanceof SolitaireFeature) ) { SolitaireFeature auxNode = ((SolitaireFeature)node); return auxNode.isOptional(); } } return false; } /* * A MandatoryTypeOfNode is one that when FALSIFIED has implications * on its ancestors */ public boolean isMandatoryTypeOfNode( FeatureTreeNode node ) { // Check if node is mandatory try { boolean isMandatoryTypeOfNode = false; if ( node != null ) { if ( (node instanceof SolitaireFeature) ) { SolitaireFeature auxNode = ((SolitaireFeature)node); isMandatoryTypeOfNode = !auxNode.isOptional(); } // or is a grouped node with min group cardinality > 0 else if ( node instanceof GroupedFeature ){ GroupedFeature auxNode = (GroupedFeature)node; isMandatoryTypeOfNode = (((FeatureGroup)auxNode.getParent()).getMin() > 0); } } return isMandatoryTypeOfNode; } catch( Exception e ) { System.out.println("A grouped feature's parent must be a FeatureGroup. Please check node '" + node + "' and its parent."); } return false; } private boolean isQualifiedNode( FeatureTreeNode node, FeatureTreeNode baseNode ) { // if baseNode is a grouped node "node" must be different from all grouped sibling nodes /* if ( baseNode instanceof GroupedFeature ) { FeatureGroup group =(FeatureGroup) baseNode.getParent(); int childCount = group.getChildCount(); for( int i = 0 ; i < childCount ; i++ ) { FeatureTreeNode childNode = (FeatureTreeNode)group.getChildAt(i); if ( node == childNode ) { return false; } } } */ return node != baseNode; } public Collection<FeatureTreeNode> descendants(Collection<FeatureTreeNode> nodes) { Collection<FeatureTreeNode> descendantNodes = new Vector<FeatureTreeNode>(); if ( nodes != null ) { for( Iterator<FeatureTreeNode> it = nodes.iterator(); it.hasNext() ;) { Collection<FeatureTreeNode> result = descendants(it.next()); if ( result != null ) { descendantNodes.addAll(result); } } } return descendantNodes; } /** * Descendants of a given node * @param node node for which descendants are to be identified * @return list of the descendants of the node indicated */ public Collection<FeatureTreeNode> descendants(FeatureTreeNode node) { Collection<FeatureTreeNode> descNodes = new Vector<FeatureTreeNode>(); descendants(node,descNodes); descNodes.remove(node); return descNodes; } /** * Descendants of a given node * @param node node for which descendants are to be identified * @param list that stores the descendants of the node indicated */ private void descendants(FeatureTreeNode node, Collection<FeatureTreeNode> descNodes) { if ( node != null ) { if ( !(node instanceof FeatureGroup) ) { descNodes.add(node); } int count = node.getChildCount(); if ( count > 0 ) { for( int i = 0 ; i < count ; i++ ) { FeatureTreeNode childNode = (FeatureTreeNode)node.getChildAt(i); descendants(childNode, descNodes); } } } } public Collection<FeatureTreeNode> ancestors(Collection<FeatureTreeNode> nodes) { Collection<FeatureTreeNode> ancestorNodes = new Vector<FeatureTreeNode>(); if ( nodes != null ) { for( Iterator<FeatureTreeNode> it = nodes.iterator(); it.hasNext() ;) { Collection<FeatureTreeNode> result = ancestors(it.next()); if ( result != null ) { ancestorNodes.addAll(result); } } } return ancestorNodes; } /** * Ancestors of a given node * @param node node for which ancestors are to be identified * @return list of the ancestors of the node indicated */ public Collection<FeatureTreeNode> ancestors(FeatureTreeNode node) { Collection<FeatureTreeNode> ancNodes = new Vector<FeatureTreeNode>(); ancestors(node,ancNodes); ancNodes.remove(node); return ancNodes; } /** * Ancestors of a given node * @param node node for which ancestors are to be identified * @param list that stores the ancestors of the node indicated */ private void ancestors(FeatureTreeNode node, Collection<FeatureTreeNode> ancNodes) { if ( node != null ) { if ( node != getRoot() ) { if ( !(node instanceof FeatureGroup) ) { ancNodes.add(node); } FeatureTreeNode parent = (FeatureTreeNode)node.getParent(); if ( parent != null ) { ancestors(parent, ancNodes); } } } } public Collection<FeatureTreeNode> groupSiblingNodes(Collection<FeatureTreeNode> nodes) { Collection<FeatureTreeNode> gsnNodes = new Vector<FeatureTreeNode>(); if ( nodes != null ) { for( Iterator<FeatureTreeNode> it = nodes.iterator(); it.hasNext() ;) { Collection<FeatureTreeNode> result = groupSiblingNodes(it.next()); if ( result != null ) { gsnNodes.addAll(result); } } } return gsnNodes; } /** * Returns the siblings nodes of a grouped node * @param node the grouped node for which the siblings are to be identified * @return the siblings of the grouped node */ public Collection<FeatureTreeNode> groupSiblingNodes(FeatureTreeNode node) { Collection<FeatureTreeNode> gNodes = new Vector<FeatureTreeNode>(); if ( node instanceof GroupedFeature ) { FeatureGroup parent = (FeatureGroup)node.getParent(); for( int i = 0 ; i < parent.getChildCount() ; i++ ) { FeatureTreeNode chNode = (FeatureTreeNode)parent.getChildAt(i); if ( chNode != node ) { gNodes.add(chNode); } } } return gNodes; } public Iterator<FeatureTreeNode> nodesIterator() { Collection<FeatureTreeNode> allNodes = new Vector<FeatureTreeNode>(); getAllNodes(getRoot(), allNodes); return allNodes.iterator(); } private void getAllNodes(FeatureTreeNode node, Collection<FeatureTreeNode> allNodes) { if ( node != null ) { allNodes.add(node); int count = node.getChildCount(); if ( count > 0 ) { for( int i = 0 ; i < count ; i++ ) { getAllNodes((FeatureTreeNode)node.getChildAt(i), allNodes); } } } } public CNFFormula EC2CNF() { if ( countConstraints() == 0 ) return null; CNFFormula cnf = new CNFFormula(); for( PropositionalFormula pf : getConstraints() ) { cnf.addClauses(pf.toCNFClauses()); } return cnf; } public CNFFormula FT2CNF() { CNFFormula cnf = new CNFFormula(); Vector<FeatureTreeNode> nodes = new Vector<FeatureTreeNode>(); nodes.add(getRoot()); CNFClause rootClause = new CNFClause(); rootClause.addLiteral(new CNFLiteral(new BooleanVariable(getRoot().getID()),true)); cnf.addClause(rootClause); while ( nodes.size() > 0 ) { FeatureTreeNode node = nodes.firstElement(); nodes.remove(node); if ( node != null ) { FeatureTreeNode parent = (FeatureTreeNode)node.getParent(); if ( node instanceof FeatureGroup ) { parent = (FeatureTreeNode)node.getParent(); int min = ((FeatureGroup)node).getMin(); int max = ((FeatureGroup)node).getMax(); int childCount = node.getChildCount(); CNFClause clause1 = new CNFClause(); FeatureTreeNode groupedNodes[] = new FeatureTreeNode[childCount]; clause1.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),false)); for( int i = 0 ; i < childCount ; i++ ) { FeatureTreeNode groupedNode = (FeatureTreeNode)node.getChildAt(i); groupedNodes[i] = groupedNode; clause1.addLiteral(new CNFLiteral(new BooleanVariable(groupedNode.getID()),true)); CNFClause clause2 = new CNFClause(); clause2.addLiteral(new CNFLiteral(new BooleanVariable(groupedNode.getID()),false)); clause2.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),true)); cnf.addClause(clause2); } cnf.addClause(clause1); // if it is a [1,1] group, add additional clauses if ( min == 1 && max == 1 ) { addExclusiveOrGroupClauses(cnf, groupedNodes, null, 0, 0, 2); } // add grouped nodes' child nodes to the recursion list for( int i = 0 ; i < node.getChildCount(); i++ ) { FeatureTreeNode groupedNode = (FeatureTreeNode)node.getChildAt(i); for( int j = 0 ; j < groupedNode.getChildCount() ; j++ ) { nodes.add((FeatureTreeNode)groupedNode.getChildAt(j)); } } } else { if ( parent != null ) { // if the node and its parent are not assigned, create the CNF clause to represent them CNFClause clause = new CNFClause(); clause.addLiteral(new CNFLiteral(new BooleanVariable(node.getID()),false)); clause.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),true)); cnf.addClause(clause); if ( !((SolitaireFeature)node).isOptional() ) { clause = new CNFClause(); clause.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),false)); clause.addLiteral(new CNFLiteral(new BooleanVariable(node.getID()),true)); cnf.addClause(clause); } } // add child nodes to the recursion list for( int i = 0 ; i < node.getChildCount(); i++ ) { nodes.add((FeatureTreeNode)node.getChildAt(i)); } } } } return cnf; } public CNFFormula FT2CNFSkipAssignedVariables() { CNFFormula cnf = new CNFFormula(); Vector<FeatureTreeNode> nodes = new Vector<FeatureTreeNode>(); nodes.add(getRoot()); while ( nodes.size() > 0 ) { FeatureTreeNode node = nodes.firstElement(); nodes.remove(node); if ( node != null ) { FeatureTreeNode parent = (FeatureTreeNode)node.getParent(); if ( node instanceof FeatureGroup ) { parent = (FeatureTreeNode)node.getParent(); int min = ((FeatureGroup)node).getMin(); int max = ((FeatureGroup)node).getMax(); Vector<FeatureTreeNode> unassignedGroupedNodes = new Vector<FeatureTreeNode>(); // count unassigned grouped nodes for( int i = 0 ; i < node.getChildCount(); i++ ) { FeatureTreeNode groupedNode = (FeatureTreeNode)node.getChildAt(i); if ( !groupedNode.isInstantiated() ) { unassignedGroupedNodes.add(groupedNode); } } // at least one grouped node is unassigned if ( unassignedGroupedNodes.size() > 0 ) { CNFClause clause1 = new CNFClause(); if ( !parent.isInstantiated() ) { clause1.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),false)); } for( Iterator<FeatureTreeNode> it = unassignedGroupedNodes.iterator() ; it.hasNext() ; ) { FeatureTreeNode groupedNode = (FeatureTreeNode)it.next(); clause1.addLiteral(new CNFLiteral(new BooleanVariable(groupedNode.getID()),true)); // if parent is true, no CNF clause needed to indicate (parent -> one of the children) // if parent is false, recursion never reaches this point // if parent is not instantiated, creates one clause for each grouped node if ( !parent.isInstantiated() ) { CNFClause clause2 = new CNFClause(); clause2.addLiteral(new CNFLiteral(new BooleanVariable(groupedNode.getID()),false)); clause2.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),true)); cnf.addClause(clause2); } } cnf.addClause(clause1); // if it is a [1,1] group, add additional clauses if ( min == 1 && max == 1 ) { addExclusiveOrGroupClauses(cnf, unassignedGroupedNodes.toArray(new FeatureTreeNode[0]), null, 0, 0, 2); } } // continue the recursion for SELECTED (true) or uninstantiated grouped nodes for( int i = 0 ; i < node.getChildCount(); i++ ) { FeatureTreeNode groupedNode = (FeatureTreeNode)node.getChildAt(i); if ( groupedNode.getValue() == FeatureTreeNode.SELECTED || !groupedNode.isInstantiated() ) { for( int j = 0 ; j < groupedNode.getChildCount() ; j++ ) { nodes.add((FeatureTreeNode)groupedNode.getChildAt(j)); } } } } else { // if node is solitaire feature if (node instanceof SolitaireFeature ) { // if the node and its parent are not assigned, create the CNF clause to represent them if ( !node.isInstantiated() && !parent.isInstantiated() ) { CNFClause clause = new CNFClause(); clause.addLiteral(new CNFLiteral(new BooleanVariable(node.getID()),false)); clause.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),true)); cnf.addClause(clause); // if its mandatory, set the clause factor to two if ( !((SolitaireFeature)node).isOptional() ) { clause = new CNFClause(); clause.addLiteral(new CNFLiteral(new BooleanVariable(parent.getID()),false)); clause.addLiteral(new CNFLiteral(new BooleanVariable(node.getID()),true)); cnf.addClause(clause); } } } // if node is SOLITAIRE or ROOT NODE // stops the recursion if node is false since all sub-tree is false // otherwise, add child nodes to the recursion list if ( !node.isInstantiated() || node.getValue() == FeatureTreeNode.SELECTED ) { for( int i = 0 ; i < node.getChildCount(); i++ ) { nodes.add((FeatureTreeNode)node.getChildAt(i)); } } } // else if root node skip it } } return cnf; } private void addExclusiveOrGroupClauses(CNFFormula cnf, FeatureTreeNode group[], int index[], int level, int start, int p) { if ( index == null ) { index = new int[p]; for( int i = 0 ; i < index.length ; i++ ) index[i] = -1; } if ( level == p ) { CNFClause clause = new CNFClause(); for( int i = 0 ; i < index.length ; i++ ) { clause.addLiteral(new CNFLiteral(group[index[i]],false)); } cnf.addClause(clause); } else { for( int i = start ; i < group.length ; i++ ) { index[level] = i; addExclusiveOrGroupClauses(cnf,group, index, level+1, i+1, p); } } } public CNFFormula FM2CNF() { CNFFormula cnf = new CNFFormula(); cnf.addClauses(FT2CNF().getClauses()); if ( countConstraints() > 0 ) { cnf.addClauses(EC2CNF().getClauses()); } return cnf; } public Map<String,FTCluster> generateFTClusterDependencyView() { Map<String,FTCluster> view = new LinkedHashMap<String,FTCluster>(); // FT root is always the main cluster FeatureTreeNode ftRootNode = getRoot(); FTCluster ftRootCluster = new FTCluster(ftRootNode); view.put(ftRootNode.getID(), ftRootCluster ); Set<FeatureTreeNode> flatNodesSet = new HashSet<FeatureTreeNode>(); for( FeatureTreeNode child : ftRootCluster.getChildren() ) { flatNodesSet.add(child); } // traverse EC clauses and build new clusters for( PropositionalFormula pf : getConstraints() ) { for( CNFClause cnfClause : pf.toCNFClauses() ) { List<FeatureTreeNode> identifiedNodes = new ArrayList<FeatureTreeNode>(); FeatureTreeNode clusterRootNode = identifyViewNodes(cnfClause, identifiedNodes); if ( identifiedNodes.size() > 1 ) { FTCluster cluster = view.get(clusterRootNode.getID()); if ( cluster == null ) { cluster = new FTCluster(clusterRootNode); view.put(clusterRootNode.getID(), cluster); } cluster.addRelation(identifiedNodes, cnfClause.getVariables()); for( FeatureTreeNode child : cluster.getChildren() ) { flatNodesSet.add(child); } } } } // identify clusters' ancestor-descendant relationships for( String clusterName : view.keySet() ) { FTCluster cluster = view.get(clusterName); FeatureTreeNode ancestor = findNearestAncestorOnList(cluster.getRoot(), flatNodesSet); if ( ancestor != null ) { cluster.setAncestor(ancestor); } } return view; } private static FeatureTreeNode findNearestAncestorOnList(FeatureTreeNode node, Set<FeatureTreeNode> list) { if ( node != null ) { FeatureTreeNode ancestor = node; do { if ( list.contains(ancestor)) { return (FeatureTreeNode)ancestor; } ancestor = (FeatureTreeNode)ancestor.getParent(); } while( ancestor != null); } return null; } public FeatureTreeNode lowestCommonAncestor(List<FeatureTreeNode> nodes) { List<List<FeatureTreeNode>> ancestorsList = new Vector<List<FeatureTreeNode>>(); for( FeatureTreeNode node : nodes) { List<FeatureTreeNode> ancestors = new ArrayList<FeatureTreeNode>(); if ( node != null ) { ancestors.add(0,node); FeatureTreeNode ancestorNode = (FeatureTreeNode)node.getParent(); while( ancestorNode != null ) { ancestors.add(0,ancestorNode); ancestorNode = (FeatureTreeNode)ancestorNode.getParent(); } ancestorsList.add(ancestors); } } // find the list with the lowest number of variables int minSize = Integer.MAX_VALUE; for( List<FeatureTreeNode> list : ancestorsList) { int listSize = list.size(); if ( listSize < minSize ) { minSize = listSize; } } // find the lowest common ancestor and its corresponding children that are // root nodes of the subtrees of the variables in 'clause' FeatureTreeNode commonAncestor = null; boolean finalCommonAncestorFound = false; int index = 0; while( index < minSize && !finalCommonAncestorFound ) { FeatureTreeNode ancestorNode = null; for( List<FeatureTreeNode> ancestors : ancestorsList ) { FeatureTreeNode tempNode = ancestors.get(index); if ( ancestorNode == null ) { ancestorNode = tempNode; } else if ( tempNode != ancestorNode ) { finalCommonAncestorFound = true; } } if ( !finalCommonAncestorFound ) { commonAncestor = ancestorNode; } index++; } Set<FeatureTreeNode> variables = new LinkedHashSet<FeatureTreeNode>(); for( List<FeatureTreeNode> ancestors : ancestorsList ) { if ( ancestors.size() >= index ) { variables.add(ancestors.get(index-1)); } } nodes.addAll(variables); return commonAncestor; } public boolean isAncestor(FeatureTreeNode possibleAncestor, FeatureTreeNode node) { FeatureTreeNode curNode = node; while( curNode != null ) { if ( curNode == possibleAncestor ) { return true; } curNode = (FeatureTreeNode)curNode.getParent(); } return false; } private FeatureTreeNode identifyViewNodes(CNFClause clause, List<FeatureTreeNode> nodes) { // for each variable in "clause" creates a list with its ancestors List<List<FeatureTreeNode>> ancestorsList = new Vector<List<FeatureTreeNode>>(); for( BooleanVariableInterface var : clause.getVariables() ) { List<FeatureTreeNode> ancestors = new ArrayList<FeatureTreeNode>(); FeatureTreeNode node = getNodeByID(var.getID()); if ( node != null ) { ancestors.add(0,node); FeatureTreeNode ancestorNode = (FeatureTreeNode)node.getParent(); // ancestorNode = ( ancestorNode instanceof FeatureGroup )? (FeatureTreeNode)ancestorNode.getParent() : ancestorNode; while( ancestorNode != null ) { ancestors.add(0,ancestorNode); ancestorNode = (FeatureTreeNode)ancestorNode.getParent(); // ancestorNode = ( ancestorNode instanceof FeatureGroup )? (FeatureTreeNode)ancestorNode.getParent() : ancestorNode; } ancestorsList.add(ancestors); } } // find the list with the lowest number of variables int minSize = Integer.MAX_VALUE; for( List<FeatureTreeNode> list : ancestorsList) { int listSize = list.size(); if ( listSize < minSize ) { minSize = listSize; } } // find the lowest common ancestor and its corresponding children that are // root nodes of the subtrees of the variables in 'clause' FeatureTreeNode commonAncestor = null; boolean finalCommonAncestorFound = false; int index = 0; while( index < minSize && !finalCommonAncestorFound ) { FeatureTreeNode ancestorNode = null; for( List<FeatureTreeNode> ancestors : ancestorsList ) { FeatureTreeNode tempNode = ancestors.get(index); if ( ancestorNode == null ) { ancestorNode = tempNode; } else if ( tempNode != ancestorNode ) { finalCommonAncestorFound = true; } } if ( !finalCommonAncestorFound ) { commonAncestor = ancestorNode; } index++; } Set<FeatureTreeNode> variables = new LinkedHashSet<FeatureTreeNode>(); for( List<FeatureTreeNode> ancestors : ancestorsList ) { if ( ancestors.size() >= index ) { variables.add(ancestors.get(index-1)); } } nodes.addAll(variables); return commonAncestor; } public void dumpValues() { dump((FeatureTreeNode)getRoot(), 0, 3, true); dumpConstraints(); } public void dump() { dump((FeatureTreeNode)getRoot(), 0, 3, false); dumpConstraints(); } public void dumpConstraints() { for( Iterator<PropositionalFormula> it = getConstraints().iterator() ; it.hasNext() ; ) { PropositionalFormula c = it.next(); System.out.println(c); } } public void dumpConstraintsXML() { for( Iterator<PropositionalFormula> it = getConstraints().iterator() ; it.hasNext() ; ) { PropositionalFormula c = it.next(); System.out.println(c.getName() + ":" + c.toString()); } } public void dump(FeatureTreeNode node, int level, int space, boolean values) { if ( node != null ) { for( int i=1 ; i <=level*space ; i++ ) { System.out.print(" "); } String value = values ? " = "+node.getValue() : ""; System.out.println(node + value); int count = node.getChildCount(); if ( count > 0 ) { for( int i = 0 ; i < count ; i++ ) { dump((FeatureTreeNode)node.getChildAt(i), level+1, space, values); } } } } public void dumpMetaData() { System.out.println("<meta>"); for( String name : metadata.keySet() ) { System.out.println("<data name=\"" + name + "\">" + metadata.get(name) + "</data>"); } System.out.println("</meta>"); } public void dumpXML() { System.out.println("<feature_model name=\"" + getName() +"\">"); dumpMetaData(); System.out.println("<feature_tree>"); dumpTabs(getRoot(), 0); System.out.println("</feature_tree>"); System.out.println("<constraints>"); dumpConstraintsXML(); System.out.println("</constraints>"); System.out.println("</feature_model>"); } public void dumpTabs(FeatureTreeNode node, int level) { if ( node != null ) { for( int i=1 ; i <=level; i++ ) { System.out.print("\t"); } System.out.println(node); int count = node.getChildCount(); if ( count > 0 ) { for( int i = 0 ; i < count ; i++ ) { dumpTabs((FeatureTreeNode)node.getChildAt(i), level+1); } } } } // Create feature model nodes protected abstract FeatureTreeNode createNodes() throws FeatureModelException; // Save the feature model in permanent storage protected abstract void saveNodes(); }