]> source.dussan.org Git - poi.git/commitdiff
Performance optimisation for EvaluationCache. Changed to use HSSFCell as key into...
authorJosh Micich <josh@apache.org>
Thu, 9 Oct 2008 06:07:36 +0000 (06:07 +0000)
committerJosh Micich <josh@apache.org>
Thu, 9 Oct 2008 06:07:36 +0000 (06:07 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@703063 13f79535-47bb-0310-9956-ffa450edef68

27 files changed:
src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationSheet.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/java/org/apache/poi/ss/formula/CellCacheEntry.java
src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java
src/java/org/apache/poi/ss/formula/CellLocation.java [deleted file]
src/java/org/apache/poi/ss/formula/EvaluationCache.java
src/java/org/apache/poi/ss/formula/EvaluationCell.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/EvaluationSheet.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/EvaluationTracker.java
src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
src/java/org/apache/poi/ss/formula/FormulaCellCache.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/FormulaCellCacheEntrySet.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/FormulaUsedBlankCellSet.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/IEvaluationListener.java
src/java/org/apache/poi/ss/formula/PlainCellCache.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/PlainValueCellCacheEntry.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
src/testcases/org/apache/poi/hssf/usermodel/HSSFEvaluationTestHelper.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorBugs.java
src/testcases/org/apache/poi/ss/formula/EvaluationListener.java
src/testcases/org/apache/poi/ss/formula/TestCellCacheEntry.java
src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java
src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java

diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java
new file mode 100644 (file)
index 0000000..e5e7116
--- /dev/null
@@ -0,0 +1,75 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import org.apache.poi.ss.formula.EvaluationCell;
+import org.apache.poi.ss.formula.EvaluationSheet;
+/**
+ * HSSF wrapper for a cell under evaluation
+ * 
+ * @author Josh Micich
+ */
+final class HSSFEvaluationCell implements EvaluationCell {
+
+       private final EvaluationSheet _evalSheet;
+       private final HSSFCell _cell;
+
+       public HSSFEvaluationCell(HSSFCell cell, EvaluationSheet evalSheet) {
+               _cell = cell;
+               _evalSheet = evalSheet;
+       }
+       public HSSFEvaluationCell(HSSFCell cell) {
+               _cell = cell;
+               _evalSheet = new HSSFEvaluationSheet(cell.getSheet());
+       }
+       // Note -  hashCode and equals defined according to underlying cell
+       public int hashCode() {
+               return _cell.hashCode();
+       }
+       public boolean equals(Object obj) {
+               return _cell == ((HSSFEvaluationCell)obj)._cell;
+       }
+
+       public HSSFCell getHSSFCell() {
+               return _cell;
+       }
+       public boolean getBooleanCellValue() {
+               return _cell.getBooleanCellValue();
+       }
+       public int getCellType() {
+               return _cell.getCellType();
+       }
+       public int getColumnIndex() {
+               return _cell.getCellNum();
+       }
+       public int getErrorCellValue() {
+               return _cell.getErrorCellValue();
+       }
+       public double getNumericCellValue() {
+               return _cell.getNumericCellValue();
+       }
+       public int getRowIndex() {
+               return _cell.getRowIndex();
+       }
+       public EvaluationSheet getSheet() {
+               return _evalSheet;
+       }
+       public String getStringCellValue() {
+               return _cell.getRichStringCellValue().getString();
+       }
+}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationSheet.java
new file mode 100644 (file)
index 0000000..166da47
--- /dev/null
@@ -0,0 +1,50 @@
+/* ====================================================================
+   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.usermodel;
+
+import org.apache.poi.ss.formula.EvaluationCell;
+import org.apache.poi.ss.formula.EvaluationSheet;
+
+/**
+ * HSSF wrapper for a sheet under evaluation
+ * 
+ * @author Josh Micich
+ */
+final class HSSFEvaluationSheet implements EvaluationSheet {
+
+       private final HSSFSheet _hs;
+
+       public HSSFEvaluationSheet(HSSFSheet hs) {
+               _hs = hs;
+       }
+
+       public HSSFSheet getHSSFSheet() {
+               return _hs;
+       }
+       public EvaluationCell getCell(int rowIndex, int columnIndex) {
+               HSSFRow row = _hs.getRow(rowIndex);
+               if (row == null) {
+                       return null;
+               }
+               HSSFCell cell = row.getCell(columnIndex);
+               if (cell == null) {
+                       return null;
+               }
+               return new HSSFEvaluationCell(cell, this);
+       }
+}
\ No newline at end of file
index 5d67606d3df2fc2e3f7325b75fc891c1dc3c67e7..359862e47732343d6670bf7bd2c5be514e77f637 100644 (file)
@@ -1,3 +1,20 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
 package org.apache.poi.hssf.usermodel;\r
 \r
 import org.apache.poi.hssf.model.HSSFFormulaParser;\r
@@ -8,7 +25,9 @@ import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
 import org.apache.poi.hssf.record.formula.NamePtg;\r
 import org.apache.poi.hssf.record.formula.NameXPtg;\r
 import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.ss.formula.EvaluationCell;\r
 import org.apache.poi.ss.formula.EvaluationName;\r
+import org.apache.poi.ss.formula.EvaluationSheet;\r
 import org.apache.poi.ss.formula.EvaluationWorkbook;\r
 import org.apache.poi.ss.formula.FormulaParsingWorkbook;\r
 import org.apache.poi.ss.formula.FormulaRenderingWorkbook;\r
@@ -43,8 +62,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                return _iBook.getExternalSheetIndex(workbookName, sheetName);\r
        }\r
 \r
-       public EvaluationName getName(int index) {\r
-               return new Name(_iBook.getNameRecord(index), index);\r
+       public NameXPtg getNameXPtg(String name) {\r
+               return _iBook.getNameXPtg(name);\r
        }\r
 \r
        public EvaluationName getName(String name) {\r
@@ -57,7 +76,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                return null;\r
        }\r
 \r
-       public int getSheetIndex(HSSFSheet sheet) {\r
+       public int getSheetIndex(EvaluationSheet evalSheet) {\r
+               HSSFSheet sheet = ((HSSFEvaluationSheet)evalSheet).getHSSFSheet();\r
                return _uBook.getSheetIndex(sheet);\r
        }\r
        public int getSheetIndex(String sheetName) {\r
@@ -68,16 +88,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                return _uBook.getSheetName(sheetIndex);\r
        }\r
 \r
-       public int getNameIndex(String name) {\r
-               return _uBook.getNameIndex(name);\r
-       }\r
-\r
-       public NameXPtg getNameXPtg(String name) {\r
-               return _iBook.getNameXPtg(name);\r
-       }\r
-\r
-       public HSSFSheet getSheet(int sheetIndex) {\r
-               return _uBook.getSheetAt(sheetIndex);\r
+       public EvaluationSheet getSheet(int sheetIndex) {\r
+               return new HSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));\r
        }\r
        public int convertFromExternSheetIndex(int externSheetIndex) {\r
                return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);\r
@@ -86,10 +98,6 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
        public ExternalSheet getExternalSheet(int externSheetIndex) {\r
                return _iBook.getExternalSheet(externSheetIndex);\r
        }\r
-       \r
-       public HSSFWorkbook getWorkbook() {\r
-               return _uBook;\r
-       }\r
 \r
        public String resolveNameXText(NameXPtg n) {\r
                return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex());\r
@@ -105,7 +113,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                int ix = namePtg.getIndex();\r
                return new Name(_iBook.getNameRecord(ix), ix);\r
        }\r
-       public Ptg[] getFormulaTokens(HSSFCell cell) {\r
+       public Ptg[] getFormulaTokens(EvaluationCell evalCell) {\r
+               HSSFCell cell = ((HSSFEvaluationCell)evalCell).getHSSFCell();\r
                if (false) {\r
                        // re-parsing the formula text also works, but is a waste of time\r
                        // It is useful from time to time to run all unit tests with this code\r
@@ -125,23 +134,18 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                        _nameRecord = nameRecord;\r
                        _index = index;\r
                }\r
-\r
                public Ptg[] getNameDefinition() {\r
                        return _nameRecord.getNameDefinition();\r
                }\r
-\r
                public String getNameText() {\r
                        return _nameRecord.getNameText();\r
                }\r
-\r
                public boolean hasFormula() {\r
                        return _nameRecord.hasFormula();\r
                }\r
-\r
                public boolean isFunctionName() {\r
                        return _nameRecord.isFunctionName();\r
                }\r
-\r
                public boolean isRange() {\r
                        return _nameRecord.hasFormula(); // TODO - is this right?\r
                }\r
index a836e65942fb0d7de685af0da2805343a3f622e9..df8d1802955e010801b921c8517a9cbf2811397d 100644 (file)
@@ -19,7 +19,6 @@ package org.apache.poi.hssf.usermodel;
 \r
 import java.util.Iterator;\r
 \r
-import org.apache.poi.hssf.record.formula.eval.BlankEval;\r
 import org.apache.poi.hssf.record.formula.eval.BoolEval;\r
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;\r
 import org.apache.poi.hssf.record.formula.eval.NumberEval;\r
@@ -83,7 +82,9 @@ public class HSSFFormulaEvaluator {
 \r
        /**\r
         * Should be called whenever there are major changes (e.g. moving sheets) to input cells\r
-        * in the evaluated workbook.\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
@@ -91,23 +92,22 @@ public class HSSFFormulaEvaluator {
                _bookEvaluator.clearAllCachedResultValues();\r
        }\r
        /**\r
-        * Sets the cached value for a plain (non-formula) cell.\r
-        * Should be called whenever there are changes to individual input cells in the evaluated workbook.\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
-        * @param never <code>null</code>. Use {@link BlankEval#INSTANCE} when the cell is being \r
-        * cleared. Otherwise an instance of {@link NumberEval}, {@link StringEval}, {@link BoolEval}\r
-        * or {@link ErrorEval} to represent a plain cell value.\r
         */\r
-       public void setCachedPlainValue(HSSFSheet sheet, int rowIndex, int columnIndex, ValueEval value) {\r
-               _bookEvaluator.setCachedPlainValue(sheet, rowIndex, columnIndex, value);\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 become a\r
-        * formula cell, or the formula text has changed \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 notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {\r
-               _bookEvaluator.notifySetFormula(sheet, rowIndex, columnIndex);\r
+       public void notifyDeleteCell(HSSFCell cell) {\r
+               _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell));\r
        }\r
 \r
        /**\r
@@ -269,7 +269,7 @@ public class HSSFFormulaEvaluator {
         * @param eval\r
         */\r
        private CellValue evaluateFormulaCellValue(HSSFCell cell) {\r
-               ValueEval eval = _bookEvaluator.evaluate(cell);\r
+               ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell(cell));\r
                if (eval instanceof NumberEval) {\r
                        NumberEval ne = (NumberEval) eval;\r
                        return new CellValue(ne.getNumberValue());\r
index bd7500023cfa246da24d66f9cdf3c2e1e6657951..1eb7298a50d082151ba8ec72a33ba13809ddb14f 100644 (file)
 
 package org.apache.poi.ss.formula;
 
-import java.util.HashSet;
-import java.util.Set;
-
 import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.BoolEval;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.IEvaluationListener.ICacheEntry;
 
 /**
  * Stores the parameters that identify the evaluation of one cell.<br/>
  */
-final class CellCacheEntry {
+abstract class CellCacheEntry implements ICacheEntry {
+       public static final CellCacheEntry[] EMPTY_ARRAY = { };
+       
+       private final FormulaCellCacheEntrySet _consumingCells;
        private ValueEval _value;
-       private final Set _consumingCells;
-       private CellLocation[] _usedCells;
-       private boolean _isPlainValueCell;
        
-       public CellCacheEntry() {
-               _consumingCells = new HashSet();
+
+       protected CellCacheEntry() {
+               _consumingCells = new FormulaCellCacheEntrySet();
        }
-       public boolean updatePlainValue(ValueEval value) {
-               boolean wasChanged = false;
-               if (!_isPlainValueCell) {
-                       wasChanged = true;
-               }
-               if (!areValuesEqual(_value, value)) {
-                       wasChanged = true;
+       protected final void clearValue() {
+               _value = null;
+       }
+
+       public final boolean updateValue(ValueEval value) {
+               if (value == null) {
+                       throw new IllegalArgumentException("Did not expect to update to null");
                }
-               _isPlainValueCell = true;
+               boolean result = !areValuesEqual(_value, value);
                _value = value;
-               _usedCells = null;
-               return wasChanged;
+               return result;
+       }
+       public final ValueEval getValue() {
+               return _value;
        }
-       private boolean areValuesEqual(ValueEval a, ValueEval b) {
+       
+       private static boolean areValuesEqual(ValueEval a, ValueEval b) {
                if (a == null) {
                        return false;
                }
@@ -78,41 +80,58 @@ final class CellCacheEntry {
                }
                throw new IllegalStateException("Unexpected value class (" + cls.getName() + ")");
        }
-       public void setFormulaResult(ValueEval value, CellLocation[] usedCells) {
-               _isPlainValueCell = false;
-               _value = value;
-               _usedCells = usedCells;
-       }
-       public void addConsumingCell(CellLocation cellLoc) {
+
+       public final void addConsumingCell(FormulaCellCacheEntry cellLoc) {
                _consumingCells.add(cellLoc);
                
        }
-       public CellLocation[] getConsumingCells() {
-               int nItems = _consumingCells.size();
-               if (nItems < 1) {
-                       return CellLocation.EMPTY_ARRAY;
-               }
-               CellLocation[] result = new CellLocation[nItems];
-               _consumingCells.toArray(result);
-               return result;
+       public final FormulaCellCacheEntry[] getConsumingCells() {
+               return _consumingCells.toArray();
        }
-       public CellLocation[] getUsedCells() {
-               return _usedCells;
+
+       public final void clearConsumingCell(FormulaCellCacheEntry cce) {
+               if(!_consumingCells.remove(cce)) {
+                       throw new IllegalStateException("Specified formula cell is not consumed by this cell");
+               }
        }
-       public void clearConsumingCell(CellLocation fc) {
-               if(!_consumingCells.remove(fc)) {
-                       throw new IllegalStateException("Cell '" + fc.formatAsString() + "' does not use this cell");
+       public final void recurseClearCachedFormulaResults(IEvaluationListener listener) {
+               if (listener == null) {
+                       recurseClearCachedFormulaResults();
+               } else {
+                       listener.onClearCachedValue(this);
+                       recurseClearCachedFormulaResults(listener, 1);
                }
+       }
+       
+       /**
+        * Calls formulaCell.setFormulaResult(null, null) recursively all the way up the tree of 
+        * dependencies. Calls usedCell.clearConsumingCell(fc) for each child of a cell that is
+        * cleared along the way.
+        * @param formulaCells
+        */
+       protected final void recurseClearCachedFormulaResults() {
+               FormulaCellCacheEntry[] formulaCells = getConsumingCells();
                
+               for (int i = 0; i < formulaCells.length; i++) {
+                       FormulaCellCacheEntry fc = formulaCells[i];
+                       fc.clearFormulaEntry();
+                       fc.recurseClearCachedFormulaResults();
+               }
        }
-       public ValueEval getValue() {
-               ValueEval result = _value;
-               if (result == null) {
-                       if (_isPlainValueCell) {
-                               throw new IllegalStateException("Plain value cell should always have a value");
-                               
-                       }
+       
+       /**
+        * Identical to {@link #recurseClearCachedFormulaResults()} except for the listener call-backs
+        */
+       protected final void recurseClearCachedFormulaResults(IEvaluationListener listener, int depth) {
+               FormulaCellCacheEntry[] formulaCells = getConsumingCells();
+
+               listener.sortDependentCachedValues(formulaCells);
+               for (int i = 0; i < formulaCells.length; i++) {
+                       FormulaCellCacheEntry fc = formulaCells[i];
+                       listener.onClearDependentCachedValue(fc, depth);
+                       fc.clearFormulaEntry();
+                       fc.recurseClearCachedFormulaResults(listener, depth+1);
                }
-               return result;
        }
+       
 }
\ No newline at end of file
index 1d438527ae69c4ee103a270446a407b963e88bac..5dbd2339d5d77fee2d6636cf295c7a35cb536ae8 100644 (file)
@@ -20,44 +20,58 @@ package org.apache.poi.ss.formula;
 import java.util.HashSet;\r
 import java.util.Set;\r
 \r
+import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
+\r
 /**\r
  * Stores details about the current evaluation of a cell.<br/>\r
  */\r
 final class CellEvaluationFrame {\r
 \r
-       private final CellLocation _cellLocation;\r
-       private final Set _usedCells;\r
+       private final FormulaCellCacheEntry _cce;\r
+       private final Set _sensitiveInputCells;\r
+       private FormulaUsedBlankCellSet _usedBlankCellGroup;\r
 \r
-       public CellEvaluationFrame(CellLocation cellLoc) {\r
-               _cellLocation = cellLoc;\r
-               _usedCells = new HashSet();\r
+       public CellEvaluationFrame(FormulaCellCacheEntry cce) {\r
+               _cce = cce;\r
+               _sensitiveInputCells = new HashSet();\r
        }\r
-       public CellLocation getCoordinates() {\r
-               return _cellLocation;\r
+       public CellCacheEntry getCCE() {\r
+               return _cce;\r
        }\r
 \r
        public String toString() {\r
                StringBuffer sb = new StringBuffer(64);\r
                sb.append(getClass().getName()).append(" [");\r
-               sb.append(_cellLocation.formatAsString());\r
                sb.append("]");\r
                return sb.toString();\r
        }\r
-       public void addUsedCell(CellLocation coordinates) {\r
-               _usedCells.add(coordinates);\r
+       /**\r
+        * @param inputCell a cell directly used by the formula of this evaluation frame\r
+        */\r
+       public void addSensitiveInputCell(CellCacheEntry inputCell) {\r
+               _sensitiveInputCells.add(inputCell);\r
        }\r
        /**\r
         * @return never <code>null</code>, (possibly empty) array of all cells directly used while \r
-        * evaluating the formula of this frame.  For non-formula cells this will always be an empty\r
-        * array;\r
+        * evaluating the formula of this frame.\r
         */\r
-       public CellLocation[] getUsedCells() {\r
-               int nItems = _usedCells.size();\r
+       private CellCacheEntry[] getSensitiveInputCells() {\r
+               int nItems = _sensitiveInputCells.size();\r
                if (nItems < 1) {\r
-                       return CellLocation.EMPTY_ARRAY;\r
+                       return CellCacheEntry.EMPTY_ARRAY;\r
                }\r
-               CellLocation[] result = new CellLocation[nItems];\r
-               _usedCells.toArray(result);\r
+               CellCacheEntry[] result = new CellCacheEntry[nItems];\r
+               _sensitiveInputCells.toArray(result);\r
                return result;\r
        }\r
+       public void addUsedBlankCell(int bookIndex, int sheetIndex, int rowIndex, int columnIndex) {\r
+               if (_usedBlankCellGroup == null) {\r
+                       _usedBlankCellGroup = new FormulaUsedBlankCellSet();\r
+               }\r
+               _usedBlankCellGroup.addCell(bookIndex, sheetIndex, rowIndex, columnIndex);\r
+       }\r
+       \r
+       public void updateFormulaResult(ValueEval result) {\r
+               _cce.updateFormulaResult(result, getSensitiveInputCells(), _usedBlankCellGroup);\r
+       }\r
 }
\ No newline at end of file
diff --git a/src/java/org/apache/poi/ss/formula/CellLocation.java b/src/java/org/apache/poi/ss/formula/CellLocation.java
deleted file mode 100644 (file)
index bb32e3d..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/* ====================================================================
-   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.util.CellReference;
-
-/**
- * Stores the parameters that identify the evaluation of one cell.<br/>
- */
-final class CellLocation {
-       public static final CellLocation[] EMPTY_ARRAY = { };
-       
-       private final int _bookIx;
-       private final int _sheetIndex;
-       private final int _rowIndex;
-       private final int _columnIndex;
-       private final int _hashCode;
-
-       public CellLocation(int bookIx, int sheetIndex, int rowIndex, int columnIndex) {
-               if (sheetIndex < 0) {
-                       throw new IllegalArgumentException("sheetIndex must not be negative");
-               }
-               _bookIx = bookIx;
-               _sheetIndex = sheetIndex;
-               _rowIndex = rowIndex;
-               _columnIndex = columnIndex;
-               _hashCode = _bookIx + 17 * (sheetIndex + 17 * (rowIndex + 17 * columnIndex));
-       }
-       public int getBookIndex() {
-               return _bookIx;
-       }
-       public int getSheetIndex() {
-               return _sheetIndex;
-       }
-       public int getRowIndex() {
-               return _rowIndex;
-       }
-       public int getColumnIndex() {
-               return _columnIndex;
-       }
-
-       public boolean equals(Object obj) {
-               CellLocation other = (CellLocation) obj;
-               if (getRowIndex() != other.getRowIndex()) {
-                       return false;
-               }
-               if (getColumnIndex() != other.getColumnIndex()) {
-                       return false;
-               }
-               if (getSheetIndex() != other.getSheetIndex()) {
-                       return false;
-               }
-               if (getBookIndex() != other.getBookIndex()) {
-                       return false;
-               }
-               return true;
-       }
-       public int hashCode() {
-               return _hashCode;
-       }
-
-       /**
-        * @return human readable string for debug purposes
-        */
-       public String formatAsString() {
-               CellReference cr = new CellReference(_rowIndex, _columnIndex, false, false);
-               return  "ShIx=" + getSheetIndex() + " " + cr.formatAsString();
-       }
-
-       public String toString() {
-               StringBuffer sb = new StringBuffer(64);
-               sb.append(getClass().getName()).append(" [");
-               sb.append(formatAsString());
-               sb.append("]");
-               return sb.toString();
-       }
-}
\ No newline at end of file
index a1b06ad17e5f0e71e0701d60e74c0a74536682dd..81745825c75d1d7c113f08779ace9f0694ef0cc8 100644 (file)
 
 package org.apache.poi.ss.formula;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.ss.formula.FormulaCellCache.IEntryOperation;
+import org.apache.poi.ss.formula.FormulaUsedBlankCellSet.BookSheetKey;
+import org.apache.poi.ss.formula.PlainCellCache.Loc;
 
 /**
  * Performance optimisation for {@link HSSFFormulaEvaluator}. This class stores previously
@@ -38,152 +39,138 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
  */
 final class EvaluationCache {
 
-       private final Map _entriesByLocation;
-       private final Map _consumingCellsByUsedCells;
+       private final PlainCellCache _plainCellCache;
+       private final FormulaCellCache _formulaCellCache;
        /** only used for testing. <code>null</code> otherwise */
-       private final IEvaluationListener _evaluationListener;
+       final IEvaluationListener _evaluationListener;
 
        /* package */EvaluationCache(IEvaluationListener evaluationListener) {
                _evaluationListener = evaluationListener;
-               _entriesByLocation = new HashMap();
-               _consumingCellsByUsedCells = new HashMap();
-       }
-
-       /**
-        * @param cellLoc never <code>null</code>
-        * @return only ever <code>null</code> for formula cells that have had their cached value cleared
-        */
-       public ValueEval getValue(CellLocation cellLoc) {
-               return getEntry(cellLoc).getValue();
+               _plainCellCache = new PlainCellCache();
+               _formulaCellCache = new FormulaCellCache();
        }
 
-       /**
-        * @param cellLoc
-        * @param usedCells never <code>null</code>, (possibly zero length) array of all cells actually 
-        * directly used when evaluating the formula
-        * @param isPlainValue pass <code>true</code> if cellLoc refers to a plain value (non-formula) 
-        * cell, <code>false</code> for a formula cell.
-        * @param value the value of a non-formula cell or the result of evaluating the cell formula
-        * Pass <code>null</code> to signify clearing the cached result of a formula cell) 
-        */
-       public void setValue(CellLocation cellLoc, boolean isPlainValue, 
-                       CellLocation[] usedCells, ValueEval value) {
+       public void notifyUpdateCell(int bookIndex, int sheetIndex, EvaluationCell cell) {
+               FormulaCellCacheEntry fcce = _formulaCellCache.get(cell);
+               
+               Loc loc = new Loc(bookIndex, sheetIndex, cell.getRowIndex(), cell.getColumnIndex());
+               PlainValueCellCacheEntry pcce = _plainCellCache.get(loc);
 
-               CellCacheEntry existingEntry = (CellCacheEntry) _entriesByLocation.get(cellLoc);
-               if (existingEntry != null && existingEntry.getValue() != null) {
-                       if (isPlainValue) {
-                               // can set a plain cell cached value any time
-                       } else if (value == null) {
-                               // or clear the cached value of a formula cell at any time
+               if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
+                       if (fcce == null) {
+                               if (pcce == null) {
+                                       updateAnyBlankReferencingFormulas(bookIndex, sheetIndex, cell.getRowIndex(), cell.getColumnIndex());
+                               }
+                               fcce = new FormulaCellCacheEntry();
+                               _formulaCellCache.put(cell, fcce);
                        } else {
-                               // but a formula cached value would only be getting updated if the cache didn't have a value to start with
-                               throw new IllegalStateException("Already have cached value for this cell: "
-                                               + cellLoc.formatAsString());
+                               fcce.recurseClearCachedFormulaResults(_evaluationListener);
+                               fcce.clearFormulaEntry();
                        }
-               }
-               sortCellLocationsForLogging(usedCells);
-               CellCacheEntry entry = getEntry(cellLoc);
-               CellLocation[] consumingFormulaCells = entry.getConsumingCells();
-               CellLocation[] prevUsedCells = entry.getUsedCells();
-               if (isPlainValue) {
-                       
-                       if(!entry.updatePlainValue(value)) {
-                               return;
+                       if (pcce == null) {
+                               // was formula cell before - no change of type
+                       } else {
+                               // changing from plain cell to formula cell
+                               pcce.recurseClearCachedFormulaResults(_evaluationListener);
+                               _plainCellCache.remove(loc);
                        }
                } else {
-                       entry.setFormulaResult(value, usedCells);
-                       for (int i = 0; i < usedCells.length; i++) {
-                               getEntry(usedCells[i]).addConsumingCell(cellLoc);
+                       ValueEval value = WorkbookEvaluator.getValueFromNonFormulaCell(cell);
+                       if (pcce == null) {
+                               if (fcce == null) {
+                                       updateAnyBlankReferencingFormulas(bookIndex, sheetIndex, cell.getRowIndex(), cell.getColumnIndex());
+                               }
+                               pcce = new PlainValueCellCacheEntry(value);
+                               _plainCellCache.put(loc, pcce);
+                               if (_evaluationListener != null) {
+                                       _evaluationListener.onReadPlainValue(sheetIndex, cell.getRowIndex(), cell.getColumnIndex(), pcce);
+                               }
+                       } else {
+                               if(pcce.updateValue(value)) {
+                                       pcce.recurseClearCachedFormulaResults(_evaluationListener);
+                               }
+                       }
+                       if (fcce == null) {
+                               // was plain cell before - no change of type
+                       } else {
+                               // was formula cell before - now a plain value
+                               _formulaCellCache.remove(cell);
+                               fcce.setSensitiveInputCells(null);
+                               fcce.recurseClearCachedFormulaResults(_evaluationListener);
                        }
                }
-               // need to tell all cells that were previously used, but no longer are, 
-               // that they are not consumed by this cell any more
-               unlinkConsumingCells(prevUsedCells, usedCells, cellLoc);
-               
-               // clear all cells that directly or indirectly depend on this one
-               recurseClearCachedFormulaResults(consumingFormulaCells, 0);
        }
 
-       /**
-        * This method sorts the supplied cellLocs so that the order of call-backs to the evaluation 
-        * listener is more deterministic
-        */
-       private void sortCellLocationsForLogging(CellLocation[] cellLocs) {
-               if (_evaluationListener == null) {
-                       // optimisation - don't bother sorting if there is no listener.
-               } else {
-                       Arrays.sort(cellLocs, CellLocationComparator);
-               }
-       }
+       private void updateAnyBlankReferencingFormulas(int bookIndex, int sheetIndex, 
+                       final int rowIndex, final int columnIndex) {
+               final BookSheetKey bsk = new BookSheetKey(bookIndex, sheetIndex);
+               _formulaCellCache.applyOperation(new IEntryOperation() {
 
-       private void unlinkConsumingCells(CellLocation[] prevUsedCells, CellLocation[] usedCells,
-                       CellLocation cellLoc) {
-               if (prevUsedCells == null) {
-                       return;
-               }
-               int nPrevUsed = prevUsedCells.length;
-               if (nPrevUsed < 1) {
-                       return;
-               }
-               int nUsed = usedCells.length;
-               Set usedSet;
-               if (nUsed < 1) {
-                       usedSet = Collections.EMPTY_SET;
-               } else {
-                       usedSet = new HashSet(nUsed * 3 / 2);
-                       for (int i = 0; i < nUsed; i++) {
-                               usedSet.add(usedCells[i]);
-                       }
-               }
-               for (int i = 0; i < nPrevUsed; i++) {
-                       CellLocation prevUsed = prevUsedCells[i];
-                       if (!usedSet.contains(prevUsed)) {
-                               // previously was used by cellLoc, but not anymore
-                               getEntry(prevUsed).clearConsumingCell(cellLoc);
-                               if (_evaluationListener != null) {
-                                       //TODO _evaluationListener.onUnconsume(prevUsed.getSheetIndex(), etc)
-                               }
+                       public void processEntry(FormulaCellCacheEntry entry) {
+                               entry.notifyUpdatedBlankCell(bsk, rowIndex, columnIndex, _evaluationListener);
                        }
-               }
-               
+               });
        }
 
-       /**
-        * Calls formulaCell.setFormulaResult(null, null) recursively all the way up the tree of 
-        * dependencies. Calls usedCell.clearConsumingCell(fc) for each child of a cell that is
-        * cleared along the way.
-        * @param formulaCells
-        */
-       private void recurseClearCachedFormulaResults(CellLocation[] formulaCells, int depth) {
-               sortCellLocationsForLogging(formulaCells);
-               int nextDepth = depth+1;
-               for (int i = 0; i < formulaCells.length; i++) {
-                       CellLocation fc = formulaCells[i];
-                       CellCacheEntry formulaCell = getEntry(fc);
-                       CellLocation[] usedCells = formulaCell.getUsedCells();
-                       if (usedCells != null) {
-                               for (int j = 0; j < usedCells.length; j++) {
-                                       CellCacheEntry usedCell = getEntry(usedCells[j]);
-                                       usedCell.clearConsumingCell(fc);
-                               }
+       public PlainValueCellCacheEntry getPlainValueEntry(int bookIndex, int sheetIndex,
+                       int rowIndex, int columnIndex, ValueEval value) {
+               
+               Loc loc = new Loc(bookIndex, sheetIndex, rowIndex, columnIndex);
+               PlainValueCellCacheEntry result = _plainCellCache.get(loc);
+               if (result == null) {
+                       result = new PlainValueCellCacheEntry(value);
+                       _plainCellCache.put(loc, result);
+                       if (_evaluationListener != null) {
+                               _evaluationListener.onReadPlainValue(sheetIndex, rowIndex, columnIndex, result);
+                       }
+               } else {
+                       // TODO - if we are confident that this sanity check is not required, we can remove 'value' from plain value cache entry  
+                       if (!areValuesEqual(result.getValue(), value)) {
+                               throw new IllegalStateException("value changed");
                        }
                        if (_evaluationListener != null) {
-                               ValueEval value = formulaCell.getValue();
-                               _evaluationListener.onClearDependentCachedValue(fc.getSheetIndex(), fc.getRowIndex(), fc.getColumnIndex(), value, nextDepth);
+                               _evaluationListener.onCacheHit(sheetIndex, rowIndex, columnIndex, value);
                        }
-                       formulaCell.setFormulaResult(null, null);
-                       recurseClearCachedFormulaResults(formulaCell.getConsumingCells(), nextDepth);
                }
+               return result;
        }
-
-       private CellCacheEntry getEntry(CellLocation cellLoc) {
-               CellCacheEntry result = (CellCacheEntry)_entriesByLocation.get(cellLoc);
+       private boolean areValuesEqual(ValueEval a, ValueEval b) {
+               if (a == null) {
+                       return false;
+               }
+               Class cls = a.getClass();
+               if (cls != b.getClass()) {
+                       // value type is changing
+                       return false;
+               }
+               if (a == BlankEval.INSTANCE) {
+                       return b == a;
+               }
+               if (cls == NumberEval.class) {
+                       return ((NumberEval)a).getNumberValue() == ((NumberEval)b).getNumberValue();
+               }
+               if (cls == StringEval.class) {
+                       return ((StringEval)a).getStringValue().equals(((StringEval)b).getStringValue());
+               }
+               if (cls == BoolEval.class) {
+                       return ((BoolEval)a).getBooleanValue() == ((BoolEval)b).getBooleanValue();
+               }
+               if (cls == ErrorEval.class) {
+                       return ((ErrorEval)a).getErrorCode() == ((ErrorEval)b).getErrorCode();
+               }
+               throw new IllegalStateException("Unexpected value class (" + cls.getName() + ")");
+       }
+       
+       public FormulaCellCacheEntry getOrCreateFormulaCellEntry(EvaluationCell cell) {
+               FormulaCellCacheEntry result = _formulaCellCache.get(cell);
                if (result == null) {
-                       result = new CellCacheEntry();
-                       _entriesByLocation.put(cellLoc, result);
+                       
+                       result = new FormulaCellCacheEntry();
+                       _formulaCellCache.put(cell, result);
                }
                return result;
        }
+
        /**
         * Should be called whenever there are changes to input cells in the evaluated workbook.
         */
@@ -191,34 +178,28 @@ final class EvaluationCache {
                if(_evaluationListener != null) {
                        _evaluationListener.onClearWholeCache();
                }
-               _entriesByLocation.clear();
-               _consumingCellsByUsedCells.clear();
+               _plainCellCache.clear();
+               _formulaCellCache.clear();
        }
-
-
-       private static final Comparator CellLocationComparator = new Comparator() {
-
-               public int compare(Object a, Object b) {
-                       CellLocation clA = (CellLocation) a;
-                       CellLocation clB = (CellLocation) b;
-                       
-                       int cmp;
-                       cmp = clA.getBookIndex() - clB.getBookIndex();
-                       if (cmp != 0) {
-                               return cmp;
-                       }
-                       cmp = clA.getSheetIndex() - clB.getSheetIndex();
-                       if (cmp != 0) {
-                               return cmp;
-                       }
-                       cmp = clA.getRowIndex() - clB.getRowIndex();
-                       if (cmp != 0) {
-                               return cmp;
+       public void notifyDeleteCell(int bookIndex, int sheetIndex, EvaluationCell cell) {
+               
+               if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
+                       FormulaCellCacheEntry fcce = _formulaCellCache.remove(cell);
+                       if (fcce == null) {
+                               // formula cell has not been evaluated yet 
+                       } else {
+                               fcce.setSensitiveInputCells(null);
+                               fcce.recurseClearCachedFormulaResults(_evaluationListener);
                        }
-                       cmp = clA.getColumnIndex() - clB.getColumnIndex();
+               } else {
+                       Loc loc = new Loc(bookIndex, sheetIndex, cell.getRowIndex(), cell.getColumnIndex());
+                       PlainValueCellCacheEntry pcce = _plainCellCache.get(loc);
                        
-                       return cmp;
+                       if (pcce == null) {
+                               // cache entry doesn't exist. nothing to do
+                       } else {
+                               pcce.recurseClearCachedFormulaResults(_evaluationListener);
+                       }
                }
-               
-       };
+       }
 }
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCell.java b/src/java/org/apache/poi/ss/formula/EvaluationCell.java
new file mode 100644 (file)
index 0000000..c15754b
--- /dev/null
@@ -0,0 +1,42 @@
+/* ====================================================================
+   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;
+
+/**
+ * Abstracts a cell for the purpose of formula evaluation.  This interface represents both formula
+ * and non-formula cells.<br/>
+ * 
+ * Implementors of this class must implement {@link #hashCode()} and {@link #equals(Object)}
+ * to provide an <em>identity</em> relationship based on the underlying HSSF or XSSF cell <p/>
+ * 
+ * For POI internal use only
+ * 
+ * @author Josh Micich
+ */
+public interface EvaluationCell {
+       // consider method Object getUnderlyingCell() to reduce memory consumption in formula cell cache
+       EvaluationSheet getSheet();
+       int getRowIndex();
+       int getColumnIndex();
+       int getCellType();
+
+       double getNumericCellValue();
+       String getStringCellValue();
+       boolean getBooleanCellValue();
+       int getErrorCellValue();
+}
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationSheet.java b/src/java/org/apache/poi/ss/formula/EvaluationSheet.java
new file mode 100644 (file)
index 0000000..5dec3aa
--- /dev/null
@@ -0,0 +1,33 @@
+/* ====================================================================
+   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;
+
+/**
+ * Abstracts a sheet for the purpose of formula evaluation.<br/>
+ * 
+ * For POI internal use only
+ * 
+ * @author Josh Micich
+ */
+public interface EvaluationSheet {
+
+       /**
+        * @return <code>null</code> if there is no cell at the specified coordinates
+        */
+       EvaluationCell getCell(int rowIndex, int columnIndex);
+}
index 834941e9be62f2a6b8835e1a0c37b0c2d07182e8..eda62da8b8664192fb64957ea29de2f8bc691164 100755 (executable)
@@ -22,7 +22,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.usermodel.HSSFCell;
 
@@ -32,11 +32,11 @@ import org.apache.poi.hssf.usermodel.HSSFCell;
  * The main purpose of this class is to detect an attempt to evaluate a cell
  * that is already being evaluated. In other words, it detects circular
  * references in spreadsheet formulas.
- * 
+ *
  * @author Josh Micich
  */
 final class EvaluationTracker {
-
+       // TODO - consider deleting this class and letting CellEvaluationFrame take care of itself
        private final List _evaluationFrames;
        private final Set _currentlyEvaluatingCells;
        private final EvaluationCache _cache;
@@ -50,47 +50,51 @@ final class EvaluationTracker {
        /**
         * Notifies this evaluation tracker that evaluation of the specified cell is
         * about to start.<br/>
-        * 
+        *
         * In the case of a <code>true</code> return code, the caller should
         * continue evaluation of the specified cell, and also be sure to call
         * <tt>endEvaluate()</tt> when complete.<br/>
-        * 
-        * In the case of a <code>false</code> return code, the caller should
+        *
+        * In the case of a <code>null</code> return code, the caller should
         * return an evaluation result of
-        * <tt>ErrorEval.CIRCULAR_REF_ERROR<tt>, and not call <tt>endEvaluate()</tt>.  
+        * <tt>ErrorEval.CIRCULAR_REF_ERROR<tt>, and not call <tt>endEvaluate()</tt>.
         * <br/>
-        * @return <code>true</code> if the specified cell has not been visited yet in the current 
-        * evaluation. <code>false</code> if the specified cell is already being evaluated.
+        * @return <code>false</code> if the specified cell is already being evaluated
         */
-       public ValueEval startEvaluate(CellLocation cellLoc) {
-               if (cellLoc == null) {
+       public boolean startEvaluate(FormulaCellCacheEntry cce) {
+               if (cce == null) {
                        throw new IllegalArgumentException("cellLoc must not be null");
                }
-               if (_currentlyEvaluatingCells.contains(cellLoc)) {
-                       return ErrorEval.CIRCULAR_REF_ERROR;
+               if (_currentlyEvaluatingCells.contains(cce)) {
+                       return false;
                }
-               ValueEval result = _cache.getValue(cellLoc);
-               if (result == null) {
-                       _currentlyEvaluatingCells.add(cellLoc);
-                       _evaluationFrames.add(new CellEvaluationFrame(cellLoc));
+               _currentlyEvaluatingCells.add(cce);
+               _evaluationFrames.add(new CellEvaluationFrame(cce));
+               return true;
+       }
+
+       public void updateCacheResult(ValueEval result) {
+
+               int nFrames = _evaluationFrames.size();
+               if (nFrames < 1) {
+                       throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
                }
-               return result;
+               CellEvaluationFrame frame = (CellEvaluationFrame) _evaluationFrames.get(nFrames-1);
+
+               frame.updateFormulaResult(result);
        }
 
        /**
-        * Notifies this evaluation tracker that the evaluation of the specified
-        * cell is complete. <p/>
-        * 
-        * Every successful call to <tt>startEvaluate</tt> must be followed by a
-        * call to <tt>endEvaluate</tt> (recommended in a finally block) to enable
+        * Notifies this evaluation tracker that the evaluation of the specified cell is complete. <p/>
+        *
+        * Every successful call to <tt>startEvaluate</tt> must be followed by a call to <tt>endEvaluate</tt> (recommended in a finally block) to enable
         * proper tracking of which cells are being evaluated at any point in time.<p/>
-        * 
+        *
         * Assuming a well behaved client, parameters to this method would not be
         * required. However, they have been included to assert correct behaviour,
         * and form more meaningful error messages.
-        * @param result 
         */
-       public void endEvaluate(CellLocation cellLoc, ValueEval result, boolean isPlainValueCell) {
+       public void endEvaluate(CellCacheEntry cce) {
 
                int nFrames = _evaluationFrames.size();
                if (nFrames < 1) {
@@ -99,32 +103,40 @@ final class EvaluationTracker {
 
                nFrames--;
                CellEvaluationFrame frame = (CellEvaluationFrame) _evaluationFrames.get(nFrames);
-               CellLocation coordinates = frame.getCoordinates();
-               if (!coordinates.equals(cellLoc)) {
-                       throw new RuntimeException("Wrong cell specified. "
-                                       + "Corresponding startEvaluate() call was for cell {"
-                                       + coordinates.formatAsString() + "} this endEvaluate() call is for cell {"
-                                       + cellLoc.formatAsString() + "}");
+               if (cce != frame.getCCE()) {
+                       throw new IllegalStateException("Wrong cell specified. ");
                }
-               // else - no problems so pop current frame 
+               // else - no problems so pop current frame
                _evaluationFrames.remove(nFrames);
-               _currentlyEvaluatingCells.remove(coordinates);
-
-               // TODO - don't cache results of volatile formulas 
-               _cache.setValue(coordinates, isPlainValueCell, frame.getUsedCells(), result);
+               _currentlyEvaluatingCells.remove(cce);
        }
-       /**
-        * Tells the currently evaluating cell frame that it has a dependency on the specified 
-        * <tt>usedCell<tt> 
-        * @param usedCell location of cell which is referenced (and used) by the current cell.
-        */
-       public void acceptDependency(CellLocation usedCell) {
+
+       public void acceptFormulaDependency(CellCacheEntry cce) {
+               // Tell the currently evaluating cell frame that it has a dependency on the specified
                int prevFrameIndex = _evaluationFrames.size()-1;
                if (prevFrameIndex < 0) {
-                       throw new IllegalStateException("Call to acceptDependency without prior call to startEvaluate");
+                       // Top level frame, there is no 'cell' above this frame that is using the current cell
+               } else {
+                       CellEvaluationFrame consumingFrame = (CellEvaluationFrame) _evaluationFrames.get(prevFrameIndex);
+                       consumingFrame.addSensitiveInputCell(cce);
                }
-               CellEvaluationFrame consumingFrame = (CellEvaluationFrame) _evaluationFrames.get(prevFrameIndex);
-               consumingFrame.addUsedCell(usedCell);
        }
 
+       public void acceptPlainValueDependency(int bookIndex, int sheetIndex,
+                       int rowIndex, int columnIndex, ValueEval value) {
+               // Tell the currently evaluating cell frame that it has a dependency on the specified
+               int prevFrameIndex = _evaluationFrames.size() - 1;
+               if (prevFrameIndex < 0) {
+                       // Top level frame, there is no 'cell' above this frame that is using the current cell
+               } else {
+                       CellEvaluationFrame consumingFrame = (CellEvaluationFrame) _evaluationFrames.get(prevFrameIndex);
+                       if (value == BlankEval.INSTANCE) {
+                               consumingFrame.addUsedBlankCell(bookIndex, sheetIndex, rowIndex, columnIndex);
+                       } else {
+                               PlainValueCellCacheEntry cce = _cache.getPlainValueEntry(bookIndex, sheetIndex,
+                                               rowIndex, columnIndex, value);
+                               consumingFrame.addSensitiveInputCell(cce);
+                       }
+               }
+       }
 }
index 41678f19200c2c0270fe4227950a76b7b3b1dad0..45b0c918f2f582ea60e520c71721286b4b0323d9 100644 (file)
@@ -20,8 +20,7 @@ package org.apache.poi.ss.formula;
 import org.apache.poi.hssf.record.formula.NamePtg;\r
 import org.apache.poi.hssf.record.formula.NameXPtg;\r
 import org.apache.poi.hssf.record.formula.Ptg;\r
-import org.apache.poi.hssf.usermodel.HSSFCell;\r
-import org.apache.poi.hssf.usermodel.HSSFSheet;\r
+\r
 /**\r
  * Abstracts a workbook for the purpose of formula evaluation.<br/>\r
  * \r
@@ -34,10 +33,10 @@ public interface EvaluationWorkbook {
        /**\r
         * @return -1 if the specified sheet is from a different book\r
         */\r
-       int getSheetIndex(HSSFSheet sheet);\r
+       int getSheetIndex(EvaluationSheet sheet);\r
        int getSheetIndex(String sheetName);\r
 \r
-       HSSFSheet getSheet(int sheetIndex);\r
+       EvaluationSheet getSheet(int sheetIndex);\r
 \r
        /**\r
         * @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook\r
@@ -46,7 +45,7 @@ public interface EvaluationWorkbook {
        int convertFromExternSheetIndex(int externSheetIndex);\r
        EvaluationName getName(NamePtg namePtg);\r
        String resolveNameXText(NameXPtg ptg);\r
-       Ptg[] getFormulaTokens(HSSFCell cell);\r
+       Ptg[] getFormulaTokens(EvaluationCell cell);\r
        \r
        class ExternalSheet {\r
                private final String _workbookName;\r
diff --git a/src/java/org/apache/poi/ss/formula/FormulaCellCache.java b/src/java/org/apache/poi/ss/formula/FormulaCellCache.java
new file mode 100644 (file)
index 0000000..4a1fce6
--- /dev/null
@@ -0,0 +1,73 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 
+ * @author Josh Micich
+ */
+final class FormulaCellCache {
+
+       static interface IEntryOperation {
+               void processEntry(FormulaCellCacheEntry entry);
+       }
+
+       private Map _formulaEntriesByCell;
+
+       public FormulaCellCache() {
+               // assumes HSSFCell does not override hashCode or equals, otherwise we need IdentityHashMap
+               _formulaEntriesByCell = new HashMap(); 
+       }
+
+       public CellCacheEntry[] getCacheEntries() {
+
+               FormulaCellCacheEntry[] result = new FormulaCellCacheEntry[_formulaEntriesByCell.size()];
+               _formulaEntriesByCell.values().toArray(result);
+               return result;
+       }
+
+       public void clear() {
+               _formulaEntriesByCell.clear();
+       }
+
+       /**
+        * @return <code>null</code> if not found
+        */
+       public FormulaCellCacheEntry get(EvaluationCell cell) {
+               return (FormulaCellCacheEntry) _formulaEntriesByCell.get(cell);
+       }
+
+       public void put(EvaluationCell cell, FormulaCellCacheEntry entry) {
+               _formulaEntriesByCell.put(cell, entry);
+       }
+
+       public FormulaCellCacheEntry remove(EvaluationCell cell) {
+               return (FormulaCellCacheEntry) _formulaEntriesByCell.remove(cell);
+       }
+
+       public void applyOperation(IEntryOperation operation) {
+               Iterator i = _formulaEntriesByCell.values().iterator();
+               while (i.hasNext()) {
+                       operation.processEntry((FormulaCellCacheEntry) i.next());
+               }
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java b/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java
new file mode 100644 (file)
index 0000000..f0fd5c3
--- /dev/null
@@ -0,0 +1,113 @@
+/* ====================================================================
+   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 java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.FormulaUsedBlankCellSet.BookSheetKey;
+
+
+/**
+ * Stores the cached result of a formula evaluation, along with the set of sensititive input cells
+ * 
+ * @author Josh Micich
+ */
+final class FormulaCellCacheEntry extends CellCacheEntry {
+       public static final FormulaCellCacheEntry[] EMPTY_ARRAY = { };
+       
+       /**
+        * Cells 'used' in the current evaluation of the formula corresponding to this cache entry
+        *
+        * If any of the following cells change, this cache entry needs to be cleared
+        */
+       private CellCacheEntry[] _sensitiveInputCells;
+
+       private FormulaUsedBlankCellSet _usedBlankCellGroup;
+
+       public FormulaCellCacheEntry() {
+               
+       }
+
+       public void setSensitiveInputCells(CellCacheEntry[] sensitiveInputCells) {
+               // need to tell all cells that were previously used, but no longer are, 
+               // that they are not consumed by this cell any more
+               changeConsumingCells(sensitiveInputCells == null ? CellCacheEntry.EMPTY_ARRAY : sensitiveInputCells);
+               _sensitiveInputCells = sensitiveInputCells;
+       }
+
+       public void clearFormulaEntry() {
+               CellCacheEntry[] usedCells = _sensitiveInputCells;
+               if (usedCells != null) {
+                       for (int i = usedCells.length-1; i>=0; i--) {
+                               usedCells[i].clearConsumingCell(this);
+                       }
+               }
+               _sensitiveInputCells = null;
+               clearValue();
+       }
+       
+       private void changeConsumingCells(CellCacheEntry[] usedCells) {
+
+               CellCacheEntry[] prevUsedCells = _sensitiveInputCells;
+               int nUsed = usedCells.length;
+               for (int i = 0; i < nUsed; i++) {
+                       usedCells[i].addConsumingCell(this);
+               }
+               if (prevUsedCells == null) {
+                       return;
+               }
+               int nPrevUsed = prevUsedCells.length;
+               if (nPrevUsed < 1) {
+                       return;
+               }
+               Set usedSet;
+               if (nUsed < 1) {
+                       usedSet = Collections.EMPTY_SET;
+               } else {
+                       usedSet = new HashSet(nUsed * 3 / 2);
+                       for (int i = 0; i < nUsed; i++) {
+                               usedSet.add(usedCells[i]);
+                       }
+               }
+               for (int i = 0; i < nPrevUsed; i++) {
+                       CellCacheEntry prevUsed = prevUsedCells[i];
+                       if (!usedSet.contains(prevUsed)) {
+                               // previously was used by cellLoc, but not anymore
+                               prevUsed.clearConsumingCell(this);
+                       }
+               }
+       }
+
+       public void updateFormulaResult(ValueEval result, CellCacheEntry[] sensitiveInputCells, FormulaUsedBlankCellSet usedBlankAreas) {
+               updateValue(result);
+               setSensitiveInputCells(sensitiveInputCells);
+               _usedBlankCellGroup = usedBlankAreas;
+       }
+
+       public void notifyUpdatedBlankCell(BookSheetKey bsk, int rowIndex, int columnIndex, IEvaluationListener evaluationListener) {
+               if (_usedBlankCellGroup != null) {
+                       if (_usedBlankCellGroup.containsCell(bsk, rowIndex, columnIndex)) {
+                               clearFormulaEntry();
+                               recurseClearCachedFormulaResults(evaluationListener);
+                       }
+               }
+       }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntrySet.java b/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntrySet.java
new file mode 100644 (file)
index 0000000..780d266
--- /dev/null
@@ -0,0 +1,156 @@
+/* ====================================================================
+   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;
+
+/**
+ * A custom implementation of {@link java.util.HashSet} in order to reduce memory consumption.
+ *
+ * Profiling tests (Oct 2008) have shown that each element {@link FormulaCellCacheEntry} takes
+ * around 32 bytes to store in a HashSet, but around 6 bytes to store here.  For Spreadsheets with
+ * thousands of formula cells with multiple interdependencies, the savings can be very significant.
+ *
+ * @author Josh Micich
+ */
+final class FormulaCellCacheEntrySet {
+
+       private int _size;
+       private FormulaCellCacheEntry[] _arr;
+
+       public FormulaCellCacheEntrySet() {
+               _arr = FormulaCellCacheEntry.EMPTY_ARRAY;
+       }
+
+       public FormulaCellCacheEntry[] toArray() {
+               int nItems = _size;
+               if (nItems < 1) {
+                       return FormulaCellCacheEntry.EMPTY_ARRAY;
+               }
+               FormulaCellCacheEntry[] result = new FormulaCellCacheEntry[nItems];
+               int j=0;
+               for(int i=0; i<_arr.length; i++) {
+                       FormulaCellCacheEntry cce = _arr[i];
+                       if (cce != null) {
+                               result[j++] = cce;
+                       }
+               }
+               if (j!= nItems) {
+                       throw new IllegalStateException("size mismatch");
+               }
+               return result;
+       }
+
+
+       public void add(CellCacheEntry cce) {
+               if (_size * 3 >= _arr.length * 2) {
+                       // re-hash
+                       FormulaCellCacheEntry[] prevArr = _arr;
+                       FormulaCellCacheEntry[] newArr = new FormulaCellCacheEntry[4 + _arr.length * 3 / 2]; // grow 50%
+                       for(int i=0; i<prevArr.length; i++) {
+                               FormulaCellCacheEntry prevCce = _arr[i];
+                               if (prevCce != null) {
+                                       addInternal(newArr, prevCce);
+                               }
+                       }
+                       _arr = newArr;
+               }
+               if (addInternal(_arr, cce)) {
+                       _size++;
+               }
+       }
+
+
+       private static boolean addInternal(CellCacheEntry[] arr, CellCacheEntry cce) {
+
+               int startIx = cce.hashCode() % arr.length;
+
+               for(int i=startIx; i<arr.length; i++) {
+                       CellCacheEntry item = arr[i];
+                       if (item == cce) {
+                               // already present
+                               return false;
+                       }
+                       if (item == null) {
+                               arr[i] = cce;
+                               return true;
+                       }
+               }
+               for(int i=0; i<startIx; i++) {
+                       CellCacheEntry item = arr[i];
+                       if (item == cce) {
+                               // already present
+                               return false;
+                       }
+                       if (item == null) {
+                               arr[i] = cce;
+                               return true;
+                       }
+               }
+               throw new IllegalStateException("No empty space found");
+       }
+
+       public boolean remove(CellCacheEntry cce) {
+               FormulaCellCacheEntry[] arr = _arr;
+
+               if (_size * 3 < _arr.length && _arr.length > 8) {
+                       // re-hash
+                       boolean found = false;
+                       FormulaCellCacheEntry[] prevArr = _arr;
+                       FormulaCellCacheEntry[] newArr = new FormulaCellCacheEntry[_arr.length / 2]; // shrink 50%
+                       for(int i=0; i<prevArr.length; i++) {
+                               FormulaCellCacheEntry prevCce = _arr[i];
+                               if (prevCce != null) {
+                                       if (prevCce == cce) {
+                                               found=true;
+                                               _size--;
+                                               // skip it
+                                               continue;
+                                       }
+                                       addInternal(newArr, prevCce);
+                               }
+                       }
+                       _arr = newArr;
+                       return found;
+               }
+               // else - usual case
+               // delete single element (without re-hashing)
+
+               int startIx = cce.hashCode() % arr.length;
+
+               // note - can't exit loops upon finding null because of potential previous deletes
+               for(int i=startIx; i<arr.length; i++) {
+                       FormulaCellCacheEntry item = arr[i];
+                       if (item == cce) {
+                               // found it
+                               arr[i] = null;
+                               _size--;
+                               return true;
+                       }
+               }
+               for(int i=0; i<startIx; i++) {
+                       FormulaCellCacheEntry item = arr[i];
+                       if (item == cce) {
+                               // found it
+                               arr[i] = null;
+                               _size--;
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaUsedBlankCellSet.java b/src/java/org/apache/poi/ss/formula/FormulaUsedBlankCellSet.java
new file mode 100644 (file)
index 0000000..7447edc
--- /dev/null
@@ -0,0 +1,189 @@
+/* ====================================================================
+   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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.hssf.util.CellReference;
+
+/**
+ * Optimisation - compacts many blank cell references used by a single formula. 
+ * 
+ * @author Josh Micich
+ */
+final class FormulaUsedBlankCellSet {
+       public static final class BookSheetKey {
+
+               private final int _bookIndex;
+               private final int _sheetIndex;
+
+               public BookSheetKey(int bookIndex, int sheetIndex) {
+                       _bookIndex = bookIndex;
+                       _sheetIndex = sheetIndex;
+               }
+               public int hashCode() {
+                       return _bookIndex * 17 + _sheetIndex;
+               }
+               public boolean equals(Object obj) {
+                       BookSheetKey other = (BookSheetKey) obj;
+                       return _bookIndex == other._bookIndex && _sheetIndex == other._sheetIndex;
+               }
+       }
+
+       private static final class BlankCellSheetGroup {
+               private final List _rectangleGroups;
+               private int _currentRowIndex;
+               private int _firstColumnIndex;
+               private int _lastColumnIndex;
+               private BlankCellRectangleGroup _currentRectangleGroup;
+
+               public BlankCellSheetGroup() {
+                       _rectangleGroups = new ArrayList();
+                       _currentRowIndex = -1;
+               }
+
+               public void addCell(int rowIndex, int columnIndex) {
+                       if (_currentRowIndex == -1) {
+                               _currentRowIndex = rowIndex;
+                               _firstColumnIndex = columnIndex;
+                               _lastColumnIndex = columnIndex;
+                       } else {
+                               if (_currentRowIndex == rowIndex && _lastColumnIndex+1 == columnIndex) {
+                                       _lastColumnIndex = columnIndex;
+                               } else {
+                                       // cell does not fit on end of current row
+                                       if (_currentRectangleGroup == null) {
+                                               _currentRectangleGroup = new BlankCellRectangleGroup(_currentRowIndex, _firstColumnIndex, _lastColumnIndex);
+                                       } else {
+                                               if (!_currentRectangleGroup.acceptRow(_currentRowIndex, _firstColumnIndex, _lastColumnIndex)) {
+                                                       _rectangleGroups.add(_currentRectangleGroup);
+                                                       _currentRectangleGroup = new BlankCellRectangleGroup(_currentRowIndex, _firstColumnIndex, _lastColumnIndex);
+                                               }
+                                       }
+                                       _currentRowIndex = rowIndex;
+                                       _firstColumnIndex = columnIndex;
+                                       _lastColumnIndex = columnIndex;
+                               }
+                       }
+               }
+
+               public boolean containsCell(int rowIndex, int columnIndex) {
+                       for (int i=_rectangleGroups.size()-1; i>=0; i--) {
+                               BlankCellRectangleGroup bcrg = (BlankCellRectangleGroup) _rectangleGroups.get(i);
+                               if (bcrg.containsCell(rowIndex, columnIndex)) {
+                                       return true;
+                               }
+                       }
+                       if(_currentRectangleGroup != null && _currentRectangleGroup.containsCell(rowIndex, columnIndex)) {
+                               return true;
+                       }
+                       if (_currentRowIndex != -1 && _currentRowIndex == rowIndex) {
+                               if (_firstColumnIndex <=  columnIndex && columnIndex <= _lastColumnIndex) {
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+       }
+
+       private static final class BlankCellRectangleGroup {
+
+               private final int _firstRowIndex;
+               private final int _firstColumnIndex;
+               private final int _lastColumnIndex;
+               private int _lastRowIndex;
+
+               public BlankCellRectangleGroup(int firstRowIndex, int firstColumnIndex, int lastColumnIndex) {
+                       _firstRowIndex = firstRowIndex;
+                       _firstColumnIndex = firstColumnIndex;
+                       _lastColumnIndex = lastColumnIndex;
+                       _lastRowIndex = firstRowIndex;
+               }
+
+               public boolean containsCell(int rowIndex, int columnIndex) {
+                       if (columnIndex < _firstColumnIndex) {
+                               return false;
+                       }
+                       if (columnIndex > _lastColumnIndex) {
+                               return false;
+                       }
+                       if (rowIndex < _firstRowIndex) {
+                               return false;
+                       }
+                       if (rowIndex > _lastRowIndex) {
+                               return false;
+                       }
+                       return true;
+               }
+
+               public boolean acceptRow(int rowIndex, int firstColumnIndex, int lastColumnIndex) {
+                       if (firstColumnIndex != _firstColumnIndex) {
+                               return false;
+                       }
+                       if (lastColumnIndex != _lastColumnIndex) {
+                               return false;
+                       }
+                       if (rowIndex != _lastRowIndex+1) {
+                               return false;
+                       }
+                       _lastRowIndex = rowIndex;
+                       return true;
+               }
+               public String toString() {
+                       StringBuffer sb = new StringBuffer(64);
+                       CellReference crA = new CellReference(_firstRowIndex, _firstColumnIndex, false, false);
+                       CellReference crB = new CellReference(_lastRowIndex, _lastColumnIndex, false, false);
+                       sb.append(getClass().getName());
+                       sb.append(" [").append(crA.formatAsString()).append(':').append(crB.formatAsString()).append("]");
+                       return sb.toString();
+               }
+       }
+
+       private final Map _sheetGroupsByBookSheet;
+
+       public FormulaUsedBlankCellSet() {
+               _sheetGroupsByBookSheet = new HashMap();
+       }
+
+       public void addCell(int bookIndex, int sheetIndex, int rowIndex, int columnIndex) {
+               BlankCellSheetGroup sbcg = getSheetGroup(bookIndex, sheetIndex);
+               sbcg.addCell(rowIndex, columnIndex);
+       }
+
+       private BlankCellSheetGroup getSheetGroup(int bookIndex, int sheetIndex) {
+               BookSheetKey key = new BookSheetKey(bookIndex, sheetIndex);
+
+               BlankCellSheetGroup result = (BlankCellSheetGroup) _sheetGroupsByBookSheet.get(key);
+               if (result == null) {
+                       result = new BlankCellSheetGroup();
+                       _sheetGroupsByBookSheet.put(key, result);
+               }
+               return result;
+       }
+
+       public boolean containsCell(BookSheetKey key, int rowIndex, int columnIndex) {
+               BlankCellSheetGroup bcsg = (BlankCellSheetGroup) _sheetGroupsByBookSheet.get(key);
+               if (bcsg == null) {
+                       return false;
+               }
+               return bcsg.containsCell(rowIndex, columnIndex);
+       }
+}
index deba4d59b00301bd15e1dbc84191db349bb36316..caf254edff2a1d545e63e8e96d54e39906b9f63e 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
 
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFCell;
 
 /**
  * Tests can implement this class to track the internal working of the {@link WorkbookEvaluator}.<br/>
@@ -28,12 +29,26 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
  * @author Josh Micich
  */
 interface IEvaluationListener {
+       /**
+        * A (mostly) opaque interface to allow test clients to trace cache values
+        * Each spreadsheet cell gets one unique cache entry instance.  These objects
+        * are safe to use as keys in {@link java.util.HashMap}s 
+        */
+       interface ICacheEntry {
+               ValueEval getValue();
+       }
 
        void onCacheHit(int sheetIndex, int rowIndex, int columnIndex, ValueEval result);
-       void onReadPlainValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value);
-       void onStartEvaluate(int sheetIndex, int rowIndex, int columnIndex, Ptg[] ptgs);
-       void onEndEvaluate(int sheetIndex, int rowIndex, int columnIndex, ValueEval result);
+       void onReadPlainValue(int sheetIndex, int rowIndex, int columnIndex, ICacheEntry entry);
+       void onStartEvaluate(EvaluationCell cell, ICacheEntry entry, Ptg[] ptgs);
+       void onEndEvaluate(ICacheEntry entry, ValueEval result);
        void onClearWholeCache();
-       void onClearCachedValue(int sheetIndex, int rowIndex, int columnIndex, ValueEval value);
-       void onClearDependentCachedValue(int sheetIndex, int rowIndex, int columnIndex, ValueEval value, int depth);
+       void onClearCachedValue(ICacheEntry entry);
+       /**
+        * Internally, formula {@link ICacheEntry}s are stored in sets which may change ordering due 
+        * to seemingly trivial changes.  This method is provided to make the order of call-backs to 
+        * {@link #onClearDependentCachedValue(ICacheEntry, int)} more deterministic.
+        */
+       void sortDependentCachedValues(ICacheEntry[] formulaCells);
+       void onClearDependentCachedValue(ICacheEntry formulaCell, int depth);
 }
diff --git a/src/java/org/apache/poi/ss/formula/PlainCellCache.java b/src/java/org/apache/poi/ss/formula/PlainCellCache.java
new file mode 100644 (file)
index 0000000..2e6476f
--- /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 java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author Josh Micich
+ */
+final class PlainCellCache {
+
+       public static final class Loc {
+
+               private final int _bookSheetColumn;
+
+               private final int _rowIndex;
+
+               public Loc(int bookIndex, int sheetIndex, int rowIndex, int columnIndex) {
+                       _bookSheetColumn = toBookSheetColumn(bookIndex, sheetIndex, columnIndex);
+                       _rowIndex = rowIndex;
+               }
+
+               public static int toBookSheetColumn(int bookIndex, int sheetIndex, int columnIndex) {
+                       return ((bookIndex & 0x00FF) << 24) + ((sheetIndex & 0x00FF) << 16)
+                                       + ((columnIndex & 0xFFFF) << 0);
+               }
+
+               public Loc(int bookSheetColumn, int rowIndex) {
+                       _bookSheetColumn = bookSheetColumn;
+                       _rowIndex = rowIndex;
+               }
+
+               public int hashCode() {
+                       return _bookSheetColumn + 17 * _rowIndex;
+               }
+
+               public boolean equals(Object obj) {
+                       Loc other = (Loc) obj;
+                       return _bookSheetColumn == other._bookSheetColumn && _rowIndex == other._rowIndex;
+               }
+
+               public int getRowIndex() {
+                       return _rowIndex;
+               }
+               public int getColumnIndex() {
+                       return _bookSheetColumn & 0x000FFFF;
+               }
+       }
+
+       private Map _plainValueEntriesByLoc;
+
+       public PlainCellCache() {
+               _plainValueEntriesByLoc = new HashMap();
+       }
+       public void put(Loc key, PlainValueCellCacheEntry cce) {
+               _plainValueEntriesByLoc.put(key, cce);
+       }
+       public void clear() {
+               _plainValueEntriesByLoc.clear();
+       }
+       public PlainValueCellCacheEntry get(Loc key) {
+               return (PlainValueCellCacheEntry) _plainValueEntriesByLoc.get(key);
+       }
+       public void remove(Loc key) {
+               _plainValueEntriesByLoc.remove(key);
+       }
+}
diff --git a/src/java/org/apache/poi/ss/formula/PlainValueCellCacheEntry.java b/src/java/org/apache/poi/ss/formula/PlainValueCellCacheEntry.java
new file mode 100644 (file)
index 0000000..d1ffb51
--- /dev/null
@@ -0,0 +1,31 @@
+/* ====================================================================
+   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.record.formula.eval.ValueEval;
+
+/**
+ * Used for non-formula cells, primarily to keep track of the referencing (formula) cells.
+ * 
+ * @author Josh Micich
+ */
+final class PlainValueCellCacheEntry extends CellCacheEntry {
+       public PlainValueCellCacheEntry(ValueEval value) {
+               updateValue(value);
+       }
+}
\ No newline at end of file
index 6babc272ccb88d2fda72cc12e7dec1f7ecfd7f2e..bf636a4da3e3f90e4c5ab0741a1d27f48447bae1 100644 (file)
@@ -18,7 +18,6 @@
 package org.apache.poi.ss.formula;\r
 \r
 import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
-import org.apache.poi.hssf.usermodel.HSSFSheet;\r
 /**\r
  * \r
  * \r
@@ -28,7 +27,7 @@ final class SheetRefEvaluator {
 \r
        private final WorkbookEvaluator _bookEvaluator;\r
        private final EvaluationTracker _tracker;\r
-       private final HSSFSheet _sheet;\r
+       private final EvaluationSheet _sheet;\r
        private final int _sheetIndex;\r
 \r
        public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker,\r
index e80de22fc03f63df24774723b4e61a7630623dd5..3590bfa82976974b26f225ea1f41743b7f13a9fd 100644 (file)
@@ -61,8 +61,6 @@ 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;
 import org.apache.poi.hssf.usermodel.HSSFCell;
-import org.apache.poi.hssf.usermodel.HSSFRow;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.util.CellReference;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
 
@@ -131,7 +129,7 @@ public final class WorkbookEvaluator {
        /* package */ IEvaluationListener getEvaluationListener() {
                return _evaluationListener;
        }
-        
+
        /**
         * Should be called whenever there are changes to input cells in the evaluated workbook.
         * Failure to call this method after changing cell values will cause incorrect behaviour
@@ -143,29 +141,23 @@ public final class WorkbookEvaluator {
        }
 
        /**
-        * Sets the cached value for a plain (non-formula) cell.
-        * @param never <code>null</code>. Use {@link BlankEval#INSTANCE} when the cell is being 
-        * cleared. Otherwise an instance of {@link NumberEval}, {@link StringEval}, {@link BoolEval}
-        * or {@link ErrorEval} to represent a plain cell value.
+        * Should be called to tell the cell value cache that the specified (value or formula) cell 
+        * has changed.
         */
-       public void setCachedPlainValue(HSSFSheet sheet, int rowIndex, int columnIndex, ValueEval value) {
-               if (value == null) {
-                       throw new IllegalArgumentException("value must not be null");
-               }
-               int sheetIndex = getSheetIndex(sheet);
-               _cache.setValue(getCellLoc(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
-
+       public void notifyUpdateCell(EvaluationCell cell) {
+               int sheetIndex = getSheetIndex(cell.getSheet());
+               _cache.notifyUpdateCell(_workbookIx, sheetIndex, cell);
        }
        /**
-        * Should be called to tell the cell value cache that the specified cell has just become a
-        * formula cell, or the formula text has changed 
+        * Should be called to tell the cell value cache that the specified cell has just been
+        * deleted. 
         */
-       public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {
-               int sheetIndex = getSheetIndex(sheet);
-               _cache.setValue(getCellLoc(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
-
+       public void notifyDeleteCell(EvaluationCell cell) {
+               int sheetIndex = getSheetIndex(cell.getSheet());
+               _cache.notifyDeleteCell(_workbookIx, sheetIndex, cell);
        }
-       private int getSheetIndex(HSSFSheet sheet) {
+
+       private int getSheetIndex(EvaluationSheet sheet) {
                Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
                if (result == null) {
                        int sheetIndex = _workbook.getSheetIndex(sheet);
@@ -178,78 +170,76 @@ public final class WorkbookEvaluator {
                return result.intValue();
        }
 
-       public ValueEval evaluate(HSSFCell srcCell) {
+       public ValueEval evaluate(EvaluationCell srcCell) {
                int sheetIndex = getSheetIndex(srcCell.getSheet());
-               CellLocation cellLoc = getCellLoc(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
-               return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
+               return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache));
        }
 
+
        /**
         * @return never <code>null</code>, never {@link BlankEval}
         */
-       private ValueEval internalEvaluate(HSSFCell srcCell, CellLocation cellLoc, EvaluationTracker tracker) {
-               int sheetIndex = cellLoc.getSheetIndex();
-               int rowIndex = cellLoc.getRowIndex();
-               int columnIndex = cellLoc.getColumnIndex();
+       private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex,
+                               int rowIndex, int columnIndex, EvaluationTracker tracker) {
 
-               ValueEval result;
+               if (srcCell == null || srcCell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
+                       ValueEval result = getValueFromNonFormulaCell(srcCell);
+                       tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
+                       return result;
+               }
 
-               result = tracker.startEvaluate(cellLoc);
+               FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell);
+               tracker.acceptFormulaDependency(cce);
                IEvaluationListener evalListener = _evaluationListener;
-               if (result != null) {
-                       if(evalListener != null) {
-                               evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, result);
+               if (cce.getValue() == null) {
+                       if (!tracker.startEvaluate(cce)) {
+                               return ErrorEval.CIRCULAR_REF_ERROR;
                        }
-                       return result;
-               }
 
-               boolean isPlainFormulaCell = false;
-               try {
-                       result = getValueFromNonFormulaCell(srcCell);
-                       if (result != null) {
-                               isPlainFormulaCell = true;
-                               if(evalListener != null) {
-                                       evalListener.onReadPlainValue(sheetIndex, rowIndex, columnIndex, result);
-                               }
-                       } else {
-                               isPlainFormulaCell = false;
+                       try {
+                               ValueEval result;
+
                                Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
-                               if(evalListener == null) {
-                                       result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
+                               if (evalListener == null) {
+                                       result = evaluateFormula(sheetIndex, rowIndex, columnIndex, ptgs, tracker);
                                } else {
-                                       evalListener.onStartEvaluate(sheetIndex, rowIndex, columnIndex, ptgs);
-                                       result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
-                                       evalListener.onEndEvaluate(sheetIndex, rowIndex, columnIndex, result);
+                                       evalListener.onStartEvaluate(srcCell, cce, ptgs);
+                                       result = evaluateFormula(sheetIndex, rowIndex, columnIndex, ptgs, tracker);
+                                       evalListener.onEndEvaluate(cce, result);
                                }
+
+                               tracker.updateCacheResult(result);
+                       } finally {
+                               tracker.endEvaluate(cce);
+                       }
+               } else {
+                       if(evalListener != null) {
+                               evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, cce.getValue());
                        }
-               } finally {
-                       tracker.endEvaluate(cellLoc, result, isPlainFormulaCell);
+                       return cce.getValue();
                }
                if (isDebugLogEnabled()) {
                        String sheetName = getSheetName(sheetIndex);
                        CellReference cr = new CellReference(rowIndex, columnIndex);
-                       logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
+                       logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + cce.getValue().toString());
                }
-               return result;
+               return cce.getValue();
        }
        /**
         * Gets the value from a non-formula cell.
         * @param cell may be <code>null</code>
-        * @return {@link BlankEval} if cell is <code>null</code> or blank, <code>null</code> if cell 
-        * is a formula cell.
+        * @return {@link BlankEval} if cell is <code>null</code> or blank, never <code>null</code>
         */
-       private static ValueEval getValueFromNonFormulaCell(HSSFCell cell) {
+       /* package */ static ValueEval getValueFromNonFormulaCell(EvaluationCell cell) {
                if (cell == null) {
                        return BlankEval.INSTANCE;
                }
                int cellType = cell.getCellType();
                switch (cellType) {
-                       case HSSFCell.CELL_TYPE_FORMULA:
-                               return null;
                        case HSSFCell.CELL_TYPE_NUMERIC:
                                return new NumberEval(cell.getNumericCellValue());
                        case HSSFCell.CELL_TYPE_STRING:
-                               return new StringEval(cell.getRichStringCellValue().getString());
+                               return new StringEval(cell.getStringCellValue());
                        case HSSFCell.CELL_TYPE_BOOLEAN:
                                return BoolEval.valueOf(cell.getBooleanCellValue());
                        case HSSFCell.CELL_TYPE_BLANK:
@@ -273,7 +263,7 @@ public final class WorkbookEvaluator {
                                        // Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator
                                        // expects the equivalent function token
                                        byte nArgs = 1;  // tAttrSum always has 1 parameter
-                                       ptg = new FuncVarPtg("SUM", nArgs); 
+                                       ptg = new FuncVarPtg("SUM", nArgs);
                                }
                        }
                        if (ptg instanceof ControlPtg) {
@@ -381,7 +371,7 @@ public final class WorkbookEvaluator {
                }
                int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
                return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
-               
+
        }
 
        /**
@@ -428,7 +418,7 @@ public final class WorkbookEvaluator {
                if (ptg instanceof MissingArgPtg) {
                        return MissingArgEval.instance;
                }
-               if (ptg instanceof AreaErrPtg ||ptg instanceof RefErrorPtg 
+               if (ptg instanceof AreaErrPtg ||ptg instanceof RefErrorPtg
                                || ptg instanceof DeletedArea3DPtg || ptg instanceof DeletedRef3DPtg) {
                                return ErrorEval.REF_INVALID;
                }
@@ -465,25 +455,14 @@ public final class WorkbookEvaluator {
                }
                return getEvalForPtg(ptgs[0], sheetIndex, tracker);
        }
-       
+
        /**
         * Used by the lazy ref evals whenever they need to get the value of a contained cell.
         */
-       /* package */ ValueEval evaluateReference(HSSFSheet sheet, int sheetIndex, int rowIndex,
+       /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex,
                        int columnIndex, EvaluationTracker tracker) {
-               
-               HSSFRow row = sheet.getRow(rowIndex);
-               HSSFCell cell;
-               if (row == null) {
-                       cell = null;
-               } else {
-                       cell = row.getCell(columnIndex);
-               }
-               CellLocation cellLoc = getCellLoc(sheetIndex, rowIndex, columnIndex);
-               tracker.acceptDependency(cellLoc);
-               return internalEvaluate(cell, cellLoc, tracker);
-       }
-       private CellLocation getCellLoc(int sheetIndex, int rowIndex, int columnIndex) {
-               return new CellLocation(_workbookIx, sheetIndex, rowIndex, columnIndex);
+
+               EvaluationCell cell = sheet.getCell(rowIndex, columnIndex);
+               return evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker);
        }
 }
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/HSSFEvaluationTestHelper.java b/src/testcases/org/apache/poi/hssf/usermodel/HSSFEvaluationTestHelper.java
new file mode 100644 (file)
index 0000000..96196c1
--- /dev/null
@@ -0,0 +1,33 @@
+/* ====================================================================
+   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.usermodel;
+
+import org.apache.poi.ss.formula.EvaluationCell;
+
+/**
+ * Raises visibility of some internal functionality for test purposes 
+ * 
+ * @author Josh Micich
+ */
+public final class HSSFEvaluationTestHelper {
+
+       public static EvaluationCell wrapCell(HSSFCell cell) {
+               return new HSSFEvaluationCell(cell);
+       }
+
+}
index baa72743d04fa2b826c093ecdde90d8277a0776f..ce57c28594f18a84c77a532d96c217df46f35758 100644 (file)
@@ -32,6 +32,7 @@ import org.apache.poi.hssf.record.formula.AreaPtg;
 import org.apache.poi.hssf.record.formula.FuncVarPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.EvaluationCell;
 import org.apache.poi.ss.formula.EvaluationListener;
 import org.apache.poi.ss.formula.WorkbookEvaluator;
 import org.apache.poi.ss.formula.WorkbookEvaluatorTestHelper;
@@ -168,6 +169,12 @@ public final class TestFormulaEvaluatorBugs extends TestCase {
                assertEquals("-1000000.0-3000000.0", cell.getCellFormula());
                assertEquals(-4000000, eva.evaluate(cell).getNumberValue(), 0);
        }
+//     public static void main(String[] args) {
+//             new TestFormulaEvaluatorBugs().test44410();
+//             new TestFormulaEvaluatorBugs().testSlowEvaluate45376();
+//             new HSSFWorkbook();
+//             System.out.println("done");
+//     }
 
        /**
         * Bug 44410: SUM(C:C) is valid in excel, and means a sum
@@ -309,7 +316,7 @@ public final class TestFormulaEvaluatorBugs extends TestCase {
                public void onCacheHit(int sheetIndex, int srcRowNum, int srcColNum, ValueEval result) {
                        _countCacheHits++;
                }
-               public void onStartEvaluate(int sheetIndex, int rowIndex, int columnIndex, Ptg[] ptgs) {
+               public void onStartEvaluate(EvaluationCell cell, ICacheEntry entry, Ptg[] ptgs) {
                        _countCacheMisses++;
                }
        }
@@ -341,7 +348,7 @@ public final class TestFormulaEvaluatorBugs extends TestCase {
                HSSFCell cell = row.getCell(8);
                EvalListener evalListener = new EvalListener();
                WorkbookEvaluator evaluator = WorkbookEvaluatorTestHelper.createEvaluator(wb, evalListener);
-               evaluator.evaluate(cell);
+               evaluator.evaluate(HSSFEvaluationTestHelper.wrapCell(cell));
                int evalCount = evalListener.getCountCacheMisses();
                if (evalCount > 10) {
                        // Without caching, evaluating cell 'A9' takes 21845 evaluations which consumes
index e6ae025a85c8d102d5a719502a6bb1c5bb7c722e..46bb0617220061c845b939e48ce976eca2e148ed 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
 
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFCell;
 
 /**
  * Tests should extend this class if they need to track the internal working of the {@link WorkbookEvaluator}.<br/>
@@ -28,26 +29,28 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
  * @author Josh Micich
  */
 public abstract class EvaluationListener implements IEvaluationListener {
-       public void onCacheHit(int sheetIndex, int rowIndex, int srcColNum, ValueEval result) {
+       public void onCacheHit(int sheetIndex, int rowIndex, int columnIndex, ValueEval result) {
                // do nothing 
        }
-       public void onReadPlainValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value) {
+       public void onReadPlainValue(int sheetIndex, int rowIndex, int columnIndex, ICacheEntry entry) {
                // do nothing 
        }
-       public void onStartEvaluate(int sheetIndex, int rowIndex, int columnIndex, Ptg[] ptgs) {
+       public void onStartEvaluate(EvaluationCell cell, ICacheEntry entry, Ptg[] ptgs) {
                // do nothing 
        }
-       public void onEndEvaluate(int sheetIndex, int rowIndex, int columnIndex, ValueEval result) {
+       public void onEndEvaluate(ICacheEntry entry, ValueEval result) {
                // do nothing 
        }
        public void onClearWholeCache() {
                // do nothing 
        }
-       public void onClearCachedValue(int sheetIndex, int rowIndex, int columnIndex, ValueEval value) {
+       public void onClearCachedValue(ICacheEntry entry) {
                // do nothing 
        }
-       public void onClearDependentCachedValue(int sheetIndex, int rowIndex, int columnIndex,
-                       ValueEval value,int depth) {
+       public void sortDependentCachedValues(ICacheEntry[] entries) {
+               // do nothing 
+       }
+       public void onClearDependentCachedValue(ICacheEntry entry, int depth) {
                // do nothing 
        }
 }
index e8ff03f8b71631307096f2a3de915fc004e4498a..d5e75dd2abf7df8a411012db8798a6024521b968 100644 (file)
 
 package org.apache.poi.ss.formula;
 
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import junit.framework.TestCase;
+
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
-import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
-
 /**
  * Tests {@link CellCacheEntry}.
  *
@@ -32,22 +30,14 @@ import junit.framework.TestCase;
 public class TestCellCacheEntry extends TestCase {
 
        public void testBasic() {
-               CellCacheEntry cce = new CellCacheEntry();
-               cce.updatePlainValue(new NumberEval(42.0));
-               ValueEval ve = cce.getValue();
+               CellCacheEntry pcce = new PlainValueCellCacheEntry(new NumberEval(42.0));
+               ValueEval ve = pcce.getValue();
                assertEquals(42, ((NumberEval)ve).getNumberValue(), 0.0);
                
-               cce.setFormulaResult(new NumberEval(10.0), new CellLocation[] { });
-       }
-
-       public void testBlank() {
-               CellCacheEntry cce = new CellCacheEntry();
-               cce.updatePlainValue(BlankEval.INSTANCE);
-               try {
-                       cce.updatePlainValue(BlankEval.INSTANCE);
-               } catch (IllegalStateException e) {
-                       // bug was visible around svn r700356
-                       throw new AssertionFailedError("cache entry does not handle blank values properly");
-               }
+               FormulaCellCacheEntry fcce = new FormulaCellCacheEntry();
+               fcce.updateFormulaResult(new NumberEval(10.0), CellCacheEntry.EMPTY_ARRAY, null);
+               
+               ve = fcce.getValue();
+               assertEquals(10, ((NumberEval)ve).getNumberValue(), 0.0);
        }
 }
index bba2ec0d053e02d5442a564a4a3995fe9c19814f..9c95508addbdf2fbe77fb0d80ca7eebf7e73dd2a 100644 (file)
@@ -19,7 +19,11 @@ package org.apache.poi.ss.formula;
 
 import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
@@ -33,10 +37,12 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFEvaluationTestHelper;
 import org.apache.poi.hssf.usermodel.HSSFRow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.ss.formula.PlainCellCache.Loc;
 
 /**
  * Tests {@link EvaluationCache}.  Makes sure that where possible (previously calculated) cached 
@@ -46,33 +52,89 @@ import org.apache.poi.hssf.util.CellReference;
  * @author Josh Micich
  */
 public class TestEvaluationCache extends TestCase {
+       
+       private static final class FormulaCellCacheEntryComparer implements Comparator {
+
+               private final Map _formulaCellsByCacheEntry;
+
+               public FormulaCellCacheEntryComparer(Map formulaCellsByCacheEntry) {
+                       _formulaCellsByCacheEntry = formulaCellsByCacheEntry;
+               }
+               private EvaluationCell getCell(Object a) {
+                       return (EvaluationCell)_formulaCellsByCacheEntry.get(a);
+               }
+
+               public int compare(Object oa, Object ob) {
+                       EvaluationCell a = getCell(oa);
+                       EvaluationCell b = getCell(ob);
+                       int cmp;
+                       cmp = a.getRowIndex() - b.getRowIndex();
+                       if (cmp != 0) {
+                               return cmp;
+                       }
+                       cmp = a.getColumnIndex() - b.getColumnIndex();
+                       if (cmp != 0) {
+                               return cmp;
+                       }
+                       if (a.getSheet() == b.getSheet()) {
+                               return 0;
+                       }
+                       throw new RuntimeException("Incomplete code - don't know how to order sheets");
+               }
+       }
+       
        private static final class EvalListener extends EvaluationListener {
 
                private final List _logList;
                private final HSSFWorkbook _book;
+               private Map _formulaCellsByCacheEntry;
+               private Map _plainCellLocsByCacheEntry;
 
                public EvalListener(HSSFWorkbook wb) {
                        _book = wb;
                        _logList = new ArrayList();
+                       _formulaCellsByCacheEntry = new HashMap();
+                       _plainCellLocsByCacheEntry = new HashMap();
                }
                public void onCacheHit(int sheetIndex, int rowIndex, int columnIndex, ValueEval result) {
                        log("hit", rowIndex, columnIndex, result);
                }
-               public void onReadPlainValue(int sheetIndex, int rowIndex, int columnIndex, ValueEval value) {
-                       log("value", rowIndex, columnIndex, value);
+               public void onReadPlainValue(int sheetIndex, int rowIndex, int columnIndex, ICacheEntry entry) {
+                       Loc loc = new Loc(0, sheetIndex, rowIndex, columnIndex);
+                       _plainCellLocsByCacheEntry.put(entry, loc);
+                       log("value", rowIndex, columnIndex, entry.getValue());
                }
-               public void onStartEvaluate(int sheetIndex, int rowIndex, int columnIndex, Ptg[] ptgs) {
-                       log("start", rowIndex, columnIndex, ptgs);
+               public void onStartEvaluate(EvaluationCell cell, ICacheEntry entry, Ptg[] ptgs) {
+                       _formulaCellsByCacheEntry.put(entry, cell);
+                       log("start", cell.getRowIndex(), cell.getColumnIndex(), ptgs);
                }
-               public void onEndEvaluate(int sheetIndex, int rowIndex, int columnIndex, ValueEval result) {
-                       log("end", rowIndex, columnIndex, result);
+               public void onEndEvaluate(ICacheEntry entry, ValueEval result) {
+                       EvaluationCell cell = (EvaluationCell) _formulaCellsByCacheEntry.get(entry);
+                       log("end", cell.getRowIndex(), cell.getColumnIndex(), result);
                }
-               public void onClearCachedValue(int sheetIndex, int rowIndex, int columnIndex, ValueEval value) {
-                       log("clear", rowIndex, columnIndex, value);
+               public void onClearCachedValue(ICacheEntry entry) {
+                       int rowIndex;
+                       int columnIndex;
+                       EvaluationCell cell = (EvaluationCell) _formulaCellsByCacheEntry.get(entry);
+                       if (cell == null) {
+                               Loc loc = (Loc)_plainCellLocsByCacheEntry.get(entry);
+                               if (loc == null) {
+                                       throw new IllegalStateException("can't find cell or location");
+                               }
+                               rowIndex = loc.getRowIndex();
+                               columnIndex = loc.getColumnIndex();
+                       } else {
+                               rowIndex = cell.getRowIndex();
+                               columnIndex = cell.getColumnIndex();
+                       }
+                       log("clear", rowIndex, columnIndex, entry.getValue());
+               }
+               public void sortDependentCachedValues(ICacheEntry[] entries) {
+                       Arrays.sort(entries, new FormulaCellCacheEntryComparer(_formulaCellsByCacheEntry));
                }
-               public void onClearDependentCachedValue(int sheetIndex, int rowIndex, int columnIndex,
-                               ValueEval value,int depth) {
-                       log("clear" + depth, rowIndex, columnIndex, value);
+               public void onClearDependentCachedValue(ICacheEntry entry, int depth) {
+                       EvaluationCell cell = (EvaluationCell) _formulaCellsByCacheEntry.get(entry);
+                       log("clear" + depth, cell.getRowIndex(), cell.getColumnIndex(),  entry.getValue());
                }
                private void log(String tag, int rowIndex, int columnIndex, Object value) {
                        StringBuffer sb = new StringBuffer(64);
@@ -139,19 +201,23 @@ public class TestEvaluationCache extends TestCase {
                        _sheet = _wb.createSheet("Sheet1");
                }
 
+               private static EvaluationCell wrapCell(HSSFCell cell) {
+                       return HSSFEvaluationTestHelper.wrapCell(cell);
+               }
+
                public void setCellValue(String cellRefText, double value) {
                        HSSFCell cell = getOrCreateCell(cellRefText);
                        // be sure to blank cell, in case it is currently a formula
                        cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
                        // otherwise this line will only set the formula cached result;
                        cell.setCellValue(value);
-                       _evaluator.setCachedPlainValue(_sheet, cell.getRowIndex(), cell.getCellNum(), new NumberEval(value));
+                       _evaluator.notifyUpdateCell(wrapCell(cell));
                }
 
                public void setCellFormula(String cellRefText, String formulaText) {
                        HSSFCell cell = getOrCreateCell(cellRefText);
                        cell.setCellFormula(formulaText);
-                       _evaluator.notifySetFormula(_sheet, cell.getRowIndex(), cell.getCellNum());
+                       _evaluator.notifyUpdateCell(wrapCell(cell));
                }
 
                private HSSFCell getOrCreateCell(String cellRefText) {
@@ -170,7 +236,7 @@ public class TestEvaluationCache extends TestCase {
                }
 
                public ValueEval evaluateCell(String cellRefText) {
-                       return _evaluator.evaluate(getOrCreateCell(cellRefText));
+                       return _evaluator.evaluate(wrapCell(getOrCreateCell(cellRefText)));
                }
 
                public String[] getAndClearLog() {
@@ -243,6 +309,7 @@ public class TestEvaluationCache extends TestCase {
                // change a low level cell
                ms.setCellValue("D1", 10);
                confirmLog(ms, new String[] {
+                               "clear D1 10",
                                "clear1 C1 54",
                                "clear2 B1 8",
                                "clear3 A1 46",
@@ -272,6 +339,7 @@ public class TestEvaluationCache extends TestCase {
 
                ms.setCellValue("B3", 3); // B3 is in the middle of the dependency tree
                confirmLog(ms, new String[] {
+                               "clear B3 3",
                                "clear1 B2 46",
                                "clear2 A1 46",
                });
@@ -296,6 +364,7 @@ public class TestEvaluationCache extends TestCase {
                ms.getAndClearLog();
                ms.setCellFormula("B2", "B3*C2-C3"); // used to be "B3*C1-C2"
                confirmLog(ms, new String[] {
+                       "clear B2 46",
                        "clear1 A1 46",
                });
 
@@ -307,7 +376,8 @@ public class TestEvaluationCache extends TestCase {
                                        "hit B3 2",
                                        "hit C2 62",
                                        "start C3 SUM(D3:E4)",
-                                               "hit D3 16", "hit E3 17", "value D4 #BLANK#", "value E4 #BLANK#",
+                                               "hit D3 16", "hit E3 17", 
+//                                             "value D4 #BLANK#", "value E4 #BLANK#",
                                        "end C3 33",
                                "end B2 91",
                        "end A1 91",
@@ -320,6 +390,7 @@ public class TestEvaluationCache extends TestCase {
                // Now change a value that should no longer affect B2
                ms.setCellValue("D1", 11);
                confirmLog(ms, new String[] {
+                       "clear D1 11",
                        "clear1 C1 54",
                        // note there is no "clear2 B2 91" here because B2 doesn't depend on C1 anymore
                        "clear2 B1 8",
@@ -342,7 +413,7 @@ public class TestEvaluationCache extends TestCase {
 
        /**
         * verifies that when updating a plain cell, depending (formula) cell cached values are cleared
-        * only when the palin cell's value actually changes
+        * only when the plain cell's value actually changes
         */
        public void testRedundantUpdate() {
                MySheet ms = new MySheet();
@@ -369,6 +440,7 @@ public class TestEvaluationCache extends TestCase {
 
                ms.setCellValue("B1", 11); // value changing
                confirmLog(ms, new String[] {
+                       "clear B1 11",
                        "clear1 A1 25", // expect consuming formula cached result to get cleared
                });
                confirmEvaluate(ms, "A1", 24);
@@ -434,9 +506,55 @@ public class TestEvaluationCache extends TestCase {
                        "hit D1 25",
                        "end A1 25",
                });
-
        }
 
+       public void testBlankCells() {
+
+
+               MySheet ms = new MySheet();
+
+               ms.setCellFormula("A1", "sum(B1:D4,B5:E6)");
+               ms.setCellValue("B1", 12);
+               ms.clearAllCachedResultValues();
+               ms.getAndClearLog();
+
+               confirmEvaluate(ms, "A1", 12);
+               confirmLog(ms, new String[] {
+                       "start A1 SUM(B1:D4,B5:E6)",
+                       "value B1 12",
+                       "end A1 12",
+               });
+               ms.setCellValue("B6", 2);
+               ms.getAndClearLog();
+
+               confirmEvaluate(ms, "A1", 14);
+               confirmLog(ms, new String[] {
+                       "start A1 SUM(B1:D4,B5:E6)",
+                       "hit B1 12",
+                       "hit B6 2",
+                       "end A1 14",
+               });
+               ms.setCellValue("E4", 2);
+               ms.getAndClearLog();
+
+               confirmEvaluate(ms, "A1", 14);
+               confirmLog(ms, new String[] {
+                       "hit A1 14",
+               });
+               
+               ms.setCellValue("D1", 1);
+               ms.getAndClearLog();
+
+               confirmEvaluate(ms, "A1", 15);
+               confirmLog(ms, new String[] {
+                       "start A1 SUM(B1:D4,B5:E6)",
+                       "hit B1 12",
+                       "hit D1 1",
+                       "hit B6 2",
+                       "end A1 15",
+               });
+       }
+       
        private static void confirmEvaluate(MySheet ms, String cellRefText, double expectedValue) {
                ValueEval v = ms.evaluateCell(cellRefText);
                assertEquals(NumberEval.class, v.getClass());
index 0d3e93ee7a129e095769bd024e51370a0f072016..47ceb2686fd5e3bde7f2c8b9c6ca7054db8f4207 100644 (file)
@@ -127,18 +127,21 @@ public class TestWorkbookEvaluator extends TestCase {
                confirmEvaluation(264, evaluatorA, cell);
 
                // change [wbB]BSheet1!B3 (from 50 to 60)
-               bSheet1.getRow(2).getCell(1).setCellValue(60);
-               evaluatorB.setCachedPlainValue(bSheet1, 2, 1, new NumberEval(60));
+               HSSFCell cellB3 = bSheet1.getRow(2).getCell(1);
+               cellB3.setCellValue(60);
+               evaluatorB.notifyUpdateCell(cellB3);
                confirmEvaluation(274, evaluatorA, cell);
 
                // change [wbA]ASheet1!A3 (from 100 to 80)
-               aSheet1.getRow(2).getCell(0).setCellValue(80);
-               evaluatorA.setCachedPlainValue(aSheet1, 2, 0, new NumberEval(80));
+               HSSFCell cellA3 = aSheet1.getRow(2).getCell(0);
+               cellA3.setCellValue(80);
+               evaluatorA.notifyUpdateCell(cellA3);
                confirmEvaluation(234, evaluatorA, cell);
 
                // change [wbA]AnotherSheet!A1 (from 2 to 3)
-               wbA.getSheetAt(1).getRow(0).getCell(0).setCellValue(3);
-               evaluatorA.setCachedPlainValue(wbA.getSheetAt(1), 0, 0, new NumberEval(3));
+               HSSFCell cellA1 = wbA.getSheetAt(1).getRow(0).getCell(0);
+               cellA1.setCellValue(3);
+               evaluatorA.notifyUpdateCell(cellA1);
                confirmEvaluation(235, evaluatorA, cell);
        }