]> source.dussan.org Git - poi.git/commitdiff
moved common formula-related code to org.apache.poi.ss.formula, eliminated dependenci...
authorYegor Kozlov <yegor@apache.org>
Sun, 21 Nov 2010 12:04:56 +0000 (12:04 +0000)
committerYegor Kozlov <yegor@apache.org>
Sun, 21 Nov 2010 12:04:56 +0000 (12:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1037436 13f79535-47bb-0310-9956-ffa450edef68

36 files changed:
src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/atp/ParityFunction.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/atp/RandBetween.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/atp/YearFrac.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/atp/YearFracCalculator.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/AreaEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/AreaEvalBase.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/BlankEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/BoolEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/ConcatEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/ErrorEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/EvaluationException.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/FunctionEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/IntersectionEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/MissingArgEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/NameEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/NameXEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/NumberEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/NumericValueEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/OperandResolver.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/PercentEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/RangeEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/RefEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/RefEvalBase.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/RelationalOperationEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/StringEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/StringValueEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/TwoOperandNumericOperation.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/UnaryMinusEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/UnaryPlusEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/ValueEval.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java
src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java
src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java
src/java/org/apache/poi/ss/usermodel/CellValue.java
test-data/spreadsheet/LookupFunctionsTestCaseData.xls

diff --git a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
new file mode 100644 (file)
index 0000000..58871a6
--- /dev/null
@@ -0,0 +1,164 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.NotImplementedException;
+
+/**
+ * @author Josh Micich
+ * @author Petr Udalau - systematized work of add-in libraries and user defined functions.
+ */
+public final class AnalysisToolPak implements UDFFinder {
+
+       public static final UDFFinder instance = new AnalysisToolPak();
+
+       private static final class NotImplemented implements FreeRefFunction {
+               private final String _functionName;
+
+               public NotImplemented(String functionName) {
+                       _functionName = functionName;
+               }
+
+               public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+                       throw new NotImplementedException(_functionName);
+               }
+       };
+
+       private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
+
+
+       private AnalysisToolPak() {
+               // enforce singleton
+       }
+
+       public FreeRefFunction findFunction(String name) {
+               return _functionsByName.get(name);
+       }
+
+       private Map<String, FreeRefFunction> createFunctionsMap() {
+               Map<String, FreeRefFunction> m = new HashMap<String, FreeRefFunction>(100);
+
+               r(m, "ACCRINT", null);
+               r(m, "ACCRINTM", null);
+               r(m, "AMORDEGRC", null);
+               r(m, "AMORLINC", null);
+               r(m, "BESSELI", null);
+               r(m, "BESSELJ", null);
+               r(m, "BESSELK", null);
+               r(m, "BESSELY", null);
+               r(m, "BIN2DEC", null);
+               r(m, "BIN2HEX", null);
+               r(m, "BIN2OCT", null);
+               r(m, "CO MPLEX", null);
+               r(m, "CONVERT", null);
+               r(m, "COUPDAYBS", null);
+               r(m, "COUPDAYS", null);
+               r(m, "COUPDAYSNC", null);
+               r(m, "COUPNCD", null);
+               r(m, "COUPNUM", null);
+               r(m, "COUPPCD", null);
+               r(m, "CUMIPMT", null);
+               r(m, "CUMPRINC", null);
+               r(m, "DEC2BIN", null);
+               r(m, "DEC2HEX", null);
+               r(m, "DEC2OCT", null);
+               r(m, "DELTA", null);
+               r(m, "DISC", null);
+               r(m, "DOLLARDE", null);
+               r(m, "DOLLARFR", null);
+               r(m, "DURATION", null);
+               r(m, "EDATE", null);
+               r(m, "EFFECT", null);
+               r(m, "EOMONTH", null);
+               r(m, "ERF", null);
+               r(m, "ERFC", null);
+               r(m, "FACTDOUBLE", null);
+               r(m, "FVSCHEDULE", null);
+               r(m, "GCD", null);
+               r(m, "GESTEP", null);
+               r(m, "HEX2BIN", null);
+               r(m, "HEX2DEC", null);
+               r(m, "HEX2OCT", null);
+               r(m, "IMABS", null);
+               r(m, "IMAGINARY", null);
+               r(m, "IMARGUMENT", null);
+               r(m, "IMCONJUGATE", null);
+               r(m, "IMCOS", null);
+               r(m, "IMDIV", null);
+               r(m, "IMEXP", null);
+               r(m, "IMLN", null);
+               r(m, "IMLOG10", null);
+               r(m, "IMLOG2", null);
+               r(m, "IMPOWER", null);
+               r(m, "IMPRODUCT", null);
+               r(m, "IMREAL", null);
+               r(m, "IMSIN", null);
+               r(m, "IMSQRT", null);
+               r(m, "IMSUB", null);
+               r(m, "IMSUM", null);
+               r(m, "INTRATE", null);
+               r(m, "ISEVEN", ParityFunction.IS_EVEN);
+               r(m, "ISODD", ParityFunction.IS_ODD);
+               r(m, "LCM", null);
+               r(m, "MDURATION", null);
+               r(m, "MROUND", null);
+               r(m, "MULTINOMIAL", null);
+               r(m, "NETWORKDAYS", null);
+               r(m, "NOMINAL", null);
+               r(m, "OCT2BIN", null);
+               r(m, "OCT2DEC", null);
+               r(m, "OCT2HEX", null);
+               r(m, "ODDFPRICE", null);
+               r(m, "ODDFYIELD", null);
+               r(m, "ODDLPRICE", null);
+               r(m, "ODDLYIELD", null);
+               r(m, "PRICE", null);
+               r(m, "PRICEDISC", null);
+               r(m, "PRICEMAT", null);
+               r(m, "QUOTIENT", null);
+               r(m, "RANDBETWEEN", RandBetween.instance);
+               r(m, "RECEIVED", null);
+               r(m, "SERIESSUM", null);
+               r(m, "SQRTPI", null);
+               r(m, "TBILLEQ", null);
+               r(m, "TBILLPRICE", null);
+               r(m, "TBILLYIELD", null);
+               r(m, "WEEKNUM", null);
+               r(m, "WORKDAY", null);
+               r(m, "XIRR", null);
+               r(m, "XNPV", null);
+               r(m, "YEARFRAC", YearFrac.instance);
+               r(m, "YIELD", null);
+               r(m, "YIELDDISC", null);
+               r(m, "YIELDMAT", null);
+
+               return m;
+       }
+
+       private static void r(Map<String, FreeRefFunction> m, String functionName, FreeRefFunction pFunc) {
+               FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc;
+               m.put(functionName, func);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/atp/ParityFunction.java b/src/java/org/apache/poi/ss/formula/atp/ParityFunction.java
new file mode 100644 (file)
index 0000000..04b5e92
--- /dev/null
@@ -0,0 +1,67 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+/**
+ * Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
+ *
+ * @author Josh Micich
+ */
+final class ParityFunction implements FreeRefFunction {
+
+       public static final FreeRefFunction IS_EVEN = new ParityFunction(0);
+       public static final FreeRefFunction IS_ODD = new ParityFunction(1);
+       private final int _desiredParity;
+
+       private ParityFunction(int desiredParity) {
+               _desiredParity = desiredParity;
+       }
+
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+               if (args.length != 1) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               int val;
+               try {
+                       val = evaluateArgParity(args[0], ec.getRowIndex(), ec.getColumnIndex());
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+
+               return BoolEval.valueOf(val == _desiredParity);
+       }
+
+       private static int evaluateArgParity(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+               ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short)srcCellCol);
+
+               double d = OperandResolver.coerceValueToDouble(ve);
+               if (d < 0) {
+                       d = -d;
+               }
+               long v = (long) Math.floor(d);
+               return (int) (v & 0x0001);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/atp/RandBetween.java b/src/java/org/apache/poi/ss/formula/atp/RandBetween.java
new file mode 100644 (file)
index 0000000..4da7a53
--- /dev/null
@@ -0,0 +1,84 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.formula.atp;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+
+/**
+ * Implementation of Excel 'Analysis ToolPak' function RANDBETWEEN()<br/>
+ *
+ * Returns a random integer number between the numbers you specify.<p/>
+ *
+ * <b>Syntax</b><br/>
+ * <b>RANDBETWEEN</b>(<b>bottom</b>, <b>top</b>)<p/>
+ *
+ * <b>bottom</b> is the smallest integer RANDBETWEEN will return.<br/>
+ * <b>top</b> is the largest integer RANDBETWEEN will return.<br/>
+
+ * @author Brendan Nolan
+ */
+final class RandBetween implements FreeRefFunction{
+
+       public static final FreeRefFunction instance = new RandBetween();
+
+       private RandBetween() {
+               //enforces singleton
+       }
+
+       /**
+        * Evaluate for RANDBETWEEN(). Must be given two arguments. Bottom must be greater than top.
+        * Bottom is rounded up and top value is rounded down. After rounding top has to be set greater
+        * than top.
+        * 
+        * @see org.apache.poi.ss.formula.functions.FreeRefFunction#evaluate(org.apache.poi.ss.formula.eval.ValueEval[], org.apache.poi.ss.formula.OperationEvaluationContext)
+        */
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+               
+               double bottom, top;
+
+               if (args.length != 2) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               
+               try {
+                       bottom = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec.getColumnIndex()));
+                       top = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[1], ec.getRowIndex(), ec.getColumnIndex()));
+                       if(bottom > top) {
+                               return ErrorEval.NUM_ERROR;
+                       }
+               } catch (EvaluationException e) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               bottom = Math.ceil(bottom);
+               top = Math.floor(top);
+
+               if(bottom > top) {
+                       top = bottom;
+               }
+               
+               return new NumberEval((bottom + (int)(Math.random() * ((top - bottom) + 1))));
+               
+       }
+               
+}
diff --git a/src/java/org/apache/poi/ss/formula/atp/YearFrac.java b/src/java/org/apache/poi/ss/formula/atp/YearFrac.java
new file mode 100644 (file)
index 0000000..062fcf6
--- /dev/null
@@ -0,0 +1,159 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.regex.Pattern;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.usermodel.DateUtil;
+/**
+ * Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
+ *
+ * Returns the fraction of the year spanned by two dates.<p/>
+ *
+ * <b>Syntax</b><br/>
+ * <b>YEARFRAC</b>(<b>startDate</b>, <b>endDate</b>, basis)<p/>
+ *
+ * The <b>basis</b> optionally specifies the behaviour of YEARFRAC as follows:
+ *
+ * <table border="0" cellpadding="1" cellspacing="0" summary="basis parameter description">
+ *   <tr><th>Value</th><th>Days per Month</th><th>Days per Year</th></tr>
+ *   <tr align='center'><td>0 (default)</td><td>30</td><td>360</td></tr>
+ *   <tr align='center'><td>1</td><td>actual</td><td>actual</td></tr>
+ *   <tr align='center'><td>2</td><td>actual</td><td>360</td></tr>
+ *   <tr align='center'><td>3</td><td>actual</td><td>365</td></tr>
+ *   <tr align='center'><td>4</td><td>30</td><td>360</td></tr>
+ * </table>
+ *
+ */
+final class YearFrac implements FreeRefFunction {
+
+       public static final FreeRefFunction instance = new YearFrac();
+
+       private YearFrac() {
+               // enforce singleton
+       }
+
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+               int srcCellRow = ec.getRowIndex();
+               int srcCellCol = ec.getColumnIndex();
+               double result;
+               try {
+                       int basis = 0; // default
+                       switch(args.length) {
+                               case 3:
+                                       basis = evaluateIntArg(args[2], srcCellRow, srcCellCol);
+                               case 2:
+                                       break;
+                               default:
+                                       return ErrorEval.VALUE_INVALID;
+                       }
+                       double startDateVal = evaluateDateArg(args[0], srcCellRow, srcCellCol);
+                       double endDateVal = evaluateDateArg(args[1], srcCellRow, srcCellCol);
+                       result = YearFracCalculator.calculate(startDateVal, endDateVal, basis);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+
+               return new NumberEval(result);
+       }
+
+       private static double evaluateDateArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+               ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
+
+               if (ve instanceof StringEval) {
+                       String strVal = ((StringEval) ve).getStringValue();
+                       Double dVal = OperandResolver.parseDouble(strVal);
+                       if (dVal != null) {
+                               return dVal.doubleValue();
+                       }
+                       Calendar date = parseDate(strVal);
+                       return DateUtil.getExcelDate(date, false);
+               }
+               return OperandResolver.coerceValueToDouble(ve);
+       }
+
+       private static Calendar parseDate(String strVal) throws EvaluationException {
+               String[] parts = Pattern.compile("/").split(strVal);
+               if (parts.length != 3) {
+                       throw new EvaluationException(ErrorEval.VALUE_INVALID);
+               }
+               String part2 = parts[2];
+               int spacePos = part2.indexOf(' ');
+               if (spacePos > 0) {
+                       // drop time portion if present
+                       part2 = part2.substring(0, spacePos);
+               }
+               int f0;
+               int f1;
+               int f2;
+               try {
+                       f0 = Integer.parseInt(parts[0]);
+                       f1 = Integer.parseInt(parts[1]);
+                       f2 = Integer.parseInt(part2);
+               } catch (NumberFormatException e) {
+                       throw new EvaluationException(ErrorEval.VALUE_INVALID);
+               }
+               if (f0<0 || f1<0 || f2<0 || (f0>12 && f1>12 && f2>12)) {
+                       // easy to see this cannot be a valid date
+                       throw new EvaluationException(ErrorEval.VALUE_INVALID);
+               }
+
+               if (f0 >= 1900 && f0 < 9999) {
+                       // when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
+                       return makeDate(f0, f1, f2);
+               }
+               // otherwise the format seems to depend on OS settings (default date format)
+               if (false) {
+                       // MM/DD/YYYY is probably a good guess, if the in the US
+                       return makeDate(f2, f0, f1);
+               }
+               // TODO - find a way to choose the correct date format
+               throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
+       }
+
+       /**
+        * @param month 1-based
+        */
+       private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
+               if (month < 1 || month > 12) {
+                       throw new EvaluationException(ErrorEval.VALUE_INVALID);
+               }
+               Calendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
+               cal.set(Calendar.MILLISECOND, 0);
+               if (day <1 || day>cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
+                       throw new EvaluationException(ErrorEval.VALUE_INVALID);
+               }
+               cal.set(Calendar.DAY_OF_MONTH, day);
+               return cal;
+       }
+
+       private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+               ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
+               return OperandResolver.coerceValueToInt(ve);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/atp/YearFracCalculator.java b/src/java/org/apache/poi/ss/formula/atp/YearFracCalculator.java
new file mode 100644 (file)
index 0000000..765b23a
--- /dev/null
@@ -0,0 +1,344 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.usermodel.DateUtil;
+
+
+/**
+ * Internal calculation methods for Excel 'Analysis ToolPak' function YEARFRAC()<br/>
+ *  
+ * Algorithm inspired by www.dwheeler.com/yearfrac
+ * 
+ * @author Josh Micich
+ */
+final class YearFracCalculator {
+       /** use UTC time-zone to avoid daylight savings issues */
+       private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
+       private static final int MS_PER_HOUR = 60 * 60 * 1000;
+       private static final int MS_PER_DAY = 24 * MS_PER_HOUR;
+       private static final int DAYS_PER_NORMAL_YEAR = 365;
+       private static final int DAYS_PER_LEAP_YEAR = DAYS_PER_NORMAL_YEAR + 1;
+
+       /** the length of normal long months i.e. 31 */
+       private static final int LONG_MONTH_LEN = 31;
+       /** the length of normal short months i.e. 30 */
+       private static final int SHORT_MONTH_LEN = 30;
+       private static final int SHORT_FEB_LEN = 28;
+       private static final int LONG_FEB_LEN = SHORT_FEB_LEN + 1;
+
+       private YearFracCalculator() {
+               // no instances of this class
+       }
+
+
+       public static double calculate(double pStartDateVal, double pEndDateVal, int basis) throws EvaluationException {
+
+               if (basis < 0 || basis >= 5) {
+                       // if basis is invalid the result is #NUM!
+                       throw new EvaluationException(ErrorEval.NUM_ERROR);
+               }
+
+               // common logic for all bases
+
+               // truncate day values
+               int startDateVal = (int) Math.floor(pStartDateVal);
+               int endDateVal = (int) Math.floor(pEndDateVal);
+               if (startDateVal == endDateVal) {
+                       // when dates are equal, result is zero 
+                       return 0;
+               }
+               // swap start and end if out of order
+               if (startDateVal > endDateVal) {
+                       int temp = startDateVal;
+                       startDateVal = endDateVal;
+                       endDateVal = temp;
+               }
+
+               switch (basis) {
+                       case 0: return basis0(startDateVal, endDateVal);
+                       case 1: return basis1(startDateVal, endDateVal);
+                       case 2: return basis2(startDateVal, endDateVal);
+                       case 3: return basis3(startDateVal, endDateVal);
+                       case 4: return basis4(startDateVal, endDateVal);
+               }
+               throw new IllegalStateException("cannot happen");
+       }
+
+
+       /**
+        * @param startDateVal assumed to be less than or equal to endDateVal
+        * @param endDateVal assumed to be greater than or equal to startDateVal
+        */
+       public static double basis0(int startDateVal, int endDateVal) {
+               SimpleDate startDate = createDate(startDateVal);
+               SimpleDate endDate = createDate(endDateVal);
+               int date1day = startDate.day;
+               int date2day = endDate.day;
+
+               // basis zero has funny adjustments to the day-of-month fields when at end-of-month 
+               if (date1day == LONG_MONTH_LEN && date2day == LONG_MONTH_LEN) {
+                       date1day = SHORT_MONTH_LEN;
+                       date2day = SHORT_MONTH_LEN;
+               } else if (date1day == LONG_MONTH_LEN) {
+                       date1day = SHORT_MONTH_LEN;
+               } else if (date1day == SHORT_MONTH_LEN && date2day == LONG_MONTH_LEN) {
+                       date2day = SHORT_MONTH_LEN;
+                       // Note: If date2day==31, it STAYS 31 if date1day < 30.
+                       // Special fixes for February:
+               } else if (startDate.month == 2 && isLastDayOfMonth(startDate)) {
+                       // Note - these assignments deliberately set Feb 30 date.
+                       date1day = SHORT_MONTH_LEN;
+                       if (endDate.month == 2 && isLastDayOfMonth(endDate)) {
+                               // only adjusted when first date is last day in Feb
+                               date2day = SHORT_MONTH_LEN;
+                       }
+               }
+               return calculateAdjusted(startDate, endDate, date1day, date2day);
+       }
+       /**
+        * @param startDateVal assumed to be less than or equal to endDateVal
+        * @param endDateVal assumed to be greater than or equal to startDateVal
+        */
+       public static double basis1(int startDateVal, int endDateVal) {
+               SimpleDate startDate = createDate(startDateVal);
+               SimpleDate endDate = createDate(endDateVal);
+               double yearLength;
+               if (isGreaterThanOneYear(startDate, endDate)) {
+                       yearLength = averageYearLength(startDate.year, endDate.year);
+               } else if (shouldCountFeb29(startDate, endDate)) {
+                       yearLength = DAYS_PER_LEAP_YEAR;
+               } else {
+                       yearLength = DAYS_PER_NORMAL_YEAR;
+               }
+               return dateDiff(startDate.tsMilliseconds, endDate.tsMilliseconds) / yearLength;
+       }
+
+       /**
+        * @param startDateVal assumed to be less than or equal to endDateVal
+        * @param endDateVal assumed to be greater than or equal to startDateVal
+        */
+       public static double basis2(int startDateVal, int endDateVal) {
+               return (endDateVal - startDateVal) / 360.0;
+       }
+       /**
+        * @param startDateVal assumed to be less than or equal to endDateVal
+        * @param endDateVal assumed to be greater than or equal to startDateVal
+        */
+       public static double basis3(double startDateVal, double endDateVal) {
+               return (endDateVal - startDateVal) / 365.0;
+       }
+       /**
+        * @param startDateVal assumed to be less than or equal to endDateVal
+        * @param endDateVal assumed to be greater than or equal to startDateVal
+        */
+       public static double basis4(int startDateVal, int endDateVal) {
+               SimpleDate startDate = createDate(startDateVal);
+               SimpleDate endDate = createDate(endDateVal);
+               int date1day = startDate.day;
+               int date2day = endDate.day;
+
+
+               // basis four has funny adjustments to the day-of-month fields when at end-of-month 
+               if (date1day == LONG_MONTH_LEN) {
+                       date1day = SHORT_MONTH_LEN;
+               }
+               if (date2day == LONG_MONTH_LEN) {
+                       date2day = SHORT_MONTH_LEN;
+               }
+               // Note - no adjustments for end of Feb
+               return calculateAdjusted(startDate, endDate, date1day, date2day);
+       }
+
+
+       private static double calculateAdjusted(SimpleDate startDate, SimpleDate endDate, int date1day,
+                       int date2day) {
+               double dayCount 
+                       = (endDate.year - startDate.year) * 360
+                       + (endDate.month - startDate.month) * SHORT_MONTH_LEN
+                       + (date2day - date1day) * 1;
+               return dayCount / 360;
+       }
+
+       private static boolean isLastDayOfMonth(SimpleDate date) {
+               if (date.day < SHORT_FEB_LEN) {
+                       return false;
+               }
+               return date.day == getLastDayOfMonth(date);
+       }
+
+       private static int getLastDayOfMonth(SimpleDate date) {
+               switch (date.month) {
+                       case 1:
+                       case 3:
+                       case 5:
+                       case 7:
+                       case 8:
+                       case 10:
+                       case 12:
+                               return LONG_MONTH_LEN;
+                       case 4:
+                       case 6:
+                       case 9:
+                       case 11:
+                               return SHORT_MONTH_LEN;
+               }
+               if (isLeapYear(date.year)) {
+                       return LONG_FEB_LEN;
+               }
+               return SHORT_FEB_LEN;
+       }
+
+       /**
+        * Assumes dates are no more than 1 year apart.
+        * @return <code>true</code> if dates both within a leap year, or span a period including Feb 29
+        */
+       private static boolean shouldCountFeb29(SimpleDate start, SimpleDate end) {
+               boolean startIsLeapYear = isLeapYear(start.year);
+               if (startIsLeapYear && start.year == end.year) {
+                       // note - dates may not actually span Feb-29, but it gets counted anyway in this case
+                       return true;
+               }
+
+               boolean endIsLeapYear = isLeapYear(end.year);
+               if (!startIsLeapYear && !endIsLeapYear) {
+                       return false;
+               }
+               if (startIsLeapYear) {
+                       switch (start.month) {
+                               case SimpleDate.JANUARY:
+                               case SimpleDate.FEBRUARY:
+                                       return true;
+                       }
+                       return false;
+               }
+               if (endIsLeapYear) {
+                       switch (end.month) {
+                               case SimpleDate.JANUARY:
+                                       return false;
+                               case SimpleDate.FEBRUARY:
+                                       break;
+                               default:
+                                       return true;
+                       }
+                       return end.day == LONG_FEB_LEN;
+               }
+               return false;
+       }
+
+       /**
+        * @return the whole number of days between the two time-stamps.  Both time-stamps are
+        * assumed to represent 12:00 midnight on the respective day.
+        */
+       private static int dateDiff(long startDateMS, long endDateMS) {
+               long msDiff = endDateMS - startDateMS;
+
+               // some extra checks to make sure we don't hide some other bug with the rounding 
+               int remainderHours = (int) ((msDiff % MS_PER_DAY) / MS_PER_HOUR);
+               switch (remainderHours) {
+                       case 0:  // normal case
+                               break;
+                       case 1:  // transition from normal time to daylight savings adjusted
+                       case 23: // transition from daylight savings adjusted to normal time
+                               // Unexpected since we are using UTC_TIME_ZONE 
+                       default:
+                               throw new RuntimeException("Unexpected date diff between " + startDateMS + " and " + endDateMS);
+
+               }
+               return (int) (0.5 + ((double)msDiff / MS_PER_DAY));
+       }
+
+       private static double averageYearLength(int startYear, int endYear) {
+               int dayCount = 0;
+               for (int i=startYear; i<=endYear; i++) {
+                       dayCount += DAYS_PER_NORMAL_YEAR;
+                       if (isLeapYear(i)) {
+                               dayCount++;
+                       }
+               }
+               double numberOfYears = endYear-startYear+1;
+               return dayCount / numberOfYears;
+       }
+
+       private static boolean isLeapYear(int i) {
+               // leap years are always divisible by 4
+               if (i % 4 != 0) {
+                       return false;
+               }
+               // each 4th century is a leap year
+               if (i % 400 == 0) {
+                       return true;
+               }
+               // all other centuries are *not* leap years
+               if (i % 100 == 0) {
+                       return false;
+               }
+               return true;
+       }
+
+       private static boolean isGreaterThanOneYear(SimpleDate start, SimpleDate end) {
+               if (start.year == end.year) {
+                       return false;
+               }
+               if (start.year + 1 != end.year) {
+                       return true;
+               }
+
+               if (start.month > end.month) {
+                       return false;
+               }
+               if (start.month < end.month) {
+                       return true;
+               }
+
+               return start.day < end.day;
+       }
+
+       private static SimpleDate createDate(int dayCount) {
+               GregorianCalendar calendar = new GregorianCalendar(UTC_TIME_ZONE);
+               DateUtil.setCalendar(calendar, dayCount, 0, false);
+               return new SimpleDate(calendar);
+       }
+
+       private static final class SimpleDate {
+
+               public static final int JANUARY = 1;
+               public static final int FEBRUARY = 2;
+
+               public final int year;
+               /** 1-based month */
+               public final int month;
+               /** day of month */
+               public final int day;
+               /** milliseconds since 1970 */
+               public long tsMilliseconds;
+
+               public SimpleDate(Calendar cal) {
+                       year = cal.get(Calendar.YEAR);
+                       month = cal.get(Calendar.MONTH) + 1;
+                       day = cal.get(Calendar.DAY_OF_MONTH);
+                       tsMilliseconds = cal.getTimeInMillis();
+               }
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/AreaEval.java b/src/java/org/apache/poi/ss/formula/eval/AreaEval.java
new file mode 100644 (file)
index 0000000..ec2e41c
--- /dev/null
@@ -0,0 +1,93 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.TwoDEval;
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ *
+ */
+public interface AreaEval extends TwoDEval {
+
+    /**
+     * returns the 0-based index of the first row in
+     * this area.
+     */
+    int getFirstRow();
+
+    /**
+     * returns the 0-based index of the last row in
+     * this area.
+     */
+    int getLastRow();
+
+    /**
+     * returns the 0-based index of the first col in
+     * this area.
+     */
+    int getFirstColumn();
+
+    /**
+     * returns the 0-based index of the last col in
+     * this area.
+     */
+    int getLastColumn();
+
+    /**
+     * @return the ValueEval from within this area at the specified row and col index. Never
+     * <code>null</code> (possibly {@link BlankEval}).  The specified indexes should be absolute
+     * indexes in the sheet and not relative indexes within the area.
+     */
+    ValueEval getAbsoluteValue(int row, int col);
+
+    /**
+     * returns true if the cell at row and col specified
+     * as absolute indexes in the sheet is contained in
+     * this area.
+     * @param row
+     * @param col
+     */
+    boolean contains(int row, int col);
+
+    /**
+     * returns true if the specified col is in range
+     * @param col
+     */
+    boolean containsColumn(int col);
+
+    /**
+     * returns true if the specified row is in range
+     * @param row
+     */
+    boolean containsRow(int row);
+
+    int getWidth();
+    int getHeight();
+    /**
+     * @return the ValueEval from within this area at the specified relativeRowIndex and
+     * relativeColumnIndex. Never <code>null</code> (possibly {@link BlankEval}). The
+     * specified indexes should relative to the top left corner of this area.
+     */
+    ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
+
+    /**
+     * Creates an {@link AreaEval} offset by a relative amount from from the upper left cell
+     * of this area
+     */
+    AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx);
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/AreaEvalBase.java b/src/java/org/apache/poi/ss/formula/eval/AreaEvalBase.java
new file mode 100644 (file)
index 0000000..8f09e6e
--- /dev/null
@@ -0,0 +1,117 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.AreaI;
+
+/**
+ * @author Josh Micich
+ */
+public abstract class AreaEvalBase implements AreaEval {
+
+       private final int _firstColumn;
+       private final int _firstRow;
+       private final int _lastColumn;
+       private final int _lastRow;
+       private final int _nColumns;
+       private final int _nRows;
+
+       protected AreaEvalBase(int firstRow, int firstColumn, int lastRow, int lastColumn) {
+               _firstColumn = firstColumn;
+               _firstRow = firstRow;
+               _lastColumn = lastColumn;
+               _lastRow = lastRow;
+
+               _nColumns = _lastColumn - _firstColumn + 1;
+               _nRows = _lastRow - _firstRow + 1;
+       }
+
+       protected AreaEvalBase(AreaI ptg) {
+               _firstRow = ptg.getFirstRow();
+               _firstColumn = ptg.getFirstColumn();
+               _lastRow = ptg.getLastRow();
+               _lastColumn = ptg.getLastColumn();
+
+               _nColumns = _lastColumn - _firstColumn + 1;
+               _nRows = _lastRow - _firstRow + 1;
+       }
+
+       public final int getFirstColumn() {
+               return _firstColumn;
+       }
+
+       public final int getFirstRow() {
+               return _firstRow;
+       }
+
+       public final int getLastColumn() {
+               return _lastColumn;
+       }
+
+       public final int getLastRow() {
+               return _lastRow;
+       }
+       public final ValueEval getAbsoluteValue(int row, int col) {
+               int rowOffsetIx = row - _firstRow;
+               int colOffsetIx = col - _firstColumn;
+
+               if(rowOffsetIx < 0 || rowOffsetIx >= _nRows) {
+                       throw new IllegalArgumentException("Specified row index (" + row
+                                       + ") is outside the allowed range (" + _firstRow + ".." + _lastRow + ")");
+               }
+               if(colOffsetIx < 0 || colOffsetIx >= _nColumns) {
+                       throw new IllegalArgumentException("Specified column index (" + col
+                                       + ") is outside the allowed range (" + _firstColumn + ".." + col + ")");
+               }
+               return getRelativeValue(rowOffsetIx, colOffsetIx);
+       }
+
+       public final boolean contains(int row, int col) {
+               return _firstRow <= row && _lastRow >= row
+                       && _firstColumn <= col && _lastColumn >= col;
+       }
+
+       public final boolean containsRow(int row) {
+               return _firstRow <= row && _lastRow >= row;
+       }
+
+       public final boolean containsColumn(int col) {
+               return _firstColumn <= col && _lastColumn >= col;
+       }
+
+       public final boolean isColumn() {
+               return _firstColumn == _lastColumn;
+       }
+
+       public final boolean isRow() {
+               return _firstRow == _lastRow;
+       }
+       public int getHeight() {
+               return _lastRow-_firstRow+1;
+       }
+
+       public final ValueEval getValue(int row, int col) {
+               return getRelativeValue(row, col);
+       }
+
+       public abstract ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
+
+       public int getWidth() {
+               return _lastColumn-_firstColumn+1;
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/BlankEval.java b/src/java/org/apache/poi/ss/formula/eval/BlankEval.java
new file mode 100644 (file)
index 0000000..b49b299
--- /dev/null
@@ -0,0 +1,35 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; This class is a
+ *         marker class. It is a special value for empty cells.
+ */
+public final class BlankEval implements ValueEval {
+
+       public static final BlankEval instance = new BlankEval();
+       /**
+        * @deprecated (Nov 2009) use {@link #instance}
+        */
+       public static final BlankEval INSTANCE = instance;
+
+       private BlankEval() {
+               // enforce singleton
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/BoolEval.java b/src/java/org/apache/poi/ss/formula/eval/BoolEval.java
new file mode 100644 (file)
index 0000000..cab4105
--- /dev/null
@@ -0,0 +1,64 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class BoolEval implements NumericValueEval, StringValueEval {
+
+       private boolean _value;
+
+       public static final BoolEval FALSE = new BoolEval(false);
+
+       public static final BoolEval TRUE = new BoolEval(true);
+
+       /**
+        * Convenience method for the following:<br/>
+        * <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
+        *
+        * @return the <tt>BoolEval</tt> instance representing <tt>b</tt>.
+        */
+       public static final BoolEval valueOf(boolean b) {
+               return b ? TRUE : FALSE;
+       }
+
+       private BoolEval(boolean value) {
+               _value = value;
+       }
+
+       public boolean getBooleanValue() {
+               return _value;
+       }
+
+       public double getNumberValue() {
+               return _value ? 1 : 0;
+       }
+
+       public String getStringValue() {
+               return _value ? "TRUE" : "FALSE";
+       }
+
+       public String toString() {
+               StringBuilder sb = new StringBuilder(64);
+               sb.append(getClass().getName()).append(" [");
+               sb.append(getStringValue());
+               sb.append("]");
+               return sb.toString();
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/ConcatEval.java b/src/java/org/apache/poi/ss/formula/eval/ConcatEval.java
new file mode 100644 (file)
index 0000000..5622be8
--- /dev/null
@@ -0,0 +1,60 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class ConcatEval  extends Fixed2ArgFunction {
+
+       public static final Function instance = new ConcatEval();
+
+       private ConcatEval() {
+               // enforce singleton
+       }
+
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+               ValueEval ve0;
+               ValueEval ve1;
+               try {
+                       ve0 = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+                       ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               StringBuilder sb = new StringBuilder();
+               sb.append(getText(ve0));
+               sb.append(getText(ve1));
+               return new StringEval(sb.toString());
+       }
+
+       private Object getText(ValueEval ve) {
+               if (ve instanceof StringValueEval) {
+                       StringValueEval sve = (StringValueEval) ve;
+                       return sve.getStringValue();
+               }
+               if (ve == BlankEval.instance) {
+                       return "";
+               }
+               throw new IllegalAccessError("Unexpected value type ("
+                                       + ve.getClass().getName() + ")");
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/ErrorEval.java b/src/java/org/apache/poi/ss/formula/eval/ErrorEval.java
new file mode 100644 (file)
index 0000000..6cc8136
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.usermodel.ErrorConstants;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ *
+ */
+public final class ErrorEval implements ValueEval {
+
+    // convenient access to namespace
+    private static final ErrorConstants EC = null;
+
+    /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
+    public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL);
+    /** <b>#DIV/0!</b> - Division by zero */
+    public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0);
+    /** <b>#VALUE!</b> - Wrong type of operand */
+    public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE);
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+    public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF);
+    /** <b>#NAME?</b> - Wrong function or range name */
+    public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME);
+    /** <b>#NUM!</b> - Value range overflow */
+    public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM);
+    /** <b>#N/A</b> - Argument or function not available */
+    public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA);
+
+
+    // POI internal error codes
+    private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
+    private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
+
+    // Note - Excel does not seem to represent this condition with an error code
+    public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE);
+
+
+    /**
+     * Translates an Excel internal error code into the corresponding POI ErrorEval instance
+     * @param errorCode
+     */
+    public static ErrorEval valueOf(int errorCode) {
+        switch(errorCode) {
+            case ErrorConstants.ERROR_NULL:  return NULL_INTERSECTION;
+            case ErrorConstants.ERROR_DIV_0: return DIV_ZERO;
+            case ErrorConstants.ERROR_VALUE: return VALUE_INVALID;
+            case ErrorConstants.ERROR_REF:   return REF_INVALID;
+            case ErrorConstants.ERROR_NAME:  return NAME_INVALID;
+            case ErrorConstants.ERROR_NUM:   return NUM_ERROR;
+            case ErrorConstants.ERROR_NA:    return NA;
+            // non-std errors (conditions modeled as errors by POI)
+            case CIRCULAR_REF_ERROR_CODE:        return CIRCULAR_REF_ERROR;
+        }
+        throw new RuntimeException("Unexpected error code (" + errorCode + ")");
+    }
+
+    /**
+     * Converts error codes to text.  Handles non-standard error codes OK.  
+     * For debug/test purposes (and for formatting error messages).
+     * @return the String representation of the specified Excel error code.
+     */
+    public static String getText(int errorCode) {
+        if(ErrorConstants.isValidCode(errorCode)) {
+            return ErrorConstants.getText(errorCode);
+        }
+        // It is desirable to make these (arbitrary) strings look clearly different from any other
+        // value expression that might appear in a formula.  In addition these error strings should
+        // look unlike the standard Excel errors.  Hence tilde ('~') was used.
+        switch(errorCode) {
+            case CIRCULAR_REF_ERROR_CODE: return "~CIRCULAR~REF~";
+            case FUNCTION_NOT_IMPLEMENTED_CODE: return "~FUNCTION~NOT~IMPLEMENTED~";
+        }
+        return "~non~std~err(" + errorCode + ")~";
+    }
+
+    private int _errorCode;
+    /**
+     * @param errorCode an 8-bit value
+     */
+    private ErrorEval(int errorCode) {
+        _errorCode = errorCode;
+    }
+
+    public int getErrorCode() {
+        return _errorCode;
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer(64);
+        sb.append(getClass().getName()).append(" [");
+        sb.append(getText(_errorCode));
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/EvaluationException.java b/src/java/org/apache/poi/ss/formula/eval/EvaluationException.java
new file mode 100644 (file)
index 0000000..bb9d957
--- /dev/null
@@ -0,0 +1,134 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * This class is used to simplify error handling logic <i>within</i> operator and function
+ * implementations.   Note - <tt>OperationEval.evaluate()</tt> and <tt>Function.evaluate()</tt>
+ * method signatures do not throw this exception so it cannot propagate outside.<p/>
+ * 
+ * Here is an example coded without <tt>EvaluationException</tt>, to show how it can help:
+ * <pre>
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ *     // ...
+ *     Eval arg0 = args[0];
+ *     if(arg0 instanceof ErrorEval) {
+ *             return arg0;
+ *     }
+ *     if(!(arg0 instanceof AreaEval)) {
+ *             return ErrorEval.VALUE_INVALID;
+ *     }
+ *     double temp = 0;
+ *     AreaEval area = (AreaEval)arg0;
+ *     ValueEval[] values = area.getValues();
+ *     for (int i = 0; i < values.length; i++) {
+ *             ValueEval ve = values[i];
+ *             if(ve instanceof ErrorEval) {
+ *                     return ve;
+ *             }
+ *             if(!(ve instanceof NumericValueEval)) {
+ *                     return ErrorEval.VALUE_INVALID;
+ *             }
+ *             temp += ((NumericValueEval)ve).getNumberValue();
+ *     }
+ *     // ...
+ * }    
+ * </pre>
+ * In this example, if any error is encountered while processing the arguments, an error is 
+ * returned immediately. This code is difficult to refactor due to all the points where errors
+ * are returned.<br/>
+ * Using <tt>EvaluationException</tt> allows the error returning code to be consolidated to one
+ * place.<p/>
+ * <pre>
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ *     try {
+ *             // ...
+ *             AreaEval area = getAreaArg(args[0]);
+ *             double temp = sumValues(area.getValues());
+ *             // ...
+ *     } catch (EvaluationException e) {
+ *             return e.getErrorEval();
+ *     }
+ *}
+ *
+ *private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
+ *     if (arg0 instanceof ErrorEval) {
+ *             throw new EvaluationException((ErrorEval) arg0);
+ *     }
+ *     if (arg0 instanceof AreaEval) {
+ *             return (AreaEval) arg0;
+ *     }
+ *     throw EvaluationException.invalidValue();
+ *}
+ *
+ *private double sumValues(ValueEval[] values) throws EvaluationException {
+ *     double temp = 0;
+ *     for (int i = 0; i < values.length; i++) {
+ *             ValueEval ve = values[i];
+ *             if (ve instanceof ErrorEval) {
+ *                     throw new EvaluationException((ErrorEval) ve);
+ *             }
+ *             if (!(ve instanceof NumericValueEval)) {
+ *                     throw EvaluationException.invalidValue();
+ *             }
+ *             temp += ((NumericValueEval) ve).getNumberValue();
+ *     }
+ *     return temp;
+ *}
+ * </pre>   
+ * It is not mandatory to use EvaluationException, doing so might give the following advantages:<br/>
+ *  - Methods can more easily be extracted, allowing for re-use.<br/>
+ *  - Type management (typecasting etc) is simpler because error conditions have been separated from
+ * intermediate calculation values.<br/>
+ *  - Fewer local variables are required. Local variables can have stronger types.<br/>
+ *  - It is easier to mimic common Excel error handling behaviour (exit upon encountering first 
+ *  error), because exceptions conveniently propagate up the call stack regardless of execution 
+ *  points or the number of levels of nested calls.<p/>
+ *  
+ * <b>Note</b> - Only standard evaluation errors are represented by <tt>EvaluationException</tt> (
+ * i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions
+ * that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should
+ * be taken to not translate any POI internal error into an Excel evaluation error code.   
+ * 
+ * @author Josh Micich
+ */
+public final class EvaluationException extends Exception {
+       private final ErrorEval _errorEval;
+
+       public EvaluationException(ErrorEval errorEval) {
+               _errorEval = errorEval;
+       }
+       // some convenience factory methods
+
+    /** <b>#VALUE!</b> - Wrong type of operand */
+       public static EvaluationException invalidValue() {
+               return new EvaluationException(ErrorEval.VALUE_INVALID);
+       }
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+       public static EvaluationException invalidRef() {
+               return new EvaluationException(ErrorEval.REF_INVALID);
+       }
+    /** <b>#NUM!</b> - Value range overflow */
+       public static EvaluationException numberError() {
+               return new EvaluationException(ErrorEval.NUM_ERROR);
+       }
+       
+       public ErrorEval getErrorEval() {
+               return _errorEval;
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
new file mode 100644 (file)
index 0000000..85957da
--- /dev/null
@@ -0,0 +1,249 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.function.FunctionMetadata;
+import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
+import org.apache.poi.ss.formula.functions.*;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class FunctionEval {
+       /**
+        * Some function IDs that require special treatment
+        */
+       private static final class FunctionID {
+               /** 1 */
+               public static final int IF = FunctionMetadataRegistry.FUNCTION_INDEX_IF;
+               /** 4 */
+               public static final int SUM = FunctionMetadataRegistry.FUNCTION_INDEX_SUM;
+               /** 78 */
+               public static final int OFFSET = 78;
+               /** 100 */
+               public static final int CHOOSE = FunctionMetadataRegistry.FUNCTION_INDEX_CHOOSE;
+               /** 148 */
+               public static final int INDIRECT = FunctionMetadataRegistry.FUNCTION_INDEX_INDIRECT;
+               /** 255 */
+               public static final int EXTERNAL_FUNC = FunctionMetadataRegistry.FUNCTION_INDEX_EXTERNAL;
+       }
+       // convenient access to namespace
+       private static final FunctionID ID = null;
+
+       /**
+        * Array elements corresponding to unimplemented functions are <code>null</code>
+        */
+       protected static final Function[] functions = produceFunctions();
+
+       private static Function[] produceFunctions() {
+               Function[] retval = new Function[368];
+
+               retval[0] = new Count();
+               retval[ID.IF] = new IfFunc();
+               retval[2] = LogicalFunction.ISNA;
+               retval[3] = LogicalFunction.ISERROR;
+               retval[ID.SUM] = AggregateFunction.SUM;
+               retval[5] = AggregateFunction.AVERAGE;
+               retval[6] = AggregateFunction.MIN;
+               retval[7] = AggregateFunction.MAX;
+               retval[8] = new RowFunc(); // ROW
+               retval[9] = new Column();
+               retval[10] = new Na();
+               retval[11] = new Npv();
+               retval[12] = AggregateFunction.STDEV;
+               retval[13] = NumericFunction.DOLLAR;
+
+               retval[15] = NumericFunction.SIN;
+               retval[16] = NumericFunction.COS;
+               retval[17] = NumericFunction.TAN;
+               retval[18] = NumericFunction.ATAN;
+               retval[19] = NumericFunction.PI;
+               retval[20] = NumericFunction.SQRT;
+               retval[21] = NumericFunction.EXP;
+               retval[22] = NumericFunction.LN;
+               retval[23] = NumericFunction.LOG10;
+               retval[24] = NumericFunction.ABS;
+               retval[25] = NumericFunction.INT;
+               retval[26] = NumericFunction.SIGN;
+               retval[27] = NumericFunction.ROUND;
+               retval[28] = new Lookup();
+               retval[29] = new Index();
+
+               retval[31] = TextFunction.MID;
+               retval[32] = TextFunction.LEN;
+               retval[33] = new Value();
+               retval[34] = BooleanFunction.TRUE;
+               retval[35] = BooleanFunction.FALSE;
+               retval[36] = BooleanFunction.AND;
+               retval[37] = BooleanFunction.OR;
+               retval[38] = BooleanFunction.NOT;
+               retval[39] = NumericFunction.MOD;
+               retval[48] = TextFunction.TEXT;
+
+               retval[56] = FinanceFunction.PV;
+               retval[57] = FinanceFunction.FV;
+               retval[58] = FinanceFunction.NPER;
+               retval[59] = FinanceFunction.PMT;
+
+               retval[63] = NumericFunction.RAND;
+               retval[64] = new Match();
+               retval[65] = DateFunc.instance;
+               retval[66] = new TimeFunc();
+               retval[67] = CalendarFieldFunction.DAY;
+               retval[68] = CalendarFieldFunction.MONTH;
+               retval[69] = CalendarFieldFunction.YEAR;
+
+               retval[74] = new Now();
+
+               retval[76] = new Rows();
+               retval[77] = new Columns();
+               retval[82] = TextFunction.SEARCH;
+               retval[ID.OFFSET] = new Offset();
+               retval[82] = TextFunction.SEARCH;
+
+               retval[97] = NumericFunction.ATAN2;
+               retval[98] = NumericFunction.ASIN;
+               retval[99] = NumericFunction.ACOS;
+               retval[ID.CHOOSE] = new Choose();
+               retval[101] = new Hlookup();
+               retval[102] = new Vlookup();
+
+               retval[105] = LogicalFunction.ISREF;
+
+               retval[109] = NumericFunction.LOG;
+
+               retval[112] = TextFunction.LOWER;
+               retval[113] = TextFunction.UPPER;
+
+               retval[115] = TextFunction.LEFT;
+               retval[116] = TextFunction.RIGHT;
+               retval[117] = TextFunction.EXACT;
+               retval[118] = TextFunction.TRIM;
+               retval[119] = new Replace();
+               retval[120] = new Substitute();
+
+               retval[124] = TextFunction.FIND;
+
+               retval[127] = LogicalFunction.ISTEXT;
+               retval[128] = LogicalFunction.ISNUMBER;
+               retval[129] = LogicalFunction.ISBLANK;
+               retval[130] = new T();
+
+               retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
+
+               retval[169] = new Counta();
+
+               retval[183] = AggregateFunction.PRODUCT;
+               retval[184] = NumericFunction.FACT;
+
+               retval[190] = LogicalFunction.ISNONTEXT;
+               retval[197] = NumericFunction.TRUNC;
+               retval[198] = LogicalFunction.ISLOGICAL;
+
+               retval[212] = NumericFunction.ROUNDUP;
+               retval[213] = NumericFunction.ROUNDDOWN;
+
+        retval[220] = new Days360();
+               retval[221] = new Today();
+
+               retval[227] = AggregateFunction.MEDIAN;
+               retval[228] = new Sumproduct();
+               retval[229] = NumericFunction.SINH;
+               retval[230] = NumericFunction.COSH;
+               retval[231] = NumericFunction.TANH;
+               retval[232] = NumericFunction.ASINH;
+               retval[233] = NumericFunction.ACOSH;
+               retval[234] = NumericFunction.ATANH;
+
+               retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction
+
+               retval[261] = new Errortype();
+
+               retval[269] = AggregateFunction.AVEDEV;
+
+               retval[276] = NumericFunction.COMBIN;
+
+               retval[279] = new Even();
+
+               retval[285] = NumericFunction.FLOOR;
+
+               retval[288] = NumericFunction.CEILING;
+
+               retval[298] = new Odd();
+
+        retval[300] = NumericFunction.POISSON;
+
+               retval[303] = new Sumxmy2();
+               retval[304] = new Sumx2my2();
+               retval[305] = new Sumx2py2();
+
+               retval[318] = AggregateFunction.DEVSQ;
+
+               retval[321] = AggregateFunction.SUMSQ;
+
+               retval[325] = AggregateFunction.LARGE;
+               retval[326] = AggregateFunction.SMALL;
+
+               retval[330] = new Mode();
+
+               retval[336] = TextFunction.CONCATENATE;
+               retval[337] = NumericFunction.POWER;
+
+               retval[342] = NumericFunction.RADIANS;
+               retval[343] = NumericFunction.DEGREES;
+
+               retval[344] = new Subtotal();
+               retval[345] = new Sumif();
+               retval[346] = new Countif();
+               retval[347] = new Countblank();
+
+               retval[359] = new Hyperlink();
+
+               retval[362] = MinaMaxa.MAXA;
+               retval[363] = MinaMaxa.MINA;
+
+               for (int i = 0; i < retval.length; i++) {
+                       Function f = retval[i];
+                       if (f == null) {
+                               FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByIndex(i);
+                               if (fm == null) {
+                                       continue;
+                               }
+                               retval[i] = new NotImplementedFunction(fm.getName());
+                       }
+               }
+               return retval;
+       }
+       /**
+        * @return <code>null</code> if the specified functionIndex is for INDIRECT() or any external (add-in) function.
+        */
+       public static Function getBasicFunction(int functionIndex) {
+               // check for 'free ref' functions first
+               switch (functionIndex) {
+                       case FunctionID.INDIRECT:
+                       case FunctionID.EXTERNAL_FUNC:
+                               return null;
+               }
+               // else - must be plain function
+               Function result = functions[functionIndex];
+               if (result == null) {
+                       throw new NotImplementedException("FuncIx=" + functionIndex);
+               }
+               return result;
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/IntersectionEval.java b/src/java/org/apache/poi/ss/formula/eval/IntersectionEval.java
new file mode 100644 (file)
index 0000000..4063790
--- /dev/null
@@ -0,0 +1,96 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Josh Micich
+ */
+public final class IntersectionEval  extends Fixed2ArgFunction {
+
+       public static final Function instance = new IntersectionEval();
+
+       private IntersectionEval() {
+               // enforces singleton
+       }
+
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+               try {
+                       AreaEval reA = evaluateRef(arg0);
+                       AreaEval reB = evaluateRef(arg1);
+                       AreaEval result = resolveRange(reA, reB);
+                       if (result == null) {
+                               return ErrorEval.NULL_INTERSECTION;
+                       }
+                       return result;
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+       }
+
+       /**
+        * @return simple rectangular {@link AreaEval} which represents the intersection of areas
+        * <tt>aeA</tt> and <tt>aeB</tt>. If the two areas do not intersect, the result is <code>null</code>.
+        */
+       private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
+
+               int aeAfr = aeA.getFirstRow();
+               int aeAfc = aeA.getFirstColumn();
+               int aeBlc = aeB.getLastColumn();
+               if (aeAfc > aeBlc) {
+                       return null;
+               }
+               int aeBfc = aeB.getFirstColumn();
+               if (aeBfc > aeA.getLastColumn()) {
+                       return null;
+               }
+               int aeBlr = aeB.getLastRow();
+               if (aeAfr > aeBlr) {
+                       return null;
+               }
+               int aeBfr = aeB.getFirstRow();
+               int aeAlr = aeA.getLastRow();
+               if (aeBfr > aeAlr) {
+                       return null;
+               }
+
+
+               int top = Math.max(aeAfr, aeBfr);
+               int bottom = Math.min(aeAlr, aeBlr);
+               int left = Math.max(aeAfc, aeBfc);
+               int right = Math.min(aeA.getLastColumn(), aeBlc);
+
+               return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
+       }
+
+       private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
+               if (arg instanceof AreaEval) {
+                       return (AreaEval) arg;
+               }
+               if (arg instanceof RefEval) {
+                       return ((RefEval) arg).offset(0, 0, 0, 0);
+               }
+               if (arg instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval)arg);
+               }
+               throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/MissingArgEval.java b/src/java/org/apache/poi/ss/formula/eval/MissingArgEval.java
new file mode 100644 (file)
index 0000000..70d2504
--- /dev/null
@@ -0,0 +1,36 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * Represents the (intermediate) evaluated result of a missing function argument.  In most cases
+ * this can be translated into {@link BlankEval} but there are some notable exceptions.  Functions
+ * COUNT and COUNTA <em>do</em> count their missing args.  Note - the differences between
+ * {@link MissingArgEval} and {@link BlankEval} have not been investigated fully, so the POI
+ * evaluator may need to be updated to account for these as they are found.
+ *
+ * @author Josh Micich
+ */
+public final class MissingArgEval implements ValueEval {
+
+       public static final MissingArgEval instance = new MissingArgEval();
+
+       private MissingArgEval() {
+               // enforce singleton
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/NameEval.java b/src/java/org/apache/poi/ss/formula/eval/NameEval.java
new file mode 100644 (file)
index 0000000..decefb9
--- /dev/null
@@ -0,0 +1,46 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Josh Micich
+ */
+public final class NameEval implements ValueEval {
+
+       private final String _functionName;
+
+       /**
+        * Creates a NameEval representing a function name
+        */
+       public NameEval(String functionName) {
+               _functionName = functionName;
+       }
+
+
+       public String getFunctionName() {
+               return _functionName;
+       }
+
+       public String toString() {
+               StringBuffer sb = new StringBuffer(64);
+               sb.append(getClass().getName()).append(" [");
+               sb.append(_functionName);
+               sb.append("]");
+               return sb.toString();
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/NameXEval.java b/src/java/org/apache/poi/ss/formula/eval/NameXEval.java
new file mode 100644 (file)
index 0000000..951d461
--- /dev/null
@@ -0,0 +1,44 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.NameXPtg;
+
+/**
+ * @author Josh Micich
+ */
+public final class NameXEval implements ValueEval {
+
+       private final NameXPtg _ptg;
+
+       public NameXEval(NameXPtg ptg) {
+               _ptg = ptg;
+       }
+
+       public NameXPtg getPtg() {
+               return _ptg;
+       }
+
+       public String toString() {
+               StringBuffer sb = new StringBuffer(64);
+               sb.append(getClass().getName()).append(" [");
+               sb.append(_ptg.getSheetRefIndex()).append(", ").append(_ptg.getNameIndex());
+               sb.append("]");
+               return sb.toString();
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/NumberEval.java b/src/java/org/apache/poi/ss/formula/eval/NumberEval.java
new file mode 100644 (file)
index 0000000..e500f70
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+ * Created on May 8, 2005
+ *
+ */
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.IntPtg;
+import org.apache.poi.hssf.record.formula.NumberPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.ss.util.NumberToTextConverter;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ *  
+ */
+public final class NumberEval implements NumericValueEval, StringValueEval {
+    
+    public static final NumberEval ZERO = new NumberEval(0);
+
+    private final double _value;
+    private String _stringValue;
+
+    public NumberEval(Ptg ptg) {
+        if (ptg == null) {
+            throw new IllegalArgumentException("ptg must not be null");
+        }
+        if (ptg instanceof IntPtg) {
+            _value = ((IntPtg) ptg).getValue();
+        } else if (ptg instanceof NumberPtg) {
+            _value = ((NumberPtg) ptg).getValue();
+        } else {
+            throw new IllegalArgumentException("bad argument type (" + ptg.getClass().getName() + ")");
+        }
+    }
+
+    public NumberEval(double value) {
+        _value = value;
+    }
+
+    public double getNumberValue() {
+        return _value;
+    }
+
+    public String getStringValue() {
+        if (_stringValue == null) {
+            _stringValue = NumberToTextConverter.toText(_value);
+        }
+        return _stringValue;
+    }
+    public final String toString() {
+        StringBuffer sb = new StringBuffer(64);
+        sb.append(getClass().getName()).append(" [");
+        sb.append(getStringValue());
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/NumericValueEval.java b/src/java/org/apache/poi/ss/formula/eval/NumericValueEval.java
new file mode 100644 (file)
index 0000000..056f21c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+ * Created on May 8, 2005
+ *
+ */
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ *  
+ */
+public interface NumericValueEval extends ValueEval {
+
+    public abstract double getNumberValue();
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/OperandResolver.java b/src/java/org/apache/poi/ss/formula/eval/OperandResolver.java
new file mode 100644 (file)
index 0000000..eea1a38
--- /dev/null
@@ -0,0 +1,324 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import java.util.regex.Pattern;
+
+/**
+ * Provides functionality for evaluating arguments to functions and operators.
+ *
+ * @author Josh Micich
+ * @author Brendan Nolan
+ */
+public final class OperandResolver {
+
+       // Based on regular expression defined in JavaDoc at {@link java.lang.Double#valueOf}
+       // modified to remove support for NaN, Infinity, Hexadecimal support and floating type suffixes
+    private static final String Digits = "(\\p{Digit}+)";
+    private static final String Exp    = "[eE][+-]?"+Digits;
+    private static final String fpRegex        =
+                   ("[\\x00-\\x20]*" + 
+                    "[+-]?(" + 
+                    "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+        
+                    "(\\.("+Digits+")("+Exp+")?))))"+       
+                    "[\\x00-\\x20]*"); 
+           
+       
+       private OperandResolver() {
+               // no instances of this class
+       }
+
+       /**
+        * Retrieves a single value from a variety of different argument types according to standard
+        * Excel rules.  Does not perform any type conversion.
+        * @param arg the evaluated argument as passed to the function or operator.
+        * @param srcCellRow used when arg is a single column AreaRef
+        * @param srcCellCol used when arg is a single row AreaRef
+        * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>.
+        * Never <code>null</code> or <tt>ErrorEval</tt>.
+        * @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
+        *  an AreaEval.  If the actual value retrieved is an ErrorEval, a corresponding
+        *  EvaluationException is thrown.
+        */
+       public static ValueEval getSingleValue(ValueEval arg, int srcCellRow, int srcCellCol)
+                       throws EvaluationException {
+               ValueEval result;
+               if (arg instanceof RefEval) {
+                       result = ((RefEval) arg).getInnerValueEval();
+               } else if (arg instanceof AreaEval) {
+                       result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
+               } else {
+                       result = arg;
+               }
+               if (result instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) result);
+               }
+               return result;
+       }
+
+       /**
+        * Implements (some perhaps not well known) Excel functionality to select a single cell from an
+        * area depending on the coordinates of the calling cell.  Here is an example demonstrating
+        * both selection from a single row area and a single column area in the same formula.
+        *
+        *    <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
+        *      <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
+        *      <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
+        *      <tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr>
+        *      <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr>
+        *      <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>400</td></tr>
+        *    </table>
+        *
+        * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
+        * will look like this:
+        *
+        *    <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
+        *      <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
+        *      <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
+        *      <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
+        *      <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
+        *      <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
+        *    </table>
+        *
+        * Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does
+        * not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula
+        * as written, but in the 4 cells A2:B3, the row and column selection works ok.<p/>
+        *
+        * The same concept is extended to references across sheets, such that even multi-row,
+        * multi-column areas can be useful.<p/>
+        *
+        * Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
+        * hence this method <b>can</b> throw a 'circular reference' EvaluationException.  Note that
+        * this method does not attempt to detect cycles.  Every cell in the specified Area <tt>ae</tt>
+        * has already been evaluated prior to this method call.  Any cell (or cell<b>s</b>) part of
+        * <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will
+        * already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon entry to this method.  It
+        * is assumed logic exists elsewhere to produce this behaviour.
+        *
+        * @return whatever the selected cell's evaluated value is.  Never <code>null</code>. Never
+        *  <tt>ErrorEval</tt>.
+        * @throws EvaluationException if there is a problem with indexing into the area, or if the
+        *  evaluated cell has an error.
+        */
+       public static ValueEval chooseSingleElementFromArea(AreaEval ae,
+                       int srcCellRow, int srcCellCol) throws EvaluationException {
+               ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
+               if (result instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) result);
+               }
+               return result;
+       }
+
+       /**
+        * @return possibly <tt>ErrorEval</tt>, and <code>null</code>
+        */
+       private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
+                       int srcCellRow, int srcCellCol) throws EvaluationException {
+
+               if(false) {
+                       // this is too simplistic
+                       if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
+                               throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
+                       }
+               /*
+               Circular references are not dealt with directly here, but it is worth noting some issues.
+
+               ANY one of the return statements in this method could return a cell that is identical
+               to the one immediately being evaluated.  The evaluating cell is identified by srcCellRow,
+               srcCellRow AND sheet.  The sheet is not available in any nearby calling method, so that's
+               one reason why circular references are not easy to detect here. (The sheet of the returned
+               cell can be obtained from ae if it is an Area3DEval.)
+
+               Another reason there's little value in attempting to detect circular references here is
+               that only direct circular references could be detected.  If the cycle involved two or more
+               cells this method could not detect it.
+
+               Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
+               (and FormulaEvaluator).
+                */
+               }
+
+               if (ae.isColumn()) {
+                       if(ae.isRow()) {
+                               return ae.getRelativeValue(0, 0);
+                       }
+                       if(!ae.containsRow(srcCellRow)) {
+                               throw EvaluationException.invalidValue();
+                       }
+                       return ae.getAbsoluteValue(srcCellRow, ae.getFirstColumn());
+               }
+               if(!ae.isRow()) {
+                       // multi-column, multi-row area
+                       if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
+                               return ae.getAbsoluteValue(ae.getFirstRow(), ae.getFirstColumn());
+                       }
+                       throw EvaluationException.invalidValue();
+               }
+               if(!ae.containsColumn(srcCellCol)) {
+                       throw EvaluationException.invalidValue();
+               }
+               return ae.getAbsoluteValue(ae.getFirstRow(), srcCellCol);
+       }
+
+       /**
+        * Applies some conversion rules if the supplied value is not already an integer.<br/>
+        * Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ).
+        * Note - <tt>BlankEval</tt> is converted to <code>0</code>.<p/>
+        *
+        * Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
+        * The equivalent java code is:<br/>
+        * &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
+        * <b>not</b>:<br/>
+        * &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward zero</code>
+        *
+        */
+       public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
+               if (ev == BlankEval.instance) {
+                       return 0;
+               }
+               double d = coerceValueToDouble(ev);
+               // Note - the standard java type conversion from double to int truncates toward zero.
+               // but Math.floor() truncates toward negative infinity
+               return (int)Math.floor(d);
+       }
+
+       /**
+        * Applies some conversion rules if the supplied value is not already a number.
+        * Note - <tt>BlankEval</tt> is converted to {@link NumberEval#ZERO}.
+        * @param ev must be a {@link NumberEval}, {@link StringEval}, {@link BoolEval} or
+        * {@link BlankEval}
+        * @return actual, parsed or interpreted double value (respectively).
+        * @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
+        * as a double (See <tt>parseDouble()</tt> for allowable formats).
+        * @throws RuntimeException if the supplied parameter is not {@link NumberEval},
+        * {@link StringEval}, {@link BoolEval} or {@link BlankEval}
+        */
+       public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
+
+               if (ev == BlankEval.instance) {
+                       return 0.0;
+               }
+               if (ev instanceof NumericValueEval) {
+                       // this also handles booleans
+                       return ((NumericValueEval)ev).getNumberValue();
+               }
+               if (ev instanceof StringEval) {
+                       Double dd = parseDouble(((StringEval) ev).getStringValue());
+                       if (dd == null) {
+                               throw EvaluationException.invalidValue();
+                       }
+                       return dd.doubleValue();
+               }
+               throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
+       }
+
+       /**
+        * Converts a string to a double using standard rules that Excel would use.<br/>
+        * Tolerates leading and trailing spaces, <p/>
+        * 
+        * Doesn't support currency prefixes, commas, percentage signs or arithmetic operations strings.  
+        *
+        *  Some examples:<br/>
+        *  " 123 " -&gt; 123.0<br/>
+        *  ".123" -&gt; 0.123<br/>
+        *  "1E4" -&gt; 1000<br/>
+        *  "-123" -&gt; -123.0<br/>
+        *  These not supported yet:<br/>
+        *  " $ 1,000.00 " -&gt; 1000.0<br/>
+        *  "$1.25E4" -&gt; 12500.0<br/>
+        *  "5**2" -&gt; 500<br/>
+        *  "250%" -&gt; 2.5<br/>
+        *
+        * @return <code>null</code> if the specified text cannot be parsed as a number
+        */
+       public static Double parseDouble(String pText) {
+               
+           if (Pattern.matches(fpRegex, pText))
+                       try {
+                               return Double.parseDouble(pText);
+                       } catch (NumberFormatException e) {
+                               return null;
+                       }
+           else {
+               return null;
+           }
+               
+       }
+
+       /**
+        * @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
+        * @return the converted string value. never <code>null</code>
+        */
+       public static String coerceValueToString(ValueEval ve) {
+               if (ve instanceof StringValueEval) {
+                       StringValueEval sve = (StringValueEval) ve;
+                       return sve.getStringValue();
+               }
+               if (ve == BlankEval.instance) {
+                       return "";
+               }
+               throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
+       }
+
+       /**
+        * @return <code>null</code> to represent blank values
+        * @throws EvaluationException if ve is an ErrorEval, or if a string value cannot be converted
+        */
+       public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException {
+
+               if (ve == null || ve == BlankEval.instance) {
+                       // TODO - remove 've == null' condition once AreaEval is fixed
+                       return null;
+               }
+               if (ve instanceof BoolEval) {
+                       return Boolean.valueOf(((BoolEval) ve).getBooleanValue());
+               }
+
+               if (ve == BlankEval.instance) {
+                       return null;
+               }
+
+               if (ve instanceof StringEval) {
+                       if (stringsAreBlanks) {
+                               return null;
+                       }
+                       String str = ((StringEval) ve).getStringValue();
+                       if (str.equalsIgnoreCase("true")) {
+                               return Boolean.TRUE;
+                       }
+                       if (str.equalsIgnoreCase("false")) {
+                               return Boolean.FALSE;
+                       }
+                       // else - string cannot be converted to boolean
+                       throw new EvaluationException(ErrorEval.VALUE_INVALID);
+               }
+
+               if (ve instanceof NumericValueEval) {
+                       NumericValueEval ne = (NumericValueEval) ve;
+                       double d = ne.getNumberValue();
+                       if (Double.isNaN(d)) {
+                               throw new EvaluationException(ErrorEval.VALUE_INVALID);
+                       }
+                       return Boolean.valueOf(d != 0);
+               }
+               if (ve instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) ve);
+               }
+               throw new RuntimeException("Unexpected eval (" + ve.getClass().getName() + ")");
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/PercentEval.java b/src/java/org/apache/poi/ss/formula/eval/PercentEval.java
new file mode 100644 (file)
index 0000000..263574a
--- /dev/null
@@ -0,0 +1,49 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+
+/**
+ * Implementation of Excel formula token '%'. <p/>
+ * @author Josh Micich
+ */
+public final class PercentEval extends Fixed1ArgFunction {
+
+       public static final Function instance = new PercentEval();
+
+       private PercentEval() {
+               // enforce singleton
+       }
+
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+               double d;
+               try {
+                       ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+                       d = OperandResolver.coerceValueToDouble(ve);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               if (d == 0.0) { // this '==' matches +0.0 and -0.0
+                       return NumberEval.ZERO;
+               }
+               return new NumberEval(d / 100);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/RangeEval.java b/src/java/org/apache/poi/ss/formula/eval/RangeEval.java
new file mode 100644 (file)
index 0000000..617d20a
--- /dev/null
@@ -0,0 +1,75 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+
+/**
+ *
+ * @author Josh Micich
+ */
+public final class RangeEval extends Fixed2ArgFunction {
+
+       public static final Function instance = new RangeEval();
+
+       private RangeEval() {
+               // enforces singleton
+       }
+
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+               try {
+                       AreaEval reA = evaluateRef(arg0);
+                       AreaEval reB = evaluateRef(arg1);
+                       return resolveRange(reA, reB);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+       }
+
+       /**
+        * @return simple rectangular {@link AreaEval} which fully encloses both areas
+        * <tt>aeA</tt> and <tt>aeB</tt>
+        */
+       private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
+               int aeAfr = aeA.getFirstRow();
+               int aeAfc = aeA.getFirstColumn();
+
+               int top = Math.min(aeAfr, aeB.getFirstRow());
+               int bottom = Math.max(aeA.getLastRow(), aeB.getLastRow());
+               int left = Math.min(aeAfc, aeB.getFirstColumn());
+               int right = Math.max(aeA.getLastColumn(), aeB.getLastColumn());
+
+               return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
+       }
+
+       private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
+               if (arg instanceof AreaEval) {
+                       return (AreaEval) arg;
+               }
+               if (arg instanceof RefEval) {
+                       return ((RefEval) arg).offset(0, 0, 0, 0);
+               }
+               if (arg instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval)arg);
+               }
+               throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/RefEval.java b/src/java/org/apache/poi/ss/formula/eval/RefEval.java
new file mode 100644 (file)
index 0000000..768e90a
--- /dev/null
@@ -0,0 +1,51 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ *
+ * RefEval is the super interface for Ref2D and Ref3DEval. Basically a RefEval
+ * impl should contain reference to the original ReferencePtg or Ref3DPtg as
+ * well as the final "value" resulting from the evaluation of the cell
+ * reference. Thus if the Cell has type CELL_TYPE_NUMERIC, the contained
+ * value object should be of type NumberEval; if cell type is CELL_TYPE_STRING,
+ * contained value object should be of type StringEval
+ */
+public interface RefEval extends ValueEval {
+
+    /**
+     * @return the evaluated value of the cell referred to by this RefEval.
+     */
+    ValueEval getInnerValueEval();
+
+    /**
+     * returns the zero based column index.
+     */
+    int getColumn();
+
+    /**
+     * returns the zero based row index.
+     */
+    int getRow();
+
+    /**
+     * Creates an {@link AreaEval} offset by a relative amount from this RefEval
+     */
+    AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx);
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/RefEvalBase.java b/src/java/org/apache/poi/ss/formula/eval/RefEvalBase.java
new file mode 100644 (file)
index 0000000..83d20fb
--- /dev/null
@@ -0,0 +1,40 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * Common base class for implementors of {@link RefEval}
+ *
+ * @author Josh Micich
+ */
+public abstract class RefEvalBase implements RefEval {
+
+       private final int _rowIndex;
+       private final int _columnIndex;
+
+       protected RefEvalBase(int rowIndex, int columnIndex) {
+               _rowIndex = rowIndex;
+               _columnIndex = columnIndex;
+       }
+       public final int getRow() {
+               return _rowIndex;
+       }
+       public final int getColumn() {
+               return _columnIndex;
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/RelationalOperationEval.java b/src/java/org/apache/poi/ss/formula/eval/RelationalOperationEval.java
new file mode 100644 (file)
index 0000000..8b3be19
--- /dev/null
@@ -0,0 +1,168 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+import org.apache.poi.ss.util.NumberComparer;
+
+/**
+ * Base class for all comparison operator evaluators
+ *
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public abstract class RelationalOperationEval extends Fixed2ArgFunction {
+
+       /**
+        * Converts a standard compare result (-1, 0, 1) to <code>true</code> or <code>false</code>
+        * according to subclass' comparison type.
+        */
+       protected abstract boolean convertComparisonResult(int cmpResult);
+
+       /**
+        * This is a description of how the relational operators apply in MS Excel.
+        * Use this as a guideline when testing/implementing the evaluate methods
+        * for the relational operators Evals.
+        *
+        * <pre>
+        * Bool.TRUE > any number.
+        * Bool > any string. ALWAYS
+        * Bool.TRUE > Bool.FALSE
+        * Bool.FALSE == Blank
+        *
+        * Strings are never converted to numbers or booleans
+        * String > any number. ALWAYS
+        * Non-empty String > Blank
+        * Empty String == Blank
+        * String are sorted dictionary wise
+        *
+        * Blank > Negative numbers
+        * Blank == 0
+        * Blank < Positive numbers
+        * </pre>
+        */
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+               ValueEval vA;
+               ValueEval vB;
+               try {
+                       vA = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+                       vB = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               int cmpResult = doCompare(vA, vB);
+               boolean result = convertComparisonResult(cmpResult);
+               return BoolEval.valueOf(result);
+       }
+
+       private static int doCompare(ValueEval va, ValueEval vb) {
+               // special cases when one operand is blank
+               if (va == BlankEval.instance) {
+                       return compareBlank(vb);
+               }
+               if (vb == BlankEval.instance) {
+                       return -compareBlank(va);
+               }
+
+               if (va instanceof BoolEval) {
+                       if (vb instanceof BoolEval) {
+                               BoolEval bA = (BoolEval) va;
+                               BoolEval bB = (BoolEval) vb;
+                               if (bA.getBooleanValue() == bB.getBooleanValue()) {
+                                       return 0;
+                               }
+                               return bA.getBooleanValue() ? 1 : -1;
+                       }
+                       return 1;
+               }
+               if (vb instanceof BoolEval) {
+                       return -1;
+               }
+               if (va instanceof StringEval) {
+                       if (vb instanceof StringEval) {
+                               StringEval sA = (StringEval) va;
+                               StringEval sB = (StringEval) vb;
+                               return sA.getStringValue().compareToIgnoreCase(sB.getStringValue());
+                       }
+                       return 1;
+               }
+               if (vb instanceof StringEval) {
+                       return -1;
+               }
+               if (va instanceof NumberEval) {
+                       if (vb instanceof NumberEval) {
+                               NumberEval nA = (NumberEval) va;
+                               NumberEval nB = (NumberEval) vb;
+                               return NumberComparer.compare(nA.getNumberValue(), nB.getNumberValue());
+                       }
+               }
+               throw new IllegalArgumentException("Bad operand types (" + va.getClass().getName() + "), ("
+                               + vb.getClass().getName() + ")");
+       }
+
+       private static int compareBlank(ValueEval v) {
+               if (v == BlankEval.instance) {
+                       return 0;
+               }
+               if (v instanceof BoolEval) {
+                       BoolEval boolEval = (BoolEval) v;
+                       return boolEval.getBooleanValue() ? -1 : 0;
+               }
+               if (v instanceof NumberEval) {
+                       NumberEval ne = (NumberEval) v;
+                       return NumberComparer.compare(0.0, ne.getNumberValue());
+               }
+               if (v instanceof StringEval) {
+                       StringEval se = (StringEval) v;
+                       return se.getStringValue().length() < 1 ? 0 : -1;
+               }
+               throw new IllegalArgumentException("bad value class (" + v.getClass().getName() + ")");
+       }
+
+       public static final Function EqualEval = new RelationalOperationEval() {
+               protected boolean convertComparisonResult(int cmpResult) {
+                       return cmpResult == 0;
+               }
+       };
+       public static final Function GreaterEqualEval = new RelationalOperationEval() {
+               protected boolean convertComparisonResult(int cmpResult) {
+                       return cmpResult >= 0;
+               }
+       };
+       public static final Function GreaterThanEval = new RelationalOperationEval() {
+               protected boolean convertComparisonResult(int cmpResult) {
+                       return cmpResult > 0;
+               }
+       };
+       public static final Function LessEqualEval = new RelationalOperationEval() {
+               protected boolean convertComparisonResult(int cmpResult) {
+                       return cmpResult <= 0;
+               }
+       };
+       public static final Function LessThanEval = new RelationalOperationEval() {
+               protected boolean convertComparisonResult(int cmpResult) {
+                       return cmpResult < 0;
+               }
+       };
+       public static final Function NotEqualEval = new RelationalOperationEval() {
+               protected boolean convertComparisonResult(int cmpResult) {
+                       return cmpResult != 0;
+               }
+       };
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/StringEval.java b/src/java/org/apache/poi/ss/formula/eval/StringEval.java
new file mode 100644 (file)
index 0000000..b2596fa
--- /dev/null
@@ -0,0 +1,54 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.StringPtg;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class StringEval implements StringValueEval {
+
+       public static final StringEval EMPTY_INSTANCE = new StringEval("");
+
+       private final String _value;
+
+       public StringEval(Ptg ptg) {
+               this(((StringPtg) ptg).getValue());
+       }
+
+       public StringEval(String value) {
+               if (value == null) {
+                       throw new IllegalArgumentException("value must not be null");
+               }
+               _value = value;
+       }
+
+       public String getStringValue() {
+               return _value;
+       }
+
+       public String toString() {
+               StringBuilder sb = new StringBuilder(64);
+               sb.append(getClass().getName()).append(" [");
+               sb.append(_value);
+               sb.append("]");
+               return sb.toString();
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/StringValueEval.java b/src/java/org/apache/poi/ss/formula/eval/StringValueEval.java
new file mode 100644 (file)
index 0000000..4e1b712
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ *  
+ */
+public interface StringValueEval extends ValueEval {
+
+    /**
+     * @return never <code>null</code>, possibly empty string.
+     */
+    String getStringValue();
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/TwoOperandNumericOperation.java b/src/java/org/apache/poi/ss/formula/eval/TwoOperandNumericOperation.java
new file mode 100644 (file)
index 0000000..a4c05d9
--- /dev/null
@@ -0,0 +1,87 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Josh Micich
+ */
+public abstract class TwoOperandNumericOperation extends Fixed2ArgFunction {
+
+       protected final double singleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+               ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
+               return OperandResolver.coerceValueToDouble(ve);
+       }
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+               double result;
+               try {
+                       double d0 = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex);
+                       double d1 = singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex);
+                       result = evaluate(d0, d1);
+                       if (result == 0.0) { // this '==' matches +0.0 and -0.0
+                               // Excel converts -0.0 to +0.0 for '*', '/', '%', '+' and '^'
+                               if (!(this instanceof SubtractEvalClass)) {
+                                       return NumberEval.ZERO;
+                               }
+                       }
+                       if (Double.isNaN(result) || Double.isInfinite(result)) {
+                               return ErrorEval.NUM_ERROR;
+                       }
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               return new NumberEval(result);
+       }
+
+       protected abstract double evaluate(double d0, double d1) throws EvaluationException;
+
+       public static final Function AddEval = new TwoOperandNumericOperation() {
+               protected double evaluate(double d0, double d1) {
+                       return d0+d1;
+               }
+       };
+       public static final Function DivideEval = new TwoOperandNumericOperation() {
+               protected double evaluate(double d0, double d1) throws EvaluationException {
+                       if (d1 == 0.0) {
+                               throw new EvaluationException(ErrorEval.DIV_ZERO);
+                       }
+                       return d0/d1;
+               }
+       };
+       public static final Function MultiplyEval = new TwoOperandNumericOperation() {
+               protected double evaluate(double d0, double d1) {
+                       return d0*d1;
+               }
+       };
+       public static final Function PowerEval = new TwoOperandNumericOperation() {
+               protected double evaluate(double d0, double d1) {
+                       return Math.pow(d0, d1);
+               }
+       };
+       private static final class SubtractEvalClass extends TwoOperandNumericOperation {
+               public SubtractEvalClass() {
+                       //
+               }
+               protected double evaluate(double d0, double d1) {
+                       return d0-d1;
+               }
+       }
+       public static final Function SubtractEval = new SubtractEvalClass();
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/UnaryMinusEval.java b/src/java/org/apache/poi/ss/formula/eval/UnaryMinusEval.java
new file mode 100644 (file)
index 0000000..02d6d5e
--- /dev/null
@@ -0,0 +1,47 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class UnaryMinusEval extends Fixed1ArgFunction {
+
+       public static final Function instance = new UnaryMinusEval();
+
+       private UnaryMinusEval() {
+               // enforce singleton
+       }
+
+       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+               double d;
+               try {
+                       ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+                       d = OperandResolver.coerceValueToDouble(ve);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               if (d == 0.0) { // this '==' matches +0.0 and -0.0
+                       return NumberEval.ZERO;
+               }
+               return new NumberEval(-d);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/UnaryPlusEval.java b/src/java/org/apache/poi/ss/formula/eval/UnaryPlusEval.java
new file mode 100644 (file)
index 0000000..9b10f2b
--- /dev/null
@@ -0,0 +1,51 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class UnaryPlusEval extends Fixed1ArgFunction {
+
+       public static final Function instance = new UnaryPlusEval();
+
+       private UnaryPlusEval() {
+               // enforce singleton
+       }
+
+       public ValueEval evaluate(int srcCellRow, int srcCellCol, ValueEval arg0) {
+               double d;
+               try {
+                       ValueEval ve = OperandResolver.getSingleValue(arg0, srcCellRow, srcCellCol);
+                       if(ve instanceof StringEval) {
+                               // Note - asymmetric with UnaryMinus
+                               // -"hello" evaluates to #VALUE!
+                               // but +"hello" evaluates to "hello"
+                               return ve;
+                       }
+                       d = OperandResolver.coerceValueToDouble(ve);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               return new NumberEval(+d);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/eval/ValueEval.java b/src/java/org/apache/poi/ss/formula/eval/ValueEval.java
new file mode 100644 (file)
index 0000000..c1309a7
--- /dev/null
@@ -0,0 +1,25 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public interface ValueEval {
+       // no methods
+}
index 8dd298193c7417fbce712d52ba6c34045b484be0..8e8690257074ba49c91e3e742cb94a0176f45266 100644 (file)
 
 package org.apache.poi.ss.formula.eval.forked;
 
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
-import org.apache.poi.hssf.record.formula.eval.BoolEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
 import org.apache.poi.ss.formula.EvaluationCell;
 import org.apache.poi.ss.formula.EvaluationSheet;
 import org.apache.poi.ss.usermodel.Cell;
@@ -60,27 +59,27 @@ final class ForkedEvaluationCell implements EvaluationCell {
                Class<? extends ValueEval> cls = value.getClass();
 
                if (cls == NumberEval.class) {
-                       _cellType = HSSFCell.CELL_TYPE_NUMERIC;
+                       _cellType = Cell.CELL_TYPE_NUMERIC;
                        _numberValue = ((NumberEval)value).getNumberValue();
                        return;
                }
                if (cls == StringEval.class) {
-                       _cellType = HSSFCell.CELL_TYPE_STRING;
+                       _cellType = Cell.CELL_TYPE_STRING;
                        _stringValue = ((StringEval)value).getStringValue();
                        return;
                }
                if (cls == BoolEval.class) {
-                       _cellType = HSSFCell.CELL_TYPE_BOOLEAN;
+                       _cellType = Cell.CELL_TYPE_BOOLEAN;
                        _booleanValue = ((BoolEval)value).getBooleanValue();
                        return;
                }
                if (cls == ErrorEval.class) {
-                       _cellType = HSSFCell.CELL_TYPE_ERROR;
+                       _cellType = Cell.CELL_TYPE_ERROR;
                        _errorValue = ((ErrorEval)value).getErrorCode();
                        return;
                }
                if (cls == BlankEval.class) {
-                       _cellType = HSSFCell.CELL_TYPE_BLANK;
+                       _cellType = Cell.CELL_TYPE_BLANK;
                        return;
                }
                throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")");
@@ -105,19 +104,19 @@ final class ForkedEvaluationCell implements EvaluationCell {
                return _cellType;
        }
        public boolean getBooleanCellValue() {
-               checkCellType(HSSFCell.CELL_TYPE_BOOLEAN);
+               checkCellType(Cell.CELL_TYPE_BOOLEAN);
                return _booleanValue;
        }
        public int getErrorCellValue() {
-               checkCellType(HSSFCell.CELL_TYPE_ERROR);
+               checkCellType(Cell.CELL_TYPE_ERROR);
                return _errorValue;
        }
        public double getNumericCellValue() {
-               checkCellType(HSSFCell.CELL_TYPE_NUMERIC);
+               checkCellType(Cell.CELL_TYPE_NUMERIC);
                return _numberValue;
        }
        public String getStringCellValue() {
-               checkCellType(HSSFCell.CELL_TYPE_STRING);
+               checkCellType(Cell.CELL_TYPE_STRING);
                return _stringValue;
        }
        public EvaluationSheet getSheet() {
index c916006764c70ad851c9773e9a43c744b2baaba5..8742b9214368e1b17afa2e701849d03f86f5fbde 100644 (file)
@@ -23,7 +23,6 @@ import java.util.Map;
 import org.apache.poi.hssf.record.formula.NamePtg;
 import org.apache.poi.hssf.record.formula.NameXPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
 import org.apache.poi.ss.formula.EvaluationCell;
 import org.apache.poi.ss.formula.EvaluationName;
 import org.apache.poi.ss.formula.EvaluationSheet;
index 258a5166fcbf4cd3f4fa6fb0a57a1534ca4c24b7..21fa5763840fb8870455f66adbe250bad4dcf8d2 100644 (file)
 
 package org.apache.poi.ss.formula.eval.forked;
 
-import org.apache.poi.hssf.record.formula.eval.BoolEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.record.formula.udf.UDFFinder;
-import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.udf.UDFFinder;
 import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
@@ -31,6 +30,7 @@ import org.apache.poi.ss.formula.EvaluationCell;
 import org.apache.poi.ss.formula.EvaluationWorkbook;
 import org.apache.poi.ss.formula.IStabilityClassifier;
 import org.apache.poi.ss.formula.WorkbookEvaluator;
+import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.Workbook;
 
 /**
@@ -113,17 +113,17 @@ public final class ForkedEvaluator {
                EvaluationCell cell = _sewb.getEvaluationCell(sheetName, rowIndex, columnIndex);
 
                switch (cell.getCellType()) {
-                       case HSSFCell.CELL_TYPE_BOOLEAN:
+                       case Cell.CELL_TYPE_BOOLEAN:
                                return BoolEval.valueOf(cell.getBooleanCellValue());
-                       case HSSFCell.CELL_TYPE_ERROR:
+                       case Cell.CELL_TYPE_ERROR:
                                return ErrorEval.valueOf(cell.getErrorCellValue());
-                       case HSSFCell.CELL_TYPE_FORMULA:
+                       case Cell.CELL_TYPE_FORMULA:
                                return _evaluator.evaluate(cell);
-                       case HSSFCell.CELL_TYPE_NUMERIC:
+                       case Cell.CELL_TYPE_NUMERIC:
                                return new NumberEval(cell.getNumericCellValue());
-                       case HSSFCell.CELL_TYPE_STRING:
+                       case Cell.CELL_TYPE_STRING:
                                return new StringEval(cell.getStringCellValue());
-                       case HSSFCell.CELL_TYPE_BLANK:
+                       case Cell.CELL_TYPE_BLANK:
                                return null;
                }
                throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
index 664f100562e2d5ba2a7caf81b58b5b9a023ebb2d..cfe69ac7f28085412b7aa322b4ef0a98309e02dd 100644 (file)
@@ -17,8 +17,7 @@
 
 package org.apache.poi.ss.usermodel;
 
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.formula.eval.ErrorEval;
 
 /**
  * Mimics the 'data view' of a cell. This allows formula evaluator
index 94f16e9840536697981a8c31ce59900625c6236f..ea7fad9f3df61e28629bfb11ff343d3b092fee7e 100644 (file)
Binary files a/test-data/spreadsheet/LookupFunctionsTestCaseData.xls and b/test-data/spreadsheet/LookupFunctionsTestCaseData.xls differ