]> source.dussan.org Git - poi.git/commitdiff
Changes to formula evaluation allowing for reduced memory usage
authorJosh Micich <josh@apache.org>
Thu, 13 Nov 2008 20:22:17 +0000 (20:22 +0000)
committerJosh Micich <josh@apache.org>
Thu, 13 Nov 2008 20:22:17 +0000 (20:22 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@713811 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java
src/java/org/apache/poi/ss/formula/FormulaUsedBlankCellSet.java
src/java/org/apache/poi/ss/formula/IStabilityClassifier.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java
src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java
src/testcases/org/apache/poi/ss/formula/WorkbookEvaluatorTestHelper.java

index a0e36343a71ee73d33b5259bae364f20232cd56c..c1740afad459b6e318f7999d6c11dbfac12f268f 100644 (file)
@@ -37,6 +37,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.5-beta4" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">Changes to formula evaluation allowing for reduced memory usage</action>
            <action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
            <action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
            <action dev="POI-DEVELOPERS" type="add">Include the sheet number in the output of XLS2CSVmra</action>
index 4d2b1a99741e9f013590545b167098ce54b27e12..57fec14dfaea8e7189c8f1c58b348324c78efe79 100644 (file)
@@ -34,6 +34,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.5-beta4" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">Changes to formula evaluation allowing for reduced memory usage</action>
            <action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
            <action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
            <action dev="POI-DEVELOPERS" type="add">Include the sheet number in the output of XLS2CSVmra</action>
index 845284c45622cc571c42c890ff1f1f1275eddbc6..1f65588caa4bd625765e46fd7690ccc551d1bfb9 100644 (file)
@@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.StringEval;\r
 import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
 import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;\r
+import org.apache.poi.ss.formula.IStabilityClassifier;\r
 import org.apache.poi.ss.formula.WorkbookEvaluator;\r
 import org.apache.poi.ss.usermodel.Cell;\r
 import org.apache.poi.ss.usermodel.CellValue;\r
@@ -42,266 +43,273 @@ import org.apache.poi.ss.usermodel.FormulaEvaluator;
  */\r
 public class HSSFFormulaEvaluator implements FormulaEvaluator  {\r
 \r
-    private WorkbookEvaluator _bookEvaluator;\r
+       private WorkbookEvaluator _bookEvaluator;\r
 \r
-    /**\r
-     * @deprecated (Sep 2008) HSSFSheet parameter is ignored\r
-     */\r
-    public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {\r
-        this(workbook);\r
-        if (false) {\r
-            sheet.toString(); // suppress unused parameter compiler warning\r
-        }\r
-    }\r
-    public HSSFFormulaEvaluator(HSSFWorkbook workbook) {\r
-        _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));\r
-    }\r
+       /**\r
+        * @deprecated (Sep 2008) HSSFSheet parameter is ignored\r
+        */\r
+       public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {\r
+               this(workbook);\r
+               if (false) {\r
+                       sheet.toString(); // suppress unused parameter compiler warning\r
+               }\r
+       }\r
+       public HSSFFormulaEvaluator(HSSFWorkbook workbook) {\r
+               this(workbook, null);\r
+       }\r
+       /**\r
+        * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code>\r
+        * for the (conservative) assumption that any cell may have its definition changed after \r
+        * evaluation begins.\r
+        */\r
+       public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) {\r
+               _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier);\r
+       }\r
 \r
-    /**\r
-     * Coordinates several formula evaluators together so that formulas that involve external\r
-     * references can be evaluated.\r
-     * @param workbookNames the simple file names used to identify the workbooks in formulas\r
-     * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1")\r
-     * @param evaluators all evaluators for the full set of workbooks required by the formulas.\r
-     */\r
-    public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {\r
-        WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];\r
-        for (int i = 0; i < wbEvals.length; i++) {\r
-            wbEvals[i] = evaluators[i]._bookEvaluator;\r
-        }\r
-        CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);\r
-    }\r
+       /**\r
+        * Coordinates several formula evaluators together so that formulas that involve external\r
+        * references can be evaluated.\r
+        * @param workbookNames the simple file names used to identify the workbooks in formulas\r
+        * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1")\r
+        * @param evaluators all evaluators for the full set of workbooks required by the formulas.\r
+        */\r
+       public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {\r
+               WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];\r
+               for (int i = 0; i < wbEvals.length; i++) {\r
+                       wbEvals[i] = evaluators[i]._bookEvaluator;\r
+               }\r
+               CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);\r
+       }\r
 \r
-    /**\r
-     * Does nothing\r
-     * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell\r
-     */\r
-    public void setCurrentRow(HSSFRow row) {\r
-        // do nothing\r
-        if (false) {\r
-            row.getClass(); // suppress unused parameter compiler warning\r
-        }\r
-    }\r
+       /**\r
+        * Does nothing\r
+        * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell\r
+        */\r
+       public void setCurrentRow(HSSFRow row) {\r
+               // do nothing\r
+               if (false) {\r
+                       row.getClass(); // suppress unused parameter compiler warning\r
+               }\r
+       }\r
 \r
-    /**\r
-     * Should be called whenever there are major changes (e.g. moving sheets) to input cells\r
-     * in the evaluated workbook.  If performance is not critical, a single call to this method\r
-     * may be used instead of many specific calls to the notify~ methods.\r
-     *\r
-     * Failure to call this method after changing cell values will cause incorrect behaviour\r
-     * of the evaluate~ methods of this class\r
-     */\r
-    public void clearAllCachedResultValues() {\r
-        _bookEvaluator.clearAllCachedResultValues();\r
-    }\r
-    /**\r
-     * Should be called to tell the cell value cache that the specified (value or formula) cell\r
-     * has changed.\r
-     * Failure to call this method after changing cell values will cause incorrect behaviour\r
-     * of the evaluate~ methods of this class\r
-     */\r
-    public void notifyUpdateCell(HSSFCell cell) {\r
-        _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell));\r
-    }\r
-    /**\r
-     * Should be called to tell the cell value cache that the specified cell has just been\r
-     * deleted.\r
-     * Failure to call this method after changing cell values will cause incorrect behaviour\r
-     * of the evaluate~ methods of this class\r
-     */\r
-    public void notifyDeleteCell(HSSFCell cell) {\r
-        _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell));\r
-    }\r
-    public void notifyDeleteCell(Cell cell) {\r
-        _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell));\r
-    }\r
+       /**\r
+        * Should be called whenever there are major changes (e.g. moving sheets) to input cells\r
+        * in the evaluated workbook.  If performance is not critical, a single call to this method\r
+        * may be used instead of many specific calls to the notify~ methods.\r
+        *\r
+        * Failure to call this method after changing cell values will cause incorrect behaviour\r
+        * of the evaluate~ methods of this class\r
+        */\r
+       public void clearAllCachedResultValues() {\r
+               _bookEvaluator.clearAllCachedResultValues();\r
+       }\r
+       /**\r
+        * Should be called to tell the cell value cache that the specified (value or formula) cell\r
+        * has changed.\r
+        * Failure to call this method after changing cell values will cause incorrect behaviour\r
+        * of the evaluate~ methods of this class\r
+        */\r
+       public void notifyUpdateCell(HSSFCell cell) {\r
+               _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell));\r
+       }\r
+       /**\r
+        * Should be called to tell the cell value cache that the specified cell has just been\r
+        * deleted.\r
+        * Failure to call this method after changing cell values will cause incorrect behaviour\r
+        * of the evaluate~ methods of this class\r
+        */\r
+       public void notifyDeleteCell(HSSFCell cell) {\r
+               _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell));\r
+       }\r
+       public void notifyDeleteCell(Cell cell) {\r
+               _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell));\r
+       }\r
 \r
-    /**\r
-     * Should be called to tell the cell value cache that the specified (value or formula) cell\r
-     * has changed.\r
-     * Failure to call this method after changing cell values will cause incorrect behaviour\r
-     * of the evaluate~ methods of this class\r
-     */\r
-    public void notifySetFormula(Cell cell) {\r
-        _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell));\r
-    }\r
+       /**\r
+        * Should be called to tell the cell value cache that the specified (value or formula) cell\r
+        * has changed.\r
+        * Failure to call this method after changing cell values will cause incorrect behaviour\r
+        * of the evaluate~ methods of this class\r
+        */\r
+       public void notifySetFormula(Cell cell) {\r
+               _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell));\r
+       }\r
 \r
-    /**\r
-     * If cell contains a formula, the formula is evaluated and returned,\r
-     * else the CellValue simply copies the appropriate cell value from\r
-     * the cell and also its cell type. This method should be preferred over\r
-     * evaluateInCell() when the call should not modify the contents of the\r
-     * original cell.\r
-     *\r
-     * @param cell may be <code>null</code> signifying that the cell is not present (or blank)\r
-     * @return <code>null</code> if the supplied cell is <code>null</code> or blank\r
-     */\r
-    public CellValue evaluate(Cell cell) {\r
-        if (cell == null) {\r
-            return null;\r
-        }\r
+       /**\r
+        * If cell contains a formula, the formula is evaluated and returned,\r
+        * else the CellValue simply copies the appropriate cell value from\r
+        * the cell and also its cell type. This method should be preferred over\r
+        * evaluateInCell() when the call should not modify the contents of the\r
+        * original cell.\r
+        *\r
+        * @param cell may be <code>null</code> signifying that the cell is not present (or blank)\r
+        * @return <code>null</code> if the supplied cell is <code>null</code> or blank\r
+        */\r
+       public CellValue evaluate(Cell cell) {\r
+               if (cell == null) {\r
+                       return null;\r
+               }\r
 \r
-        switch (cell.getCellType()) {\r
-            case HSSFCell.CELL_TYPE_BOOLEAN:\r
-                return CellValue.valueOf(cell.getBooleanCellValue());\r
-            case HSSFCell.CELL_TYPE_ERROR:\r
-                return CellValue.getError(cell.getErrorCellValue());\r
-            case HSSFCell.CELL_TYPE_FORMULA:\r
-                return evaluateFormulaCellValue(cell);\r
-            case HSSFCell.CELL_TYPE_NUMERIC:\r
-                return new CellValue(cell.getNumericCellValue());\r
-            case HSSFCell.CELL_TYPE_STRING:\r
-                return new CellValue(cell.getRichStringCellValue().getString());\r
-            case HSSFCell.CELL_TYPE_BLANK:\r
-                return null;\r
-        }\r
-        throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");\r
-    }\r
+               switch (cell.getCellType()) {\r
+                       case HSSFCell.CELL_TYPE_BOOLEAN:\r
+                               return CellValue.valueOf(cell.getBooleanCellValue());\r
+                       case HSSFCell.CELL_TYPE_ERROR:\r
+                               return CellValue.getError(cell.getErrorCellValue());\r
+                       case HSSFCell.CELL_TYPE_FORMULA:\r
+                               return evaluateFormulaCellValue(cell);\r
+                       case HSSFCell.CELL_TYPE_NUMERIC:\r
+                               return new CellValue(cell.getNumericCellValue());\r
+                       case HSSFCell.CELL_TYPE_STRING:\r
+                               return new CellValue(cell.getRichStringCellValue().getString());\r
+                       case HSSFCell.CELL_TYPE_BLANK:\r
+                               return null;\r
+               }\r
+               throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");\r
+       }\r
 \r
 \r
-    /**\r
-     * If cell contains formula, it evaluates the formula, and saves the result of the formula. The\r
-     * cell remains as a formula cell. If the cell does not contain formula, this method returns -1\r
-     * and leaves the cell unchanged.\r
-     *\r
-     * Note that the type of the <em>formula result</em> is returned, so you know what kind of\r
-     * cached formula result is also stored with  the formula.\r
-     * <pre>\r
-     * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);\r
-     * </pre>\r
-     * Be aware that your cell will hold both the formula, and the result. If you want the cell\r
-     * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)}\r
-     * @param cell The cell to evaluate\r
-     * @return -1 for non-formula cells, or the type of the <em>formula result</em>\r
-     */\r
-    public int evaluateFormulaCell(Cell cell) {\r
-        if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {\r
-            return -1;\r
-        }\r
-        CellValue cv = evaluateFormulaCellValue(cell);\r
-        // cell remains a formula cell, but the cached value is changed\r
-        setCellValue(cell, cv);\r
-        return cv.getCellType();\r
-    }\r
+       /**\r
+        * If cell contains formula, it evaluates the formula, and saves the result of the formula. The\r
+        * cell remains as a formula cell. If the cell does not contain formula, this method returns -1\r
+        * and leaves the cell unchanged.\r
+        *\r
+        * Note that the type of the <em>formula result</em> is returned, so you know what kind of\r
+        * cached formula result is also stored with  the formula.\r
+        * <pre>\r
+        * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);\r
+        * </pre>\r
+        * Be aware that your cell will hold both the formula, and the result. If you want the cell\r
+        * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)}\r
+        * @param cell The cell to evaluate\r
+        * @return -1 for non-formula cells, or the type of the <em>formula result</em>\r
+        */\r
+       public int evaluateFormulaCell(Cell cell) {\r
+               if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {\r
+                       return -1;\r
+               }\r
+               CellValue cv = evaluateFormulaCellValue(cell);\r
+               // cell remains a formula cell, but the cached value is changed\r
+               setCellValue(cell, cv);\r
+               return cv.getCellType();\r
+       }\r
 \r
-    /**\r
-     * If cell contains formula, it evaluates the formula, and\r
-     *  puts the formula result back into the cell, in place\r
-     *  of the old formula.\r
-     * Else if cell does not contain formula, this method leaves\r
-     *  the cell unchanged.\r
-     * Note that the same instance of HSSFCell is returned to\r
-     * allow chained calls like:\r
-     * <pre>\r
-     * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();\r
-     * </pre>\r
-     * Be aware that your cell value will be changed to hold the\r
-     *  result of the formula. If you simply want the formula\r
-     *  value computed for you, use {@link #evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)}}\r
-     * @param cell\r
-     */\r
-    public HSSFCell evaluateInCell(Cell cell) {\r
-        if (cell == null) {\r
-            return null;\r
-        }\r
-        HSSFCell result = (HSSFCell) cell;\r
-        if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {\r
-            CellValue cv = evaluateFormulaCellValue(cell);\r
-            setCellType(cell, cv); // cell will no longer be a formula cell\r
-            setCellValue(cell, cv);\r
-        }\r
-        return result;\r
-    }\r
-    private static void setCellType(Cell cell, CellValue cv) {\r
-        int cellType = cv.getCellType();\r
-        switch (cellType) {\r
-            case HSSFCell.CELL_TYPE_BOOLEAN:\r
-            case HSSFCell.CELL_TYPE_ERROR:\r
-            case HSSFCell.CELL_TYPE_NUMERIC:\r
-            case HSSFCell.CELL_TYPE_STRING:\r
-                cell.setCellType(cellType);\r
-                return;\r
-            case HSSFCell.CELL_TYPE_BLANK:\r
-                // never happens - blanks eventually get translated to zero\r
-            case HSSFCell.CELL_TYPE_FORMULA:\r
-                // this will never happen, we have already evaluated the formula\r
-        }\r
-        throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
-    }\r
+       /**\r
+        * If cell contains formula, it evaluates the formula, and\r
+        *  puts the formula result back into the cell, in place\r
+        *  of the old formula.\r
+        * Else if cell does not contain formula, this method leaves\r
+        *  the cell unchanged.\r
+        * Note that the same instance of HSSFCell is returned to\r
+        * allow chained calls like:\r
+        * <pre>\r
+        * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();\r
+        * </pre>\r
+        * Be aware that your cell value will be changed to hold the\r
+        *  result of the formula. If you simply want the formula\r
+        *  value computed for you, use {@link #evaluateFormulaCell(Cell)}}\r
+        */\r
+       public HSSFCell evaluateInCell(Cell cell) {\r
+               if (cell == null) {\r
+                       return null;\r
+               }\r
+               HSSFCell result = (HSSFCell) cell;\r
+               if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {\r
+                       CellValue cv = evaluateFormulaCellValue(cell);\r
+                       setCellType(cell, cv); // cell will no longer be a formula cell\r
+                       setCellValue(cell, cv);\r
+               }\r
+               return result;\r
+       }\r
+       private static void setCellType(Cell cell, CellValue cv) {\r
+               int cellType = cv.getCellType();\r
+               switch (cellType) {\r
+                       case HSSFCell.CELL_TYPE_BOOLEAN:\r
+                       case HSSFCell.CELL_TYPE_ERROR:\r
+                       case HSSFCell.CELL_TYPE_NUMERIC:\r
+                       case HSSFCell.CELL_TYPE_STRING:\r
+                               cell.setCellType(cellType);\r
+                               return;\r
+                       case HSSFCell.CELL_TYPE_BLANK:\r
+                               // never happens - blanks eventually get translated to zero\r
+                       case HSSFCell.CELL_TYPE_FORMULA:\r
+                               // this will never happen, we have already evaluated the formula\r
+               }\r
+               throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
+       }\r
 \r
-    private static void setCellValue(Cell cell, CellValue cv) {\r
-        int cellType = cv.getCellType();\r
-        switch (cellType) {\r
-            case HSSFCell.CELL_TYPE_BOOLEAN:\r
-                cell.setCellValue(cv.getBooleanValue());\r
-                break;\r
-            case HSSFCell.CELL_TYPE_ERROR:\r
-                cell.setCellErrorValue(cv.getErrorValue());\r
-                break;\r
-            case HSSFCell.CELL_TYPE_NUMERIC:\r
-                cell.setCellValue(cv.getNumberValue());\r
-                break;\r
-            case HSSFCell.CELL_TYPE_STRING:\r
-                cell.setCellValue(new HSSFRichTextString(cv.getStringValue()));\r
-                break;\r
-            case HSSFCell.CELL_TYPE_BLANK:\r
-                // never happens - blanks eventually get translated to zero\r
-            case HSSFCell.CELL_TYPE_FORMULA:\r
-                // this will never happen, we have already evaluated the formula\r
-            default:\r
-                throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
-        }\r
-    }\r
+       private static void setCellValue(Cell cell, CellValue cv) {\r
+               int cellType = cv.getCellType();\r
+               switch (cellType) {\r
+                       case HSSFCell.CELL_TYPE_BOOLEAN:\r
+                               cell.setCellValue(cv.getBooleanValue());\r
+                               break;\r
+                       case HSSFCell.CELL_TYPE_ERROR:\r
+                               cell.setCellErrorValue(cv.getErrorValue());\r
+                               break;\r
+                       case HSSFCell.CELL_TYPE_NUMERIC:\r
+                               cell.setCellValue(cv.getNumberValue());\r
+                               break;\r
+                       case HSSFCell.CELL_TYPE_STRING:\r
+                               cell.setCellValue(new HSSFRichTextString(cv.getStringValue()));\r
+                               break;\r
+                       case HSSFCell.CELL_TYPE_BLANK:\r
+                               // never happens - blanks eventually get translated to zero\r
+                       case HSSFCell.CELL_TYPE_FORMULA:\r
+                               // this will never happen, we have already evaluated the formula\r
+                       default:\r
+                               throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
+               }\r
+       }\r
 \r
-    /**\r
-     * Loops over all cells in all sheets of the supplied\r
-     *  workbook.\r
-     * For cells that contain formulas, their formulas are\r
-     *  evaluated, and the results are saved. These cells\r
-     *  remain as formula cells.\r
-     * For cells that do not contain formulas, no changes\r
-     *  are made.\r
-     * This is a helpful wrapper around looping over all\r
-     *  cells, and calling evaluateFormulaCell on each one.\r
-     */\r
-    public static void evaluateAllFormulaCells(HSSFWorkbook wb) {\r
-        HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);\r
-        for(int i=0; i<wb.getNumberOfSheets(); i++) {\r
-            HSSFSheet sheet = wb.getSheetAt(i);\r
+       /**\r
+        * Loops over all cells in all sheets of the supplied\r
+        *  workbook.\r
+        * For cells that contain formulas, their formulas are\r
+        *  evaluated, and the results are saved. These cells\r
+        *  remain as formula cells.\r
+        * For cells that do not contain formulas, no changes\r
+        *  are made.\r
+        * This is a helpful wrapper around looping over all\r
+        *  cells, and calling evaluateFormulaCell on each one.\r
+        */\r
+       public static void evaluateAllFormulaCells(HSSFWorkbook wb) {\r
+               HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);\r
+               for(int i=0; i<wb.getNumberOfSheets(); i++) {\r
+                       HSSFSheet sheet = wb.getSheetAt(i);\r
 \r
-            for (Iterator rit = sheet.rowIterator(); rit.hasNext();) {\r
-                HSSFRow r = (HSSFRow)rit.next();\r
+                       for (Iterator rit = sheet.rowIterator(); rit.hasNext();) {\r
+                               HSSFRow r = (HSSFRow)rit.next();\r
 \r
-                for (Iterator cit = r.cellIterator(); cit.hasNext();) {\r
-                    HSSFCell c = (HSSFCell)cit.next();\r
-                    if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA)\r
-                        evaluator.evaluateFormulaCell(c);\r
-                }\r
-            }\r
-        }\r
-    }\r
+                               for (Iterator cit = r.cellIterator(); cit.hasNext();) {\r
+                                       HSSFCell c = (HSSFCell)cit.next();\r
+                                       if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA)\r
+                                               evaluator.evaluateFormulaCell(c);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
 \r
-    /**\r
-     * Returns a CellValue wrapper around the supplied ValueEval instance.\r
-     * @param eval\r
-     */\r
-    private CellValue evaluateFormulaCellValue(Cell cell) {\r
-        ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell));\r
-        if (eval instanceof NumberEval) {\r
-            NumberEval ne = (NumberEval) eval;\r
-            return new CellValue(ne.getNumberValue());\r
-        }\r
-        if (eval instanceof BoolEval) {\r
-            BoolEval be = (BoolEval) eval;\r
-            return CellValue.valueOf(be.getBooleanValue());\r
-        }\r
-        if (eval instanceof StringEval) {\r
-            StringEval ne = (StringEval) eval;\r
-            return new CellValue(ne.getStringValue());\r
-        }\r
-        if (eval instanceof ErrorEval) {\r
-            return CellValue.getError(((ErrorEval)eval).getErrorCode());\r
-        }\r
-        throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");\r
-    }\r
+       /**\r
+        * Returns a CellValue wrapper around the supplied ValueEval instance.\r
+        * @param eval\r
+        */\r
+       private CellValue evaluateFormulaCellValue(Cell cell) {\r
+               ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell));\r
+               if (eval instanceof NumberEval) {\r
+                       NumberEval ne = (NumberEval) eval;\r
+                       return new CellValue(ne.getNumberValue());\r
+               }\r
+               if (eval instanceof BoolEval) {\r
+                       BoolEval be = (BoolEval) eval;\r
+                       return CellValue.valueOf(be.getBooleanValue());\r
+               }\r
+               if (eval instanceof StringEval) {\r
+                       StringEval ne = (StringEval) eval;\r
+                       return new CellValue(ne.getStringValue());\r
+               }\r
+               if (eval instanceof ErrorEval) {\r
+                       return CellValue.getError(((ErrorEval)eval).getErrorCode());\r
+               }\r
+               throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");\r
+       }\r
 }\r
index f0fd5c3a76877af3cd719004e2417db46442d961..2be15480744bd8e9fbb693e3ca5208116287ffa0 100644 (file)
@@ -45,6 +45,15 @@ final class FormulaCellCacheEntry extends CellCacheEntry {
        public FormulaCellCacheEntry() {
                
        }
+       
+       public boolean isInputSensitive() {
+               if (_sensitiveInputCells != null) {
+                       if (_sensitiveInputCells.length > 0 ) {
+                               return true;
+                       }
+               }
+               return _usedBlankCellGroup == null ? false : !_usedBlankCellGroup.isEmpty();
+       }
 
        public void setSensitiveInputCells(CellCacheEntry[] sensitiveInputCells) {
                // need to tell all cells that were previously used, but no longer are, 
index 7447edca0f3a2f42057513b16926764c6aa37e47..d96df2e903c686c37ea323ef254cfb5c74e4396d 100644 (file)
@@ -186,4 +186,8 @@ final class FormulaUsedBlankCellSet {
                }
                return bcsg.containsCell(rowIndex, columnIndex);
        }
+
+       public boolean isEmpty() {
+               return _sheetGroupsByBookSheet.isEmpty();
+       }
 }
diff --git a/src/java/org/apache/poi/ss/formula/IStabilityClassifier.java b/src/java/org/apache/poi/ss/formula/IStabilityClassifier.java
new file mode 100644 (file)
index 0000000..a7d2d3f
--- /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;
+
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+
+/**
+ * Used to help optimise cell evaluation result caching by allowing applications to specify which
+ * parts of a workbook are <em>final</em>.<br/>
+ * The term <b>final</b> is introduced here to denote immutability or 'having constant definition'.
+ * This classification refers to potential actions (on the evaluated workbook) by the evaluating
+ * application.  It does not refer to operations performed by the evaluator ({@link 
+ * WorkbookEvaluator}).<br/>
+ * <br/>
+ * <b>General guidelines</b>:
+ * <ul>
+ * <li>a plain value cell can be marked as 'final' if it will not be changed after the first call
+ * to {@link WorkbookEvaluator#evaluate(EvaluationCell)}.
+ * </li>
+ * <li>a formula cell can be marked as 'final' if its formula will not be changed after the first
+ * call to {@link WorkbookEvaluator#evaluate(EvaluationCell)}.  This remains true even if changes 
+ * in dependent values may cause the evaluated value to change.</li>
+ * <li>plain value cells should be marked as 'not final' if their plain value value may change.
+ * </li>  
+ * <li>formula cells should be marked as 'not final' if their formula definition may change.</li>  
+ * <li>cells which may switch between plain value and formula should also be marked as 'not final'.
+ * </li>  
+ * </ul>
+ * <b>Notes</b>:
+ * <ul>
+ * <li>If none of the spreadsheet cells is expected to have its definition changed after evaluation
+ * begins, every cell can be marked as 'final'. This is the most efficient / least resource 
+ * intensive option.</li>
+ * <li>To retain freedom to change any cell definition at any time, an application may classify all
+ * cells as 'not final'.  This freedom comes at the expense of greater memory consumption.</li>
+ * <li>For the purpose of these classifications, setting the cached formula result of a cell (for 
+ * example in {@link HSSFFormulaEvaluator#evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)})
+ * does not constitute changing the definition of the cell.</li>
+ * <li>Updating cells which have been classified as 'final' will cause the evaluator to behave 
+ * unpredictably (typically ignoring the update).</li> 
+ * </ul>
+ * 
+ * @author Josh Micich
+ */
+public interface IStabilityClassifier {
+
+       /**
+        * Convenience implementation for situations where all cell definitions remain fixed after
+        * evaluation begins.
+        */
+       IStabilityClassifier TOTALLY_IMMUTABLE = new IStabilityClassifier() {
+               public boolean isCellFinal(int sheetIndex, int rowIndex, int columnIndex) {
+                       return true;
+               }
+       };
+
+       /**
+        * Checks if a cell's value(/formula) is fixed - in other words - not expected to be modified
+        * between calls to the evaluator. (Note - this is an independent concept from whether a 
+        * formula cell's evaluated value may change during successive calls to the evaluator).
+        * 
+        * @param sheetIndex zero based index into workbook sheet list
+        * @param rowIndex zero based row index of cell
+        * @param columnIndex zero based column index of cell
+        * @return <code>false</code> if the evaluating application may need to modify the specified 
+        * cell between calls to the evaluator. 
+        */
+       boolean isCellFinal(int sheetIndex, int rowIndex, int columnIndex);
+}
index d3955f8796db6f5ff97237eb45defefcf37913e4..a2a3b97149685c31ebc934539e41eb491a1c4f11 100644 (file)
@@ -82,19 +82,22 @@ public final class WorkbookEvaluator {
        private int _workbookIx;
 
        private final IEvaluationListener _evaluationListener;
-       private final Map _sheetIndexesBySheet;
+       private final Map<EvaluationSheet, Integer> _sheetIndexesBySheet;
        private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
+       private final IStabilityClassifier _stabilityClassifier;
 
-       public WorkbookEvaluator(EvaluationWorkbook workbook) {
-               this (workbook, null);
+       public WorkbookEvaluator(EvaluationWorkbook workbook, IStabilityClassifier stabilityClassifier) {
+               this (workbook, null, stabilityClassifier);
        }
-       /* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener) {
+       /* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener,
+                       IStabilityClassifier stabilityClassifier) {
                _workbook = workbook;
                _evaluationListener = evaluationListener;
                _cache = new EvaluationCache(evaluationListener);
-               _sheetIndexesBySheet = new IdentityHashMap();
+               _sheetIndexesBySheet = new IdentityHashMap<EvaluationSheet, Integer>();
                _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
                _workbookIx = 0;
+               _stabilityClassifier = stabilityClassifier;
        }
 
        /**
@@ -141,7 +144,7 @@ public final class WorkbookEvaluator {
        }
 
        /**
-        * Should be called to tell the cell value cache that the specified (value or formula) cell 
+        * Should be called to tell the cell value cache that the specified (value or formula) cell
         * has changed.
         */
        public void notifyUpdateCell(EvaluationCell cell) {
@@ -150,7 +153,7 @@ public final class WorkbookEvaluator {
        }
        /**
         * Should be called to tell the cell value cache that the specified cell has just been
-        * deleted. 
+        * deleted.
         */
        public void notifyDeleteCell(EvaluationCell cell) {
                int sheetIndex = getSheetIndex(cell.getSheet());
@@ -158,7 +161,7 @@ public final class WorkbookEvaluator {
        }
 
        private int getSheetIndex(EvaluationSheet sheet) {
-               Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
+               Integer result = _sheetIndexesBySheet.get(sheet);
                if (result == null) {
                        int sheetIndex = _workbook.getSheetIndex(sheet);
                        if (sheetIndex < 0) {
@@ -182,14 +185,21 @@ public final class WorkbookEvaluator {
        private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex,
                                int rowIndex, int columnIndex, EvaluationTracker tracker) {
 
+               // avoid tracking dependencies for cells that have constant definition
+               boolean shouldCellDependencyBeRecorded = _stabilityClassifier == null ? true
+                                       : !_stabilityClassifier.isCellFinal(sheetIndex, rowIndex, columnIndex);
                if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) {
                        ValueEval result = getValueFromNonFormulaCell(srcCell);
-                       tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
+                       if (shouldCellDependencyBeRecorded) {
+                               tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
+                       }
                        return result;
                }
 
                FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell);
-               tracker.acceptFormulaDependency(cce);
+               if (shouldCellDependencyBeRecorded || cce.isInputSensitive()) {
+                       tracker.acceptFormulaDependency(cce);
+               }
                IEvaluationListener evalListener = _evaluationListener;
                if (cce.getValue() == null) {
                        if (!tracker.startEvaluate(cce)) {
@@ -252,7 +262,7 @@ public final class WorkbookEvaluator {
        // visibility raised for testing
        /* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
 
-               Stack stack = new Stack();
+               Stack<Eval> stack = new Stack<Eval>();
                for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
 
                        // since we don't know how to handle these yet :(
@@ -289,7 +299,7 @@ public final class WorkbookEvaluator {
 
                                // storing the ops in reverse order since they are popping
                                for (int j = numops - 1; j >= 0; j--) {
-                                       Eval p = (Eval) stack.pop();
+                                       Eval p = stack.pop();
                                        ops[j] = p;
                                }
 //                             logDebug("invoke " + operation + " (nAgs=" + numops + ")");
@@ -307,7 +317,7 @@ public final class WorkbookEvaluator {
                        stack.push(opResult);
                }
 
-               ValueEval value = ((ValueEval) stack.pop());
+               ValueEval value = (ValueEval) stack.pop();
                if (!stack.isEmpty()) {
                        throw new IllegalStateException("evaluation stack not empty");
                }
index cd20997c87a5dc4e8b57089c6442af37e3107846..8780ae23f3525308c6c444e228508722d8668c49 100644 (file)
@@ -24,6 +24,7 @@ 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.ss.formula.IStabilityClassifier;
 import org.apache.poi.ss.formula.WorkbookEvaluator;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellValue;
@@ -46,7 +47,15 @@ public class XSSFFormulaEvaluator implements FormulaEvaluator {
        private WorkbookEvaluator _bookEvaluator;
 
        public XSSFFormulaEvaluator(XSSFWorkbook workbook) {
-               _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook));
+               this(workbook, null);
+       }
+       /**
+        * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code>
+        * for the (conservative) assumption that any cell may have its definition changed after 
+        * evaluation begins.
+        */
+       public XSSFFormulaEvaluator(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) {
+               _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook), stabilityClassifier);
        }
 
        /**
index 47ceb2686fd5e3bde7f2c8b9c6ca7054db8f4207..81dad57d16d93119e0a9a3d0b6fbbccae6fca9fc 100644 (file)
@@ -42,6 +42,10 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  */
 public class TestWorkbookEvaluator extends TestCase {
 
+       private static WorkbookEvaluator createEvaluator() {
+               return new WorkbookEvaluator(null, null);
+       }
+
        /**
         * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
         * the whole formula which converts tAttrSum to tFuncVar("SUM") )
@@ -53,7 +57,7 @@ public class TestWorkbookEvaluator extends TestCase {
                        AttrPtg.SUM,
                };
 
-               ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
+               ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
                assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
        }
 
@@ -74,7 +78,7 @@ public class TestWorkbookEvaluator extends TestCase {
                        ptg,
                };
 
-               ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
+               ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
                assertEquals(ErrorEval.REF_INVALID, result);
        }
 
@@ -89,7 +93,7 @@ public class TestWorkbookEvaluator extends TestCase {
                        AttrPtg.SUM,
                };
 
-               ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
+               ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
                assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
        }
 
index 2111cf4e858b5a94c3a47a8e46fbe47eba8f92d8..b8b5de991e151823c91cdb2881f0b31ede19cd5b 100644 (file)
@@ -32,6 +32,6 @@ public final class WorkbookEvaluatorTestHelper {
        }
        
        public static WorkbookEvaluator createEvaluator(HSSFWorkbook wb, EvaluationListener listener) {
-               return new WorkbookEvaluator(HSSFEvaluationWorkbook.create(wb), listener);
+               return new WorkbookEvaluator(HSSFEvaluationWorkbook.create(wb), listener, null);
        }
 }