001    /*
002     * Copyright (c) 2005 Peter Palotas, Fredrik Johansson, Einar Pehrson,
003     * Sebastian Kekkonen, Lars Magnus Lång, Malin Johansson and Sofia Nilsson
004     *
005     * This file is part of
006     * CleanSheets Extension for Assertions
007     *
008     * CleanSheets Extension for Assertions is free software; you can
009     * redistribute it and/or modify it under the terms of the GNU General Public
010     * License as published by the Free Software Foundation; either version 2 of
011     * the License, or (at your option) any later version.
012     *
013     * CleanSheets Extension for Assertions is distributed in the hope that
014     * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
015     * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016     * See the GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with CleanSheets Extension for Assertions; if not, write to the
020     * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
021     * Boston, MA  02111-1307  USA
022     */
023    package csheets.ext.assertion;
024    
025    import java.util.ArrayList;
026    import java.util.List;
027    import java.util.Set;
028    import java.util.Stack;
029    import java.util.TreeSet;
030    
031    import csheets.core.Cell;
032    import csheets.core.IllegalValueTypeException;
033    import csheets.core.formula.BinaryOperation;
034    import csheets.core.formula.Expression;
035    import csheets.core.formula.FunctionCall;
036    import csheets.core.formula.Literal;
037    import csheets.core.formula.Operator;
038    import csheets.core.formula.Reference;
039    import csheets.core.formula.UnaryOperation;
040    import csheets.core.formula.lang.Adder;
041    import csheets.core.formula.lang.Divider;
042    import csheets.core.formula.lang.Exponentiator;
043    import csheets.core.formula.lang.Multiplier;
044    import csheets.core.formula.lang.Negator;
045    import csheets.core.formula.lang.Subtracter;
046    import csheets.core.formula.util.ExpressionVisitor;
047    
048    /** A Visitor for calculating System Generated assertions for a formula
049        in the form of an Expression tree. */
050    public class AssertionArithmeticVisitor implements ExpressionVisitor {
051    
052            /** A stack used to calculate the final interval */
053            private Stack<List<MultiInterval>> intervalStack = new Stack<List<MultiInterval>>();
054    
055            private Set<Cell> referencedCells = new TreeSet<Cell>();
056    
057            /**
058             * Constructs a new AssertionArithmeticVisitor.
059             */
060            public AssertionArithmeticVisitor() {}
061    
062            /** Retrieve the result of the arithmetic calculations performed by this visitor.
063                    <p><b>NOTE!</b> This function should only be called after the visitor
064                    has been used to traverse some Expression tree (by calling Expression.accept() passing
065                    this visitor as an argument. Otherwise an exception will be thrown.
066                    @param expression the expression from which the
067                    @return The resulting interval from the performed calculations.
068                    @throws AssertionArithmeticException if no result has been calculated yet, or if
069                                    the calculations resulted in more than one result. (Indicates an error in the formula). */
070            public MultiInterval getResult(Expression expression)
071                            throws AssertionArithmeticException, MathException {
072                    // Clears collections
073                    intervalStack.clear();
074                    referencedCells.clear();
075    
076                    // Builds intervals
077                    expression.accept(this);
078    
079                    // intervalStack == null set temporary? by Fredrik
080                    if (intervalStack == null || intervalStack.size() != 1) {
081                            throw new AssertionArithmeticException("Result from assertion arithmetics was errenous. Multiple results found. Error in formula?");
082                    }
083    
084                    List<MultiInterval> list = intervalStack.peek();
085    
086                    if (list.size() != 1) {
087                            throw new AssertionArithmeticException("Result from assertion arithmetics was errenous. Single result with multiple intervals. Error in formula?");
088                    }
089    
090                    return list.get(0);
091            }
092    
093            public Object visitBinaryOperation(BinaryOperation operation)
094                            throws AssertionArithmeticException, MathException {
095    
096                    Operator operator = operation.getOperator();
097    
098                    operation.getLeftOperand().accept(this);
099                    List<MultiInterval> leftList = intervalStack.pop();
100    
101                    operation.getRightOperand().accept(this);
102                    List<MultiInterval> rightList = intervalStack.pop();
103    
104                    if (leftList.size() != 1 || rightList.size() != 1)
105                            throw new AssertionArithmeticException("No supported binary operator exist for ranges.");
106    
107                    MultiInterval left = leftList.get(0);
108                    MultiInterval right = rightList.get(0);
109    
110                    List<MultiInterval> list = new ArrayList<MultiInterval>(1);
111                    if (operator instanceof Multiplier) {
112                            list.add(MultiInterval.mul(left, right));
113                            intervalStack.push(list);
114                    } else if (operator instanceof Adder) {
115                            list.add(MultiInterval.add(left, right));
116                            intervalStack.push(list);
117                    } else if (operator instanceof Subtracter) {
118                            list.add(MultiInterval.sub(left, right));
119                            intervalStack.push(list);
120                    } else if (operator instanceof Divider) {
121                            list.add(MultiInterval.div(left, right));
122                            intervalStack.push(list);
123                    } else if (operator instanceof Exponentiator) {
124                            list.add(MultiInterval.pow(left, right));
125                            intervalStack.push(list);
126                    } else {
127                            throw new AssertionArithmeticException("Unsupported binary operator " + operator + " found.");
128                    }
129                    return operation;
130            }
131    
132            public Object visitFunctionCall(FunctionCall call)
133                            throws AssertionArithmeticException {
134    
135                    List<MultiInterval> paramList = new ArrayList<MultiInterval>();
136                    for (Expression argument : call.getArguments()) {
137                            argument.accept(this);
138                            List<MultiInterval> list = intervalStack.pop();
139                            paramList.addAll(list);
140                    }
141    
142                    List<MultiInterval> list = new ArrayList<MultiInterval>(1);
143    
144                    String funcName = call.getFunction().getIdentifier().toUpperCase();
145    
146                    if (funcName.equals("RAND")) {
147                            list.add(MultiInterval.rand());
148                    } else if (funcName.equals("COS")) {
149                            list.add(MultiInterval.cos(paramList.get(0)));
150                    } else if (funcName.equals("SIN")) {
151                            list.add(MultiInterval.sin(paramList.get(0)));
152                    } else if (funcName.equals("TAN")) {
153                            list.add(MultiInterval.tan(paramList.get(0)));
154                    } else if (funcName.equals("ABS")) {
155                            list.add(MultiInterval.abs(paramList.get(0)));
156                    } else if (funcName.equals("INTEGER")) {
157                            list.add(MultiInterval.toInt(paramList.get(0)));
158                    } else if (funcName.equals("SQRT")) {
159                            list.add(MultiInterval.sqrt(paramList.get(0)));
160                    } else if (funcName.equals("EXP")) {
161                            list.add(MultiInterval.exp(paramList.get(0)));
162                    } else if (funcName.equals("LOG")) {
163                            list.add(MultiInterval.log10(paramList.get(0)));
164                    } else if (funcName.equals("LN")) {
165                            list.add(MultiInterval.ln(paramList.get(0)));
166                    } else if (funcName.equals("FACT")) {
167                            list.add(MultiInterval.fact(paramList.get(0)));
168                    } else if (funcName.equals("SUM")) {
169                            list.add(MultiInterval.sum(paramList));
170                    } else if (funcName.equals("AVG")) {
171                            list.add(MultiInterval.avg(paramList));
172                    } else {
173                            throw new AssertionArithmeticException(
174                                    "Call to unsupported function " + call.getFunction() + " found.");
175                    }
176    
177                    intervalStack.push(list);
178                    return call;
179            }
180    
181            public Object visitLiteral(Literal literal) throws AssertionArithmeticException {
182                    try {
183                            double value = literal.getValue().toDouble();
184                            MultiInterval literalInterval = new MultiInterval();
185                            literalInterval.include(new Interval(value));
186                            List<MultiInterval> list = new ArrayList<MultiInterval>(1);
187                            list.add(literalInterval);
188                            intervalStack.push(list);
189                    } catch (IllegalValueTypeException e) {
190                            throw new AssertionArithmeticException("Non-numeric value found in formula.");
191                    }
192                    return literal;
193            }
194    
195            public Object visitReference(Reference reference) throws AssertionArithmeticException {
196                    List<MultiInterval> list = new ArrayList<MultiInterval>(1);
197    
198                    for (Cell cell : reference.getCells()) {
199    
200                            AssertableCell c = (AssertableCell)cell.getExtension(AssertionExtension.NAME);
201                            checkReference(c);
202    
203                            if (!c.isAsserted())
204                                    throw new AssertionArithmeticException("Referenced cell "
205                                            + c + " does not have an assertion associated with it.");
206    
207                            Assertion ass = c.getSGAssertion();
208                            if (ass == null)
209                                    ass = c.getUSAssertion();
210    
211                            list.add(ass.getMultiInterval());
212                    }
213    
214                    intervalStack.push(list);
215                    return reference;
216            }
217    
218            public Object visitUnaryOperation(UnaryOperation operation)
219                            throws AssertionArithmeticException  {
220    
221                    operation.getOperand().accept(this);
222    
223                    List<MultiInterval> operandList = intervalStack.pop();
224    
225                    if (operandList.size() != 1)
226                            throw new AssertionArithmeticException("No supported unary operator exist for ranges.");
227    
228                    MultiInterval operandInterval = operandList.get(0);
229                    Operator operator = operation.getOperator();
230    
231                    if (operator instanceof Negator) {
232                            List<MultiInterval> negList = new ArrayList<MultiInterval>(1);
233                            negList.add(MultiInterval.negate(operandInterval));
234                            intervalStack.push(negList);
235                    } else {
236                            throw new AssertionArithmeticException("Unsupported unary operator " + operator + " found.");
237                    }
238                    return operation;
239            }
240    
241            /** Checks that multiple references to the same cell does not exist within formula.
242                    Even checks indirect references.
243                    @param cell the cell to check
244                    @throws AssertionArithmeticException if a multiple reference to the same cell was found. */
245            private void checkReference(Cell cell) {
246                    if (referencedCells.contains(cell))
247                            throw new AssertionArithmeticException("Multiple references to the same cell found in formula. Cannot generate assertion.");
248                    referencedCells.add(cell);
249                    for (Cell c : cell.getPrecedents())
250                            checkReference(c);
251            }
252    }