001/*
002 * Copyright (c) 2005 Peter Palotas, Fredrik Johansson, Einar Pehrson,
003 * Sebastian Kekkonen, Lars Magnus Lang, 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 */
023package csheets.ext.assertion;
024
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Set;
028import java.util.Stack;
029import java.util.TreeSet;
030
031import csheets.core.Cell;
032import csheets.core.IllegalValueTypeException;
033import csheets.core.formula.BinaryOperation;
034import csheets.core.formula.Expression;
035import csheets.core.formula.FunctionCall;
036import csheets.core.formula.Literal;
037import csheets.core.formula.Operator;
038import csheets.core.formula.Reference;
039import csheets.core.formula.UnaryOperation;
040import csheets.core.formula.lang.Adder;
041import csheets.core.formula.lang.Divider;
042import csheets.core.formula.lang.Exponentiator;
043import csheets.core.formula.lang.Multiplier;
044import csheets.core.formula.lang.Negator;
045import csheets.core.formula.lang.Subtracter;
046import 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. */
050public 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}