]> source.dussan.org Git - poi.git/commitdiff
Implement CountA, CountIf, Index, Rows and Columns functions. Patch from Josh Micich...
authorNick Burch <nick@apache.org>
Mon, 4 Feb 2008 10:48:29 +0000 (10:48 +0000)
committerNick Burch <nick@apache.org>
Mon, 4 Feb 2008 10:48:29 +0000 (10:48 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@618230 13f79535-47bb-0310-9956-ffa450edef68

13 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Countif.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java [new file with mode: 0755]

index 169112f7b3ca75bc3497b2e937fc9f17760da750..df0e5e51adb43f1269e256acfa95fec69e241617 100644 (file)
@@ -36,6 +36,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
            <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
            <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
            <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>
index 4f7e153775c6e2d2bec8b0af37c5dbb170bf0150..9a7a3f43cba804cbdc9286518442ad90b77920ad 100644 (file)
@@ -33,6 +33,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
            <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
            <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
            <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>
index e25fad66e40ad0bac0f29dadf781aa7bd3bd458c..b75864e72d80a610166863421f1b3a69d0c3e1c2 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
+
 package org.apache.poi.hssf.record.formula.functions;
 
-public class Columns extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for Excel COLUMNS function.
+ * 
+ * @author Josh Micich
+ */
+public final class Columns implements Function {
 
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               switch(args.length) {
+                       case 1:
+                               // expected
+                               break;
+                       case 0:
+                               // too few arguments
+                               return ErrorEval.VALUE_INVALID;
+                       default:
+                               // too many arguments
+                               return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               
+               int result;
+        if (firstArg instanceof AreaEval) {
+            AreaEval ae = (AreaEval) firstArg;
+            result = ae.getLastColumn() - ae.getFirstColumn() + 1;
+        } else if (firstArg instanceof RefEval) {
+            result = 1;
+        } else { // anything else is not valid argument
+            return ErrorEval.VALUE_INVALID;
+        }
+        return new NumberEval(result);
+       }
 }
index 6c6b3057425810ae0303cc9e5cca027d58b8ffad..9061e77e5da9cc5b13044bbb3986f666817846dc 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
+
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Counts the number of cells that contain data within the list of arguments. 
  *
+ * Excel Syntax
+ * COUNTA(value1,value2,...)
+ * Value1, value2, ...   are 1 to 30 arguments representing the values or ranges to be counted.
+ * 
+ * @author Josh Micich
  */
-package org.apache.poi.hssf.record.formula.functions;
+public final class Counta implements Function {
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               int nArgs = args.length;
+               if (nArgs < 1) {
+                       // too few arguments
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               if (nArgs > 30) {
+                       // too many arguments
+                       return ErrorEval.VALUE_INVALID;
+               }
+               
+               int temp = 0;
+               // Note - observed behavior of Excel:
+               // Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error
+               // in fact, they seem to get counted
+               
+               for(int i=0; i<nArgs; i++) {
+                       temp += countArg(args[i]);
+                       
+               }
+               return new NumberEval(temp);
+       }
+
+       private static int countArg(Eval eval) {
+        if (eval instanceof AreaEval) {
+            AreaEval ae = (AreaEval) eval;
+            return countAreaEval(ae);
+        }
+        if (eval instanceof RefEval) {
+            RefEval refEval = (RefEval)eval;
+                       return countValue(refEval.getInnerValueEval());
+        }
+        if (eval instanceof NumberEval) {
+            return 1;
+        }
+        if (eval instanceof StringEval) {
+            return 1;
+        }
+        
+               
+               throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+       }
+
+       private static int countAreaEval(AreaEval ae) {
+               
+               int temp = 0;
+               ValueEval[] values = ae.getValues();
+               for (int i = 0; i < values.length; i++) {
+                       ValueEval val = values[i];
+                       if(val == null) {
+                               // seems to occur.  Really we would have expected BlankEval
+                               continue;
+                       }
+                       temp += countValue(val);
+                       
+               }
+               return temp;
+       }
+
+       private static int countValue(ValueEval valueEval) {
+               
+               if(valueEval == BlankEval.INSTANCE) {
+                       return 0;
+               }
+               
+               if(valueEval instanceof BlankEval) {
+                       // wouldn't need this if BlankEval was final
+                       return 0;
+               }
 
-public class Counta extends NotImplementedFunction {
+               if(valueEval instanceof ErrorEval) {
+                       // note - error values are counted
+                       return 1;
+               }
+               // also empty strings and zeros are counted too
 
+               return 1;
+       }
 }
index f97b8a59abca08d020c20ba3aa79c27baffab6b9..2e445a8bf6bb45ef0d27a620db603886fc179696 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
+
 package org.apache.poi.hssf.record.formula.functions;
 
-public class Countif extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+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.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Implementation for the function COUNTIF<p/>
+ * 
+ * Syntax: COUNTIF ( range, criteria )
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ *      <tr><th>range&nbsp;&nbsp;&nbsp;</th><td>is the range of cells to be counted based on the criteria</td></tr>
+ *      <tr><th>criteria</th><td>is used to determine which cells to count</td></tr>
+ *    </table>
+ * <p/>
+ * 
+ * @author Josh Micich
+ */
+public final class Countif implements Function {
+       
+       /**
+        * Common interface for the matching criteria.
+        */
+       private interface I_MatchPredicate {
+               boolean matches(Eval x);
+       }
+       
+       private static final class NumberMatcher implements I_MatchPredicate {
+
+               private final double _value;
+
+               public NumberMatcher(double value) {
+                       _value = value;
+               }
+
+               public boolean matches(Eval x) {
+                       if(x instanceof StringEval) {
+                               // if the target(x) is a string, but parses as a number
+                               // it may still count as a match
+                               StringEval se = (StringEval)x;
+                               Double val = parseDouble(se.getStringValue());
+                               if(val == null) {
+                                       // x is text that is not a number
+                                       return false;
+                               }
+                               return val.doubleValue() == _value;
+                       }
+                       if(!(x instanceof NumberEval)) {
+                               return false;
+                       }
+                       NumberEval ne = (NumberEval) x;
+                       return ne.getNumberValue() == _value;
+               }
+       }
+       private static final class BooleanMatcher implements I_MatchPredicate {
+
+               private final boolean _value;
+
+               public BooleanMatcher(boolean value) {
+                       _value = value;
+               }
+
+               public boolean matches(Eval x) {
+                       if(x instanceof StringEval) {
+                               StringEval se = (StringEval)x;
+                               Boolean val = parseBoolean(se.getStringValue());
+                               if(val == null) {
+                                       // x is text that is not a boolean
+                                       return false;
+                               }
+                               if (true) { // change to false to observe more intuitive behaviour
+                                       // Note - Unlike with numbers, it seems that COUNTA never matches 
+                                       // boolean values when the target(x) is a string
+                                       return false;
+                               }
+                               return val.booleanValue() == _value;
+                       }
+                       if(!(x instanceof BoolEval)) {
+                               return false;
+                       }
+                       BoolEval be = (BoolEval) x;
+                       return be.getBooleanValue() == _value;
+               }
+       }
+       private static final class StringMatcher implements I_MatchPredicate {
+
+               private final String _value;
+
+               public StringMatcher(String value) {
+                       _value = value;
+               }
+
+               public boolean matches(Eval x) {
+                       if(!(x instanceof StringEval)) {
+                               return false;
+                       }
+                       StringEval se = (StringEval) x;
+                       return se.getStringValue() == _value;
+               }
+       }
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               switch(args.length) {
+                       case 2:
+                               // expected
+                               break;
+                       default:
+                               // TODO - it doesn't seem to be possible to enter COUNTIF() into Excel with the wrong arg count
+                               // perhaps this should be an exception
+                               return ErrorEval.VALUE_INVALID;
+               }
+               
+               AreaEval range = (AreaEval) args[0];
+               Eval criteriaArg = args[1];
+               if(criteriaArg instanceof RefEval) {
+                       // criteria is not a literal value, but a cell reference
+                       // for example COUNTIF(B2:D4, E1)
+                       RefEval re = (RefEval)criteriaArg;
+                       criteriaArg = re.getInnerValueEval();
+               } else {
+                       // other non literal tokens such as function calls, have been fully evaluated
+                       // for example COUNTIF(B2:D4, COLUMN(E1))
+               }
+               I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
+               return countMatchingCellsInArea(range, mp);
+       }
+       /**
+        * @return the number of evaluated cells in the range that match the specified criteria
+        */
+       private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) {
+               ValueEval[] values = range.getValues();
+               int result = 0;
+               for (int i = 0; i < values.length; i++) {
+                       if(criteriaPredicate.matches(values[i])) {
+                               result++;
+                       }
+               }
+               return new NumberEval(result);
+       }
+       
+       private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
+               if(evaluatedCriteriaArg instanceof NumberEval) {
+                       return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue());
+               }
+               if(evaluatedCriteriaArg instanceof BoolEval) {
+                       return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue());
+               }
+               
+               if(evaluatedCriteriaArg instanceof StringEval) {
+                       return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
+               }
+               throw new RuntimeException("Unexpected type for criteria (" 
+                               + evaluatedCriteriaArg.getClass().getName() + ")");
+       }
+
+       /**
+        * When the second argument is a string, many things are possible
+        */
+       private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
+               String value = stringEval.getStringValue();
+               char firstChar = value.charAt(0);
+               Boolean booleanVal = parseBoolean(value);
+               if(booleanVal != null) {
+                       return new BooleanMatcher(booleanVal.booleanValue());
+               }
+               
+               Double doubleVal = parseDouble(value);
+               if(doubleVal != null) {
+                       return new NumberMatcher(doubleVal.doubleValue());
+               }
+               switch(firstChar) {
+                       case '>':
+                       case '<':
+                       case '=':
+                               throw new RuntimeException("Incomplete code - criteria expressions such as '"
+                                               + value + "' not supported yet");
+               }
+               
+               //else - just a plain string with no interpretation.
+               return new StringMatcher(value);
+       }
 
+       /**
+        * Under certain circumstances COUNTA will equate a plain number with a string representation of that number
+        */
+       /* package */ static Double parseDouble(String strRep) {
+               if(!Character.isDigit(strRep.charAt(0))) {
+                       // avoid using NumberFormatException to tell when string is not a number
+                       return null;
+               }
+               // TODO - support notation like '1E3' (==1000)
+               
+               double val;
+               try {
+                       val = Double.parseDouble(strRep);
+               } catch (NumberFormatException e) {
+                       return null;
+               }
+               return new Double(val);
+       }
+       /**
+        * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers. 
+        */
+       /* package */ static Boolean parseBoolean(String strRep) {
+               switch(strRep.charAt(0)) {
+                       case 't':
+                       case 'T':
+                               if("TRUE".equalsIgnoreCase(strRep)) {
+                                       return Boolean.TRUE;
+                               }
+                               break;
+                       case 'f':
+                       case 'F':
+                               if("FALSE".equalsIgnoreCase(strRep)) {
+                                       return Boolean.FALSE;
+                               }
+                               break;
+               }
+               return null;
+       }
 }
index db798ee0faefa19c9f2fb3ed977a63816a61b66c..aebf6aab0d4843ab3b0b88f7aa1b2238e163123a 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
+
 package org.apache.poi.hssf.record.formula.functions;
 
-public class Index extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for the Excel function INDEX<p/>
+ * 
+ * Syntax : <br/>
+ *  INDEX ( reference, row_num[, column_num [, area_num]])</br>
+ *  INDEX ( array, row_num[, column_num])
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ *      <tr><th>reference</th><td>typically an area reference, possibly a union of areas</td></tr>
+ *      <tr><th>array</th><td>a literal array value (currently not supported)</td></tr>
+ *      <tr><th>row_num</th><td>selects the row within the array or area reference</td></tr>
+ *      <tr><th>column_num</th><td>selects column within the array or area reference. default is 1</td></tr>
+ *      <tr><th>area_num</th><td>used when reference is a union of areas</td></tr>
+ *    </table>
+ * <p/>
+ * 
+ * @author Josh Micich
+ */
+public final class Index implements Function {
 
+       // TODO - javadoc for interface method
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               int nArgs = args.length;
+               if(nArgs < 2) {
+                       // too few arguments
+                       return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               if(firstArg instanceof AreaEval) {
+                       AreaEval reference = (AreaEval) firstArg;
+                       
+                       int rowIx = 0;
+                       int columnIx = 0;
+                       int areaIx = 0;
+                       switch(nArgs) {
+                               case 4:
+                                       areaIx = convertIndexArgToZeroBase(args[3]);
+                                       throw new RuntimeException("Incomplete code" +
+                                                       " - don't know how to support the 'area_num' parameter yet)");
+                                       // Excel expression might look like this "INDEX( (A1:B4, C3:D6, D2:E5 ), 1, 2, 3)
+                                       // In this example, the 3rd area would be used i.e. D2:E5, and the overall result would be E2
+                                       // Token array might be encoded like this: MemAreaPtg, AreaPtg, AreaPtg, UnionPtg, UnionPtg, ParenthesesPtg
+                                       // The formula parser doesn't seem to support this yet. Not sure if the evaluator does either
+                                       
+                               case 3:
+                                       columnIx = convertIndexArgToZeroBase(args[2]);
+                               case 2:
+                                       rowIx = convertIndexArgToZeroBase(args[1]);
+                                       break;
+                               default:
+                                       // too many arguments
+                                       return ErrorEval.VALUE_INVALID;
+                       }
+                       
+               int nColumns = reference.getLastColumn()-reference.getFirstColumn()+1;
+                       int index = rowIx * nColumns + columnIx;
+                       
+                       return reference.getValues()[index];
+               }
+               
+               // else the other variation of this function takes an array as the first argument
+               // it seems like interface 'ArrayEval' does not even exist yet
+               
+               throw new RuntimeException("Incomplete code - cannot handle first arg of type ("
+                               + firstArg.getClass().getName() + ")");
+       }
+       
+       /**
+        * takes a NumberEval representing a 1-based index and returns the zero-based int value
+        */
+       private static int convertIndexArgToZeroBase(Eval ev) {
+               NumberEval ne;
+               if(ev instanceof RefEval) {
+                       // TODO - write junit to justify this
+                       RefEval re = (RefEval) ev;
+                       ne = (NumberEval) re.getInnerValueEval();
+               } else {
+                       ne = (NumberEval)ev;
+               }
+               
+               return (int)ne.getNumberValue() - 1;
+       }
 }
index 47e4dc8702d9bc824054fc707450c5b56f7828aa..6a4eb8edb7b45f46149c1c805e38a3641dc01bdc 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
+
 package org.apache.poi.hssf.record.formula.functions;
 
-public class Rows extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for Excel COLUMNS function.
+ * 
+ * @author Josh Micich
+ */
+public final class Rows implements Function {
 
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               switch(args.length) {
+                       case 1:
+                               // expected
+                               break;
+                       case 0:
+                               // too few arguments
+                               return ErrorEval.VALUE_INVALID;
+                       default:
+                               // too many arguments
+                               return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               
+               int result;
+        if (firstArg instanceof AreaEval) {
+            AreaEval ae = (AreaEval) firstArg;
+            result = ae.getLastRow() - ae.getFirstRow() + 1;
+        } else if (firstArg instanceof RefEval) {
+            result = 1;
+        } else { // anything else is not valid argument
+            return ErrorEval.VALUE_INVALID;
+        }
+        return new NumberEval(result);
+       }
 }
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
new file mode 100755 (executable)
index 0000000..b5e0843
--- /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.hssf.record.formula.functions;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Direct tests for all implementors of <code>Function</code>.
+ * 
+ * @author Josh Micich
+ */
+public final class AllIndividualFunctionEvaluationTests {
+
+       // TODO - have this suite incorporated into a higher level one
+       public static Test suite() {
+               TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions");
+               result.addTestSuite(TestCountFuncs.class);
+               result.addTestSuite(TestDate.class);
+               result.addTestSuite(TestFinanceLib.class);
+               result.addTestSuite(TestIndex.class);
+               result.addTestSuite(TestMathX.class);
+               result.addTestSuite(TestRowCol.class);
+               result.addTestSuite(TestStatsLib.class);
+               return result;
+       }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
new file mode 100755 (executable)
index 0000000..958c486
--- /dev/null
@@ -0,0 +1,63 @@
+/* ====================================================================
+   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.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Test helper class for creating mock <code>Eval</code> objects
+ * 
+ * @author Josh Micich
+ */
+final class EvalFactory {
+       private static final NumberEval ZERO = new NumberEval(0);
+
+       private EvalFactory() {
+               // no instances of this class
+       }
+
+       /**
+        * Creates a dummy AreaEval (filled with zeros)
+        * <p/>
+        * nCols and nRows could have been derived
+        */
+       public static AreaEval createAreaEval(String areaRefStr, int nCols, int nRows) {
+               int nValues = nCols * nRows;
+               ValueEval[] values = new ValueEval[nValues];
+               for (int i = 0; i < nValues; i++) {
+                       values[i] = ZERO;
+               }
+               
+               return new Area2DEval(new AreaPtg(areaRefStr), values);
+       }
+
+       /**
+        * Creates a single RefEval (with value zero)
+        */
+       public static RefEval createRefEval(String refStr) {
+               return new Ref2DEval(new ReferencePtg(refStr), ZERO, true);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
new file mode 100755 (executable)
index 0000000..87405a4
--- /dev/null
@@ -0,0 +1,101 @@
+/* ====================================================================
+   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.hssf.record.formula.functions;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+
+/**
+ * Test helper class for invoking functions with numeric results.
+ * 
+ * @author Josh Micich
+ */
+final class NumericFunctionInvoker {
+
+       private NumericFunctionInvoker() {
+               // no instances of this class
+       }
+       
+       private static final class NumericEvalEx extends Exception {
+               public NumericEvalEx(String msg) {
+                       super(msg);
+               }
+       }
+       
+       /**
+        * Invokes the specified function with the arguments.
+        * <p/>
+        * Assumes that the cell coordinate parameters of
+        *  <code>Function.evaluate(args, srcCellRow, srcCellCol)</code>
+        * are not required.
+        * <p/>
+        * This method cannot be used for confirming error return codes.  Any non-numeric evaluation
+        * result causes the current junit test to fail.
+        */
+       public static double invoke(Function f, Eval[] args) {
+               try {
+                       return invokeInternal(f, args, -1, -1);
+               } catch (NumericEvalEx e) {
+                       throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName() 
+                                       + ") failed: " + e.getMessage());
+               }
+               
+       }
+       /**
+        * Formats nicer error messages for the junit output
+        */
+       private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
+                               throws NumericEvalEx {
+               Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
+               if(evalResult == null) {
+                       throw new NumericEvalEx("Result object was null");
+               }
+               if(evalResult instanceof ErrorEval) {
+                       ErrorEval ee = (ErrorEval) evalResult;
+                       throw new NumericEvalEx(formatErrorMessage(ee));
+               }
+               if(!(evalResult instanceof NumericValueEval)) {
+                       throw new NumericEvalEx("Result object type (" + evalResult.getClass().getName()
+                                       + ") is invalid.  Expected implementor of (" 
+                                       + NumericValueEval.class.getName() + ")");
+               }
+               
+               NumericValueEval result = (NumericValueEval) evalResult;
+               return result.getNumberValue();
+       }
+       private static String formatErrorMessage(ErrorEval ee) {
+               if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) {
+                       return "Function not implemented";
+               }
+               if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
+                       return "Unknown error";
+               }
+               return "Error code=" + ee.getErrorCode();
+       }
+       private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {
+               if(a==b) {
+                       return true;
+               }
+               return a.getErrorCode() == b.getErrorCode();
+       }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
new file mode 100755 (executable)
index 0000000..fbaace9
--- /dev/null
@@ -0,0 +1,150 @@
+/* ====================================================================
+   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.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+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.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
+ * 
+ * @author Josh Micich
+ */
+public final class TestCountFuncs extends TestCase {
+
+       public TestCountFuncs(String testName) {
+               super(testName);
+       }
+       
+       public void testCountA() {
+               
+               Eval[] args;
+               
+               args = new Eval[] {
+                       new NumberEval(0),       
+               };
+               confirmCountA(1, args);
+               
+               args = new Eval[] {
+                       new NumberEval(0),      
+                       new NumberEval(0),
+                       new StringEval(""),
+               };
+               confirmCountA(3, args);
+               
+               args = new Eval[] {
+                       EvalFactory.createAreaEval("D2:F5", 3, 4),      
+               };
+               confirmCountA(12, args);
+               
+               args = new Eval[] {
+                       EvalFactory.createAreaEval("D1:F5", 3, 5),      // 15
+                       EvalFactory.createRefEval("A1"),        
+                       EvalFactory.createAreaEval("A1:F6", 7, 6),      // 42
+                       new NumberEval(0),
+               };
+               confirmCountA(59, args);
+       }
+
+       public void testCountIf() {
+               
+               AreaEval range;
+               ValueEval[] values;
+               
+               // when criteria is a boolean value
+               values = new ValueEval[] {
+                               new NumberEval(0),      
+                               new StringEval("TRUE"), // note - does not match boolean TRUE
+                               BoolEval.TRUE,
+                               BoolEval.FALSE,
+                               BoolEval.TRUE,
+                               BlankEval.INSTANCE,
+               };
+               range = createAreaEval("A1:B2", values);
+               confirmCountIf(2, range, BoolEval.TRUE);
+               
+               // when criteria is numeric
+               values = new ValueEval[] {
+                               new NumberEval(0),      
+                               new StringEval("2"),    
+                               new StringEval("2.001"),        
+                               new NumberEval(2),      
+                               new NumberEval(2),      
+                               BoolEval.TRUE,
+                               BlankEval.INSTANCE,
+               };
+               range = createAreaEval("A1:B2", values);
+               confirmCountIf(3, range, new NumberEval(2));
+               // note - same results when criteria is a string that parses as the number with the same value
+               confirmCountIf(3, range, new StringEval("2.00"));
+               
+               if (false) { // not supported yet: 
+                       // when criteria is an expression (starting with a comparison operator)
+                       confirmCountIf(4, range, new StringEval(">1"));
+               }
+       }
+       /**
+        * special case where the criteria argument is a cell reference
+        */
+       public void testCountIfWithCriteriaReference() {
+
+               ValueEval[] values = { 
+                               new NumberEval(22),
+                               new NumberEval(25),
+                               new NumberEval(21),
+                               new NumberEval(25),
+                               new NumberEval(25),
+                               new NumberEval(25),
+               };
+               Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values);
+               
+               Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true);
+               Eval[] args=  { arg0, criteriaArg, };
+               
+               double actual = NumericFunctionInvoker.invoke(new Countif(), args);
+               assertEquals(4, actual, 0D);
+       }
+       
+
+       private static AreaEval createAreaEval(String areaRefStr, ValueEval[] values) {
+               return new Area2DEval(new AreaPtg(areaRefStr), values);
+       }
+
+       private static void confirmCountA(int expected, Eval[] args) {
+               double result = NumericFunctionInvoker.invoke(new Counta(), args);
+               assertEquals(expected, result, 0);
+       }
+       private static void confirmCountIf(int expected, AreaEval range, Eval criteria) {
+               
+               Eval[] args = { range, criteria, };
+               double result = NumericFunctionInvoker.invoke(new Countif(), args);
+               assertEquals(expected, result, 0);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java
new file mode 100755 (executable)
index 0000000..902c412
--- /dev/null
@@ -0,0 +1,89 @@
+/* ====================================================================
+   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.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Tests for the INDEX() function
+ * 
+ * @author Josh Micich
+ */
+public final class TestIndex extends TestCase {
+
+       public TestIndex(String testName) {
+               super(testName);
+       }
+       
+       private static final double[] TEST_VALUES0 = {
+                       1, 2,
+                       3, 4,
+                       5, 6,
+                       7, 8,
+                       9, 10,
+                       11, 12,
+                       13, // excess array element. TODO - Area2DEval currently has no validation to ensure correct size of values array
+       };
+       
+       /**
+        * For the case when the first argument to INDEX() is an area reference
+        */
+       public void testEvaluateAreaReference() {
+               
+               double[] values = TEST_VALUES0;
+               confirmAreaEval("C1:D6", values, 4, 1, 7);
+               confirmAreaEval("C1:D6", values, 6, 2, 12);
+               confirmAreaEval("C1:D6", values, 3, -1, 5);
+               
+               // now treat same data as 3 columns, 4 rows
+               confirmAreaEval("C10:E13", values, 2, 2, 5); 
+               confirmAreaEval("C10:E13", values, 4, -1, 10);
+       }
+       
+       /**
+        * @param areaRefString in Excel notation e.g. 'D2:E97'
+        * @param dValues array of evaluated values for the area reference
+        * @param rowNum 1-based
+        * @param colNum 1-based, pass -1 to signify argument not present
+        */
+       private static void confirmAreaEval(String areaRefString, double[] dValues, 
+                       int rowNum, int colNum, double expectedResult) {
+               ValueEval[] values = new ValueEval[dValues.length];
+               for (int i = 0; i < values.length; i++) {
+                       values[i] = new NumberEval(dValues[i]);
+               }
+               Area2DEval arg0 = new Area2DEval(new AreaPtg(areaRefString), values);
+               
+               Eval[] args;
+               if (colNum > 0) {
+                       args = new Eval[] { arg0, new NumberEval(rowNum), new NumberEval(colNum), };
+               } else {
+                       args = new Eval[] { arg0, new NumberEval(rowNum), };
+               }
+               
+               double actual = NumericFunctionInvoker.invoke(new Index(), args);
+               assertEquals(expectedResult, actual, 0D);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java
new file mode 100755 (executable)
index 0000000..4002c30
--- /dev/null
@@ -0,0 +1,102 @@
+/* ====================================================================
+   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.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.Eval;
+
+/**
+ * Tests for ROW(), ROWS(), COLUMN(), COLUMNS()
+ * 
+ * @author Josh Micich
+ */
+public final class TestRowCol extends TestCase {
+
+       public TestRowCol(String testName) {
+               super(testName);
+       }
+       
+       public void testCol() {
+               Function target = new Column();
+               {
+                       Eval[] args = { EvalFactory.createRefEval("C5"), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(3, actual, 0D);
+               }
+               {
+                       Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(5, actual, 0D);
+               }
+       }
+       
+       public void testRow() {
+               Function target = new Row();
+               {
+                       Eval[] args = { EvalFactory.createRefEval("C5"), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(5, actual, 0D);
+               }
+               {
+                       Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(2, actual, 0D);
+               }
+       }
+       
+       public void testColumns() {
+               
+               confirmColumnsFunc("A1:F1", 6, 1);
+               confirmColumnsFunc("A1:C2", 3, 2);
+               confirmColumnsFunc("A1:B3", 2, 3);
+               confirmColumnsFunc("A1:A6", 1, 6);
+               
+               Eval[] args = { EvalFactory.createRefEval("C5"), };
+               double actual = NumericFunctionInvoker.invoke(new Columns(), args);
+               assertEquals(1, actual, 0D);
+       }
+       
+       public void testRows() {
+               
+               confirmRowsFunc("A1:F1", 6, 1);
+               confirmRowsFunc("A1:C2", 3, 2);
+               confirmRowsFunc("A1:B3", 2, 3);
+               confirmRowsFunc("A1:A6", 1, 6);
+               
+               Eval[] args = { EvalFactory.createRefEval("C5"), };
+               double actual = NumericFunctionInvoker.invoke(new Rows(), args);
+               assertEquals(1, actual, 0D);
+       }
+       
+       private static void confirmRowsFunc(String areaRefStr, int nCols, int nRows) {
+               Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), };
+
+               double actual = NumericFunctionInvoker.invoke(new Rows(), args);
+               assertEquals(nRows, actual, 0D);
+       }
+       
+
+       private static void confirmColumnsFunc(String areaRefStr, int nCols, int nRows) {
+               Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), };
+
+               double actual = NumericFunctionInvoker.invoke(new Columns(), args);
+               assertEquals(nCols, actual, 0D);
+       }
+}