--- /dev/null
+/* ====================================================================
+ 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();
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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
+/* ====================================================================\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
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
-import org.apache.poi.ss.usermodel.Cell;\r
-import org.apache.poi.ss.usermodel.Sheet;\r
\r
/**\r
* Internal POI use only\r
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
return null;\r
}\r
\r
- public int getSheetIndex(Sheet 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
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 Sheet 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
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
int ix = namePtg.getIndex();\r
return new Name(_iBook.getNameRecord(ix), ix);\r
}\r
- public Ptg[] getFormulaTokens(Cell 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
// to make sure that all formulas POI can evaluate can also be parsed.\r
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);\r
}\r
- HSSFCell hCell = (HSSFCell) cell;\r
- FormulaRecord fr = ((FormulaRecordAggregate) hCell.getCellValueRecord()).getFormulaRecord();\r
+ FormulaRecord fr = ((FormulaRecordAggregate) cell.getCellValueRecord()).getFormulaRecord();\r
return fr.getParsedExpression();\r
}\r
\r
_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
\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
import org.apache.poi.ss.formula.WorkbookEvaluator;\r
import org.apache.poi.ss.usermodel.Cell;\r
import org.apache.poi.ss.usermodel.CellValue;\r
-import org.apache.poi.ss.usermodel.Sheet;\r
\r
/**\r
* Evaluates formula cells.<p/>\r
\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
_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(Sheet 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
* @param eval\r
*/\r
private CellValue evaluateFormulaCellValue(Cell cell) {\r
- ValueEval eval = _bookEvaluator.evaluate(cell);\r
+ ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell));\r
if (eval instanceof NumberEval) {\r
NumberEval ne = (NumberEval) eval;\r
return new CellValue(ne.getNumberValue());\r
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;
}
}
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
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
+++ /dev/null
-/* ====================================================================
- 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
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
*/
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.
*/
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);
+ }
}
-
- };
+ }
}
--- /dev/null
+/* ====================================================================
+ 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();
+}
--- /dev/null
+/* ====================================================================
+ 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);
+}
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;
* 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;
/**
* 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) {
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);
+ }
+ }
+ }
}
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.usermodel.Cell;\r
-import org.apache.poi.ss.usermodel.Sheet;\r
+\r
/**\r
* Abstracts a workbook for the purpose of formula evaluation.<br/>\r
* \r
/**\r
* @return -1 if the specified sheet is from a different book\r
*/\r
- int getSheetIndex(Sheet sheet);\r
+ int getSheetIndex(EvaluationSheet sheet);\r
int getSheetIndex(String sheetName);\r
\r
- Sheet 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
int convertFromExternSheetIndex(int externSheetIndex);\r
EvaluationName getName(NamePtg namePtg);\r
String resolveNameXText(NameXPtg ptg);\r
- Ptg[] getFormulaTokens(Cell cell);\r
+ Ptg[] getFormulaTokens(EvaluationCell cell);\r
\r
class ExternalSheet {\r
private final String _workbookName;\r
--- /dev/null
+/* ====================================================================
+ 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());
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
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/>
* @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);
}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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
package org.apache.poi.ss.formula;\r
\r
import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
-import org.apache.poi.ss.usermodel.Sheet;\r
/**\r
* \r
* \r
\r
private final WorkbookEvaluator _bookEvaluator;\r
private final EvaluationTracker _tracker;\r
- private final Sheet _sheet;\r
+ private final EvaluationSheet _sheet;\r
private final int _sheetIndex;\r
\r
public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker,\r
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
/**
* Evaluates formula cells.<p/>
/* 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
}
/**
- * 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(Sheet 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(Sheet 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(Sheet sheet) {
+
+ private int getSheetIndex(EvaluationSheet sheet) {
Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
if (result == null) {
int sheetIndex = _workbook.getSheetIndex(sheet);
return result.intValue();
}
- public ValueEval evaluate(Cell 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(Cell 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() != Cell.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(Cell cell) {
+ /* package */ static ValueEval getValueFromNonFormulaCell(EvaluationCell cell) {
if (cell == null) {
return BlankEval.INSTANCE;
}
int cellType = cell.getCellType();
switch (cellType) {
- case Cell.CELL_TYPE_FORMULA:
- return null;
case Cell.CELL_TYPE_NUMERIC:
return new NumberEval(cell.getNumericCellValue());
case Cell.CELL_TYPE_STRING:
- return new StringEval(cell.getRichStringCellValue().getString());
+ return new StringEval(cell.getStringCellValue());
case Cell.CELL_TYPE_BOOLEAN:
return BoolEval.valueOf(cell.getBooleanCellValue());
case Cell.CELL_TYPE_BLANK:
// 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) {
}
int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
-
+
}
/**
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;
}
}
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(Sheet sheet, int sheetIndex, int rowIndex,
+ /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex,
int columnIndex, EvaluationTracker tracker) {
-
- Row row = sheet.getRow(rowIndex);
- Cell 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);
}
}
*/
void clearAllCachedResultValues();
/**
- * Sets the cached value for a plain (non-formula) cell.
- * Should be called whenever there are changes to individual input cells in the evaluated workbook.
+ * Should be called to tell the cell value cache that the specified (value or formula) cell
+ * has changed.
* Failure to call this method after changing cell values will cause incorrect behaviour
* of the evaluate~ methods of this class
- * @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.
*/
- void setCachedPlainValue(Sheet sheet, int rowIndex, int columnIndex, ValueEval value);
+ void notifySetFormula(Cell 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
*/
- void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex);
+ void notifyDeleteCell(Cell cell);
/**
* If cell contains a formula, the formula is evaluated and returned,
* else the CellValue simply copies the appropriate cell value from
--- /dev/null
+/* ====================================================================
+ 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.xssf.usermodel;
+
+import org.apache.poi.ss.formula.EvaluationCell;
+import org.apache.poi.ss.formula.EvaluationSheet;
+
+/**
+ * XSSF wrapper for a cell under evaluation
+ *
+ * @author Josh Micich
+ */
+final class XSSFEvaluationCell implements EvaluationCell {
+
+ private final EvaluationSheet _evalSheet;
+ private final XSSFCell _cell;
+
+ public XSSFEvaluationCell(XSSFCell cell) {
+ _cell = cell;
+ _evalSheet = new XSSFEvaluationSheet((XSSFSheet)cell.getSheet());
+ }
+
+ public XSSFEvaluationCell(XSSFCell cell, XSSFEvaluationSheet evaluationSheet) {
+ _cell = cell;
+ _evalSheet = evaluationSheet;
+ }
+
+ // Note - hashCode and equals defined according to underlying cell
+ public int hashCode() {
+ return _cell.hashCode();
+ }
+ public boolean equals(Object obj) {
+ return _cell == ((XSSFEvaluationCell)obj)._cell;
+ }
+
+ public XSSFCell getXSSFCell() {
+ 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();
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.xssf.usermodel;
+
+import org.apache.poi.ss.formula.EvaluationCell;
+import org.apache.poi.ss.formula.EvaluationSheet;
+
+/**
+ * XSSF wrapper for a sheet under evaluation
+ *
+ * @author Josh Micich
+ */
+final class XSSFEvaluationSheet implements EvaluationSheet {
+
+ private final XSSFSheet _xs;
+
+ public XSSFEvaluationSheet(XSSFSheet sheet) {
+ _xs = sheet;
+ }
+
+ public XSSFSheet getXSSFSheet() {
+ return _xs;
+ }
+ public EvaluationCell getCell(int rowIndex, int columnIndex) {
+ XSSFRow row = _xs.getRow(rowIndex);
+ if (row == null) {
+ return null;
+ }
+ XSSFCell cell = row.getCell(columnIndex);
+ if (cell == null) {
+ return null;
+ }
+ return new XSSFEvaluationCell(cell, this);
+ }
+}
+/* ====================================================================\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.xssf.usermodel;\r
\r
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.FormulaParser;\r
import org.apache.poi.ss.formula.FormulaParsingWorkbook;\r
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;\r
-import org.apache.poi.ss.usermodel.Cell;\r
-import org.apache.poi.ss.usermodel.Sheet;\r
-import org.apache.poi.ss.usermodel.Workbook;\r
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;\r
\r
/**\r
return null;\r
}\r
\r
- public int getSheetIndex(Sheet sheet) {\r
+ public int getSheetIndex(EvaluationSheet evalSheet) {\r
+ XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet();\r
return _uBook.getSheetIndex(sheet);\r
}\r
\r
throw new RuntimeException("Not implemented yet");\r
}\r
\r
- public Sheet getSheet(int sheetIndex) {\r
- return _uBook.getSheetAt(sheetIndex);\r
+ public EvaluationSheet getSheet(int sheetIndex) {\r
+ return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));\r
}\r
\r
public ExternalSheet getExternalSheet(int externSheetIndex) {\r
return _uBook.getSheetIndex(sheetName);\r
}\r
\r
- public Workbook getWorkbook() {\r
- return _uBook;\r
- }\r
-\r
/**\r
* TODO - figure out what the hell this methods does in\r
* HSSF...\r
int ix = namePtg.getIndex();\r
return new Name(_uBook.getNameAt(ix), ix, this);\r
}\r
- public Ptg[] getFormulaTokens(Cell cell) {\r
+ public Ptg[] getFormulaTokens(EvaluationCell evalCell) {\r
+ XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell();\r
XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook);\r
return FormulaParser.parse(cell.getCellFormula(), frBook);\r
}\r
\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
import org.apache.poi.hssf.record.formula.eval.StringEval;\r
import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
import org.apache.poi.hssf.usermodel.HSSFCell;\r
-import org.apache.poi.hssf.usermodel.HSSFSheet;\r
import org.apache.poi.ss.formula.WorkbookEvaluator;\r
import org.apache.poi.ss.usermodel.Cell;\r
import org.apache.poi.ss.usermodel.CellValue;\r
public void clearAllCachedResultValues() {\r
_bookEvaluator.clearAllCachedResultValues();\r
}\r
- public void setCachedPlainValue(Sheet sheet, int rowIndex, int columnIndex, ValueEval value) {\r
- _bookEvaluator.setCachedPlainValue(sheet, rowIndex, columnIndex, value);\r
+ public void notifySetFormula(Cell cell) {\r
+ _bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell));\r
}\r
- public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {\r
- _bookEvaluator.notifySetFormula(sheet, rowIndex, columnIndex);\r
+ public void notifyDeleteCell(Cell cell) {\r
+ _bookEvaluator.notifyDeleteCell(new XSSFEvaluationCell((XSSFCell)cell));\r
}\r
\r
/**\r
* @param eval\r
*/\r
private CellValue evaluateFormulaCellValue(Cell cell) {\r
- ValueEval eval = _bookEvaluator.evaluate(cell);\r
+ ValueEval eval = _bookEvaluator.evaluate(new XSSFEvaluationCell((XSSFCell) cell));\r
if (eval instanceof NumberEval) {\r
NumberEval ne = (NumberEval) eval;\r
return new CellValue(ne.getNumberValue());\r
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+
+}
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;
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
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++;
}
}
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
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/>
* @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
}
}
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}.
*
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);
}
}
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;
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
* @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);
_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) {
}
public ValueEval evaluateCell(String cellRefText) {
- return _evaluator.evaluate(getOrCreateCell(cellRefText));
+ return _evaluator.evaluate(wrapCell(getOrCreateCell(cellRefText)));
}
public String[] getAndClearLog() {
// 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",
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",
});
ms.getAndClearLog();
ms.setCellFormula("B2", "B3*C2-C3"); // used to be "B3*C1-C2"
confirmLog(ms, new String[] {
+ "clear B2 46",
"clear1 A1 46",
});
"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",
// 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",
/**
* 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();
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);
"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());
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);
}