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}