/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package photoSpreadParser.photoSpreadExpression; import java.util.ArrayList; import java.util.HashMap; import photoSpread.PhotoSpread; import photoSpread.PhotoSpreadException; import photoSpreadObjects.PhotoSpreadObject; import photoSpreadObjects.PhotoSpreadStringObject; import photoSpreadParser.photoSpreadExpression.photoSpreadFunctions.PhotoSpreadFunction; import photoSpreadParser.photoSpreadNormalizedExpression.PhotoSpreadNormalizedExpression; import photoSpreadTable.PhotoSpreadCell; import photoSpreadUtilities.Const; import photoSpreadUtilities.PhotoSpreadObjIndexerFinder; import photoSpreadUtilities.TreeSetRandomSubsetIterable; /** * * @author skandel */ abstract public class PhotoSpreadFormulaExpression extends PhotoSpreadExpression implements PhotoSpreadEvaluatable { // Strings that the parser delivers when calling // addCondition(). These constants indicate whether // the condition is to be ANDed or ORed with the // previous condition, or whether it is to be // Negated: private final String PARSED_AND_CONDITION = "AND"; private final String PARSED_OR_CONDITION = "OR"; // For niceness and speed in the code below, // we use an enum in place of the clumsy // strings above: private enum ConditionBoolConnector { NO_BOOLEAN, AND, OR } private HashMap<PhotoSpreadCondition, ConditionBoolConnector> conditionsBoolConnectors = new HashMap<PhotoSpreadCondition, ConditionBoolConnector>(); String _info = Const.NULL_VALUE_STRING; PhotoSpreadFunction _func = null; ArrayList<PhotoSpreadCondition> _conditions = new ArrayList<PhotoSpreadCondition>(); String _selection = null; public PhotoSpreadFormulaExpression() { this("", null); } public PhotoSpreadFormulaExpression(String info, PhotoSpreadFunction function) { super(); _info = info; _func = function; } public String toString() { String res = ""; res += "<PhotoSpreadFormulaExpression (" + _info + "); " + _func; if (_conditions.size() > 0) { res += "["; for (PhotoSpreadCondition cond : _conditions) { if (cond != _conditions.get(0)) res += " & "; res += cond; } res += "]"; } if (_selection.length() > 0) res += "." + _selection; return res; } protected String conditionsAndSelectionToString(int rowOffset, int colOffset) { String str = ""; if (_conditions != null) { if (_conditions.size() > 0) { str += "["; str += _conditions.get(0).copyCondition(rowOffset, colOffset); for (int i = 1; i < _conditions.size(); i++) { str += " & " + _conditions.get(i).copyCondition(rowOffset, colOffset); } str += "]"; } } if (_selection != null) { str += "." + _selection; } return str; } abstract public PhotoSpreadNormalizedExpression normalize( PhotoSpreadCell cell); abstract public TreeSetRandomSubsetIterable<PhotoSpreadObject> evaluate( PhotoSpreadCell cell) throws PhotoSpreadException.FormulaError; /** * Add a dot expression selection to the expression: A1.age * * @param selection * to be added to the expression */ public void addSelection(String selection) { this._selection = selection; PhotoSpread.trace("Adding selection " + selection + " to" + this); } /** * Add a bracket condition to an expression: A1[age>20] This method is * called from the auto-generated parser, which is driven by the file * PhotoSpreadParser.jj. * * The condition is added to a list of conditions. Through a HashMap, the * boolean connective, if any, is associated with this condition (AND, OR, * NOT, or NO_BOOLEAN). * * This information is used in expression evaluation later on * (applyConditionsAndSelection()). * * @param condition * condition to be added to the expression * @param whether * to AND, OR, or Negate this condition. Values are one of * PARSED_AND_CONDITION, PARSED_OR_CONDITION, * PARSED_NOT_CONDITION, or PARSED_NO_BOOLEAN_CONDITION * */ public void addCondition(PhotoSpreadCondition condition, String parsedConnective) { this._conditions.add(condition); ConditionBoolConnector theConnective; if (parsedConnective.equals(PARSED_AND_CONDITION)) theConnective = ConditionBoolConnector.AND; else if (parsedConnective.equals(PARSED_OR_CONDITION)) theConnective = ConditionBoolConnector.OR; else theConnective = ConditionBoolConnector.NO_BOOLEAN; this.conditionsBoolConnectors.put(condition, theConnective); PhotoSpread.trace( "Adding condition " + condition + " to " + this + ". Connective: " + parsedConnective); } /** * Add multiple conditions to expression * * @param conditions * conditions to be added to the expression */ public void addConditions(ArrayList<PhotoSpreadCondition> conditions) { this._conditions.addAll(conditions); PhotoSpread.trace("Adding conditions " + conditions + " to " + this); } protected TreeSetRandomSubsetIterable<PhotoSpreadObject> applyConditionsAndSelection( TreeSetRandomSubsetIterable<PhotoSpreadObject> objects) { // For remembering whether a condition is ORed, ANDed, etc.: ConditionBoolConnector booleanConnector = null; // Whether one object satisfies one condition boolean doesSatisfy = false; // NOTE: we are indexing some of these objects twice here, // once when we satisfy conditions, and then again // when we do the selections. Could likely be optimized. // Destination for objects that satisfy this formula // expression's conditions. TreeSetRandomSubsetIterable<PhotoSpreadObject> satisfyingObjects = null; // Note: we had a bug here before: we // just removed *non*-satisfying objects // from the passed-in objects collection // in the 'conditions' inner loop below. // That's wrong, because we were modifying // the collection that we were iterating over. // So, unless there are no conditions, we // now copy the objs that do satisfy from 'objects' // to 'satisfyingObjects': if (_conditions.isEmpty()) satisfyingObjects = objects; else { satisfyingObjects = new TreeSetRandomSubsetIterable<PhotoSpreadObject>(); satisfyingObjects.setIndexer(new PhotoSpreadObjIndexerFinder()); } // This for loop is the heart of expression evaluation. // We run through each object and check whether it should // be in the result set or not: for (PhotoSpreadObject obj : objects) { boolean objInResultSet = false; for (PhotoSpreadCondition cond : _conditions) { // Is this condition to be ANDed or ORed with // previous conditions? booleanConnector = conditionsBoolConnectors.get(cond); // Evaluate the condition: doesSatisfy = cond.satisfiesCondition(obj); if (doesSatisfy) switch (booleanConnector) { case OR: // This object is a winner: satisfyingObjects.add(obj); break; // stop evaluating conditions; go on to next object case NO_BOOLEAN: // So far, obj qualifies: objInResultSet = true; continue; case AND: // We leave objInResultSet alone. // This condition does not *dis*-qualify the obj. continue; default: // can't be anything else. } else // obj does NOT satisfy this condition. switch (booleanConnector) { case OR: // We leave objInResultSet alone. // This condition does not newly qualify the obj. continue; case NO_BOOLEAN: // So far, obj does not get into the // result set. But we have to keep going, // in case an 'OR' condition comes later: objInResultSet = false; continue; case AND: // Same as NO_BOOLEAN: objInResultSet = false; continue; } } if (objInResultSet) satisfyingObjects.add(obj); } // Now we have the set of objects that satisfy // the formula. Next, pull out the attribute // values that were requested for selection // e.g. the year in: (=A1[Attr=value].Year if (this._selection == null) return satisfyingObjects; TreeSetRandomSubsetIterable<PhotoSpreadObject> selections = new TreeSetRandomSubsetIterable<PhotoSpreadObject>( photoSpreadUtilities.PhotoSpreadComparatorFactory .createPSMetadataComparator()); selections.setIndexer(new PhotoSpreadObjIndexerFinder()); for (PhotoSpreadObject obj : satisfyingObjects) { String selection = obj.getMetaData(_selection); if (!selection.equals(Const.NULL_VALUE_STRING)) // We *don't* want to uniquify selections. Each // selection, list A3.name or C4.name needs to be // separate objects, even if they are the same strings. // So we *do* create a new object each time: selections.add(new PhotoSpreadStringObject(obj.getCell(), selection)); } return selections; } }