You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by jo...@apache.org on 2008/09/23 23:52:50 UTC

svn commit: r698370 - in /poi/branches/ooxml: ./ src/documentation/content/xdocs/ src/java/org/apache/poi/hssf/usermodel/ src/java/org/apache/poi/ss/formula/ src/java/org/apache/poi/ss/usermodel/ src/ooxml/java/org/apache/poi/xssf/usermodel/ src/testca...

Author: josh
Date: Tue Sep 23 14:52:49 2008
New Revision: 698370

URL: http://svn.apache.org/viewvc?rev=698370&view=rev
Log:
Merged revisions 698047 via svnmerge from 
https://svn.apache.org/repos/asf/poi/trunk

........
  r698047 | josh | 2008-09-22 17:40:22 -0700 (Mon, 22 Sep 2008) | 1 line
  
  Optimised the FormulaEvaluator to take cell dependencies into account
........

Added:
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellCacheEntry.java
      - copied unchanged from r698047, poi/trunk/src/java/org/apache/poi/ss/formula/CellCacheEntry.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellLocation.java
      - copied unchanged from r698047, poi/trunk/src/java/org/apache/poi/ss/formula/CellLocation.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/IEvaluationListener.java
      - copied unchanged from r698047, poi/trunk/src/java/org/apache/poi/ss/formula/IEvaluationListener.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
      - copied, changed from r698047, poi/trunk/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
    poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/EvaluationListener.java
      - copied unchanged from r698047, poi/trunk/src/testcases/org/apache/poi/ss/formula/EvaluationListener.java
    poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java
      - copied, changed from r698047, poi/trunk/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java
    poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/WorkbookEvaluatorTestHelper.java
      - copied unchanged from r698047, poi/trunk/src/testcases/org/apache/poi/ss/formula/WorkbookEvaluatorTestHelper.java
Removed:
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellEvaluator.java
Modified:
    poi/branches/ooxml/   (props changed)
    poi/branches/ooxml/src/documentation/content/xdocs/changes.xml
    poi/branches/ooxml/src/documentation/content/xdocs/status.xml
    poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
    poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationCache.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationTracker.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyAreaEval.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyRefEval.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
    poi/branches/ooxml/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
    poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
    poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java
    poi/branches/ooxml/src/testcases/org/apache/poi/hssf/HSSFTests.java
    poi/branches/ooxml/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java
    poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorBugs.java
    poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java

Propchange: poi/branches/ooxml/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Sep 23 14:52:49 2008
@@ -1 +1 @@
-/poi/trunk:693591-694881,695264-695420,695621,695649-697145,697520,697559,697562-698039
+/poi/trunk:693591-694881,695264-695420,695621,695649-698047

Propchange: poi/branches/ooxml/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Tue Sep 23 14:52:49 2008
@@ -1 +1 @@
-/poi/trunk:1-638784,638786-639486,639488-639601,639603-640056,640058-642562,642564-642566,642568-642574,642576-642736,642739-650914,650916-698039
+/poi/trunk:1-638784,638786-639486,639488-639601,639603-640056,640058-642562,642564-642566,642568-642574,642576-642736,642739-650914,650916-698047

Modified: poi/branches/ooxml/src/documentation/content/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/documentation/content/xdocs/changes.xml?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/documentation/content/xdocs/changes.xml (original)
+++ poi/branches/ooxml/src/documentation/content/xdocs/changes.xml Tue Sep 23 14:52:49 2008
@@ -67,6 +67,7 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </release>
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
            <action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
            <action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>
            <action dev="POI-DEVELOPERS" type="add">Include the sheet name in the output of examples.XLS2CSVmra</action>

Modified: poi/branches/ooxml/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/documentation/content/xdocs/status.xml?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/documentation/content/xdocs/status.xml (original)
+++ poi/branches/ooxml/src/documentation/content/xdocs/status.xml Tue Sep 23 14:52:49 2008
@@ -64,6 +64,7 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </release>
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
            <action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
            <action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>
            <action dev="POI-DEVELOPERS" type="add">Include the sheet name in the output of examples.XLS2CSVmra</action>

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java Tue Sep 23 14:52:49 2008
@@ -73,11 +73,9 @@
 	public Sheet getSheet(int sheetIndex) {
 		return _uBook.getSheetAt(sheetIndex);
 	}
-
-	public Sheet getSheetByExternSheetIndex(int externSheetIndex) {
-		int sheetIndex = _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
-		return _uBook.getSheetAt(sheetIndex);
-	}
+	public int convertFromExternSheetIndex(int externSheetIndex) {
+		return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
+}
 
 	public HSSFWorkbook getWorkbook() {
 		return _uBook;

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java Tue Sep 23 14:52:49 2008
@@ -19,6 +19,7 @@
 
 import java.util.Iterator;
 
+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;
@@ -53,14 +54,7 @@
 		}
 	}
 	public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
-   		_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
-	}
-
-	/**
-	 * TODO for debug/test use
-	 */
-	/* package */ int getEvaluationCount() {
-		return _bookEvaluator.getEvaluationCount();
+		_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
 	}
 
 	/**
@@ -84,12 +78,23 @@
 		_bookEvaluator.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.
 	 * 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.
 	 */
-	public void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex) {
-		_bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex);
+	public void setCachedPlainValue(Sheet sheet, int rowIndex, int columnIndex, ValueEval value) {
+		_bookEvaluator.setCachedPlainValue(sheet, rowIndex, columnIndex, value);
+	}
+	/**
+	 * 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 
+	 */
+	public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {
+		_bookEvaluator.notifySetFormula(sheet, rowIndex, columnIndex);
 	}
 
 	/**
@@ -98,7 +103,9 @@
 	 * the cell and also its cell type. This method should be preferred over
 	 * evaluateInCell() when the call should not modify the contents of the
 	 * original cell.
-	 * @param cell
+	 * 
+	 * @param cell may be <code>null</code> signifying that the cell is not present (or blank)
+	 * @return <code>null</code> if the supplied cell is <code>null</code> or blank
 	 */
 	public CellValue evaluate(Cell cell) {
 		if (cell == null) {
@@ -116,6 +123,8 @@
 				return new CellValue(cell.getNumericCellValue());
 			case HSSFCell.CELL_TYPE_STRING:
 				return new CellValue(cell.getRichStringCellValue().getString());
+			case HSSFCell.CELL_TYPE_BLANK:
+				return null;
 		}
 		throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
 	}

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java Tue Sep 23 14:52:49 2008
@@ -17,55 +17,47 @@
 
 package org.apache.poi.ss.formula;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
- * Stores the parameters that identify the evaluation of one cell.<br/>
+ * Stores details about the current evaluation of a cell.<br/>
  */
 final class CellEvaluationFrame {
 
-	private final int _sheetIndex;
-	private final int _srcRowNum;
-	private final int _srcColNum;
-	private final int _hashCode;
-
-	public CellEvaluationFrame(int sheetIndex, int srcRowNum, int srcColNum) {
-		if (sheetIndex < 0) {
-			throw new IllegalArgumentException("sheetIndex must not be negative");
-		}
-		_sheetIndex = sheetIndex;
-		_srcRowNum = srcRowNum;
-		_srcColNum = srcColNum;
-		_hashCode = sheetIndex + 17 * (srcRowNum + 17 * srcColNum);
-	}
+	private final CellLocation _cellLocation;
+	private final Set _usedCells;
 
-	public boolean equals(Object obj) {
-		CellEvaluationFrame other = (CellEvaluationFrame) obj;
-		if (_sheetIndex != other._sheetIndex) {
-			return false;
-		}
-		if (_srcRowNum != other._srcRowNum) {
-			return false;
-		}
-		if (_srcColNum != other._srcColNum) {
-			return false;
-		}
-		return true;
+	public CellEvaluationFrame(CellLocation cellLoc) {
+		_cellLocation = cellLoc;
+		_usedCells = new HashSet();
 	}
-	public int hashCode() {
-		return _hashCode;
-	}
-
-	/**
-	 * @return human readable string for debug purposes
-	 */
-	public String formatAsString() {
-		return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _sheetIndex;
+	public CellLocation getCoordinates() {
+		return _cellLocation;
 	}
 
 	public String toString() {
 		StringBuffer sb = new StringBuffer(64);
 		sb.append(getClass().getName()).append(" [");
-		sb.append(formatAsString());
+		sb.append(_cellLocation.formatAsString());
 		sb.append("]");
 		return sb.toString();
 	}
+	public void addUsedCell(CellLocation coordinates) {
+		_usedCells.add(coordinates);
+	}
+	/**
+	 * @return never <code>null</code>, (possibly empty) array of all cells directly used while 
+	 * evaluating the formula of this frame.  For non-formula cells this will always be an empty
+	 * array;
+	 */
+	public CellLocation[] getUsedCells() {
+		int nItems = _usedCells.size();
+		if (nItems < 1) {
+			return CellLocation.EMPTY_ARRAY;
+		}
+		CellLocation[] result = new CellLocation[nItems];
+		_usedCells.toArray(result);
+		return result;
+	}
 }
\ No newline at end of file

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationCache.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationCache.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationCache.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationCache.java Tue Sep 23 14:52:49 2008
@@ -17,9 +17,13 @@
 
 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.List;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
@@ -34,66 +38,176 @@
  */
 final class EvaluationCache {
 
-	private static final CellEvaluationFrame[] EMPTY_CEF_ARRAY = { };
-	private final Map _valuesByKey;
-	private final Map _consumingCellsByDest;
-
-	/* package */EvaluationCache() {
-		_valuesByKey = new HashMap();
-		_consumingCellsByDest = new HashMap();
+	private final Map _entriesByLocation;
+	private final Map _consumingCellsByUsedCells;
+	/** only used for testing. <code>null</code> otherwise */
+	private final IEvaluationListener _evaluationListener;
+
+	/* package */EvaluationCache(IEvaluationListener evaluationListener) {
+		_evaluationListener = evaluationListener;
+		_entriesByLocation = new HashMap();
+		_consumingCellsByUsedCells = new HashMap();
 	}
 
-	public ValueEval getValue(int sheetIndex, int srcRowNum, int srcColNum) {
-		return getValue(new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum));
+	/**
+	 * @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();
 	}
 
-	/* package */ ValueEval getValue(CellEvaluationFrame key) {
-		return (ValueEval) _valuesByKey.get(key);
+	/**
+	 * @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) {
+
+		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
+			} 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());
+			}
+		}
+		if (_evaluationListener == null) {
+			// optimisation - don't bother sorting if there is no listener.
+		} else {
+			// for testing
+			// make order of callbacks to listener more deterministic
+			Arrays.sort(usedCells, CellLocationComparator);
+		}
+		CellCacheEntry entry = getEntry(cellLoc);
+		CellLocation[] consumingFormulaCells = entry.getConsumingCells();
+		CellLocation[] prevUsedCells = entry.getUsedCells();
+		if (isPlainValue) {
+			
+			if(!entry.updatePlainValue(value)) {
+				return;
+			}
+		} else {
+			entry.setFormulaResult(value, usedCells);
+			for (int i = 0; i < usedCells.length; i++) {
+				getEntry(usedCells[i]).addConsumingCell(cellLoc);
+			}
+		}
+		// 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);
 	}
 
-	public void setValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value) {
-		setValue(new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum), value);
+	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)
+				}
+			}
+		}
+		
 	}
 
-	/* package */ void setValue(CellEvaluationFrame key, ValueEval value) {
-		if (_valuesByKey.containsKey(key)) {
-			throw new RuntimeException("Already have cached value for this cell");
+	/**
+	 * 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) {
+		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);
+				}
+			}
+			if (_evaluationListener != null) {
+				ValueEval value = formulaCell.getValue();
+				_evaluationListener.onClearDependentCachedValue(fc.getSheetIndex(), fc.getRowIndex(), fc.getColumnIndex(), value, nextDepth);
+			}
+			formulaCell.setFormulaResult(null, null);
+			recurseClearCachedFormulaResults(formulaCell.getConsumingCells(), nextDepth);
 		}
-		_valuesByKey.put(key, value);
 	}
 
+	private CellCacheEntry getEntry(CellLocation cellLoc) {
+		CellCacheEntry result = (CellCacheEntry)_entriesByLocation.get(cellLoc);
+		if (result == null) {
+			result = new CellCacheEntry();
+			_entriesByLocation.put(cellLoc, result);
+		}
+		return result;
+	}
 	/**
 	 * Should be called whenever there are changes to input cells in the evaluated workbook.
 	 */
 	public void clear() {
-		_valuesByKey.clear();
+		if(_evaluationListener != null) {
+			_evaluationListener.onClearWholeCache();
+		}
+		_entriesByLocation.clear();
+		_consumingCellsByUsedCells.clear();
 	}
 
-	public void clearValue(int sheetIndex, int rowIndex, int columnIndex) {
-		clearValuesRecursive(new CellEvaluationFrame(sheetIndex, rowIndex, columnIndex));
-		
-	}
 
-	private void clearValuesRecursive(CellEvaluationFrame cef) {
-		CellEvaluationFrame[] consumingCells = getConsumingCells(cef);
-		for (int i = 0; i < consumingCells.length; i++) {
-			clearValuesRecursive(consumingCells[i]);
-		}
-		_valuesByKey.remove(cef);
-		_consumingCellsByDest.remove(cef);
-	}
-
-	private CellEvaluationFrame[] getConsumingCells(CellEvaluationFrame cef) {
-		List temp = (List) _consumingCellsByDest.get(cef);
-		if (temp == null) {
-			return EMPTY_CEF_ARRAY;
-		}
-		int nItems = temp.size();
-		if (temp.size() < 1) {
-			return EMPTY_CEF_ARRAY;
+	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.getSheetIndex() - clB.getSheetIndex();
+			if (cmp != 0) {
+				return cmp;
+			}
+			cmp = clA.getRowIndex() - clB.getRowIndex();
+			if (cmp != 0) {
+				return cmp;
+			}
+			cmp = clA.getColumnIndex() - clB.getColumnIndex();
+			
+			return cmp;
 		}
-		CellEvaluationFrame[] result = new CellEvaluationFrame[nItems];
-		temp.toArray(result);
-		return result;
-	}
+		
+	};
 }

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationTracker.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationTracker.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationTracker.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationTracker.java Tue Sep 23 14:52:49 2008
@@ -18,7 +18,9 @@
 package org.apache.poi.ss.formula;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
@@ -36,11 +38,13 @@
 final class EvaluationTracker {
 
 	private final List _evaluationFrames;
+	private final Set _currentlyEvaluatingCells;
 	private final EvaluationCache _cache;
 
 	public EvaluationTracker(EvaluationCache cache) {
 		_cache = cache;
 		_evaluationFrames = new ArrayList();
+		_currentlyEvaluatingCells = new HashSet();
 	}
 
 	/**
@@ -58,14 +62,17 @@
 	 * @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.
 	 */
-	public ValueEval startEvaluate(int sheetIndex, int srcRowNum, int srcColNum) {
-		CellEvaluationFrame cef = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum);
-		if (_evaluationFrames.contains(cef)) {
+	public ValueEval startEvaluate(CellLocation cellLoc) {
+		if (cellLoc == null) {
+			throw new IllegalArgumentException("cellLoc must not be null");
+		}
+		if (_currentlyEvaluatingCells.contains(cellLoc)) {
 			return ErrorEval.CIRCULAR_REF_ERROR;
 		}
-		ValueEval result = _cache.getValue(cef);
+		ValueEval result = _cache.getValue(cellLoc);
 		if (result == null) {
-			_evaluationFrames.add(cef);
+			_currentlyEvaluatingCells.add(cellLoc);
+			_evaluationFrames.add(new CellEvaluationFrame(cellLoc));
 		}
 		return result;
 	}
@@ -83,24 +90,41 @@
 	 * and form more meaningful error messages.
 	 * @param result 
 	 */
-	public void endEvaluate(int sheetIndex, int srcRowNum, int srcColNum, ValueEval result) {
+	public void endEvaluate(CellLocation cellLoc, ValueEval result, boolean isPlainValueCell) {
+
 		int nFrames = _evaluationFrames.size();
 		if (nFrames < 1) {
 			throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
 		}
 
 		nFrames--;
-		CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames);
-		CellEvaluationFrame cefActual = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum);
-		if (!cefActual.equals(cefExpected)) {
+		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 {"
-					+ cefExpected.formatAsString() + "} this endEvaluate() call is for cell {"
-					+ cefActual.formatAsString() + "}");
+					+ coordinates.formatAsString() + "} this endEvaluate() call is for cell {"
+					+ cellLoc.formatAsString() + "}");
 		}
 		// else - no problems so pop current frame 
 		_evaluationFrames.remove(nFrames);
-		
-		_cache.setValue(cefActual, result);
+		_currentlyEvaluatingCells.remove(coordinates);
+
+		// TODO - don't cache results of volatile formulas 
+		_cache.setValue(coordinates, isPlainValueCell, frame.getUsedCells(), result);
+	}
+	/**
+	 * 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) {
+		int prevFrameIndex = _evaluationFrames.size()-1;
+		if (prevFrameIndex < 0) {
+			throw new IllegalStateException("Call to acceptDependency without prior call to startEvaluate");
+		}
+		CellEvaluationFrame consumingFrame = (CellEvaluationFrame) _evaluationFrames.get(prevFrameIndex);
+		consumingFrame.addUsedCell(usedCell);
 	}
+
 }

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java Tue Sep 23 14:52:49 2008
@@ -35,7 +35,7 @@
 
 	Sheet getSheet(int sheetIndex);
 
-	Sheet getSheetByExternSheetIndex(int externSheetIndex);
+	int convertFromExternSheetIndex(int externSheetIndex);
 	EvaluationName getName(NamePtg namePtg);
 	String resolveNameXText(NameXPtg ptg);
 	Ptg[] getFormulaTokens(Cell cell);

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyAreaEval.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyAreaEval.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyAreaEval.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyAreaEval.java Tue Sep 23 14:52:49 2008
@@ -21,11 +21,7 @@
 import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
 import org.apache.poi.hssf.record.formula.eval.AreaEval;
 import org.apache.poi.hssf.record.formula.eval.AreaEvalBase;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.hssf.util.CellReference;
 
 /**
@@ -34,12 +30,10 @@
  */
 final class LazyAreaEval extends AreaEvalBase {
 
-	private final Sheet _sheet;
-	private final CellEvaluator _evaluator;
+	private final SheetRefEvaluator _evaluator;
 
-	public LazyAreaEval(AreaI ptg, Sheet sheet, CellEvaluator evaluator) {
+	public LazyAreaEval(AreaI ptg, SheetRefEvaluator evaluator) {
 		super(ptg);
-		_sheet = sheet;
 		_evaluator = evaluator;
 	}
 
@@ -48,30 +42,21 @@
 		int rowIx = (relativeRowIndex + getFirstRow() ) & 0xFFFF;
 		int colIx = (relativeColumnIndex + getFirstColumn() ) & 0x00FF;
 		
-		Row row = _sheet.getRow(rowIx);
-		if (row == null) {
-			return BlankEval.INSTANCE;
-		}
-		Cell cell = row.getCell(colIx);
-		if (cell == null) {
-			return BlankEval.INSTANCE;
-		}
-		return _evaluator.getEvalForCell(cell);
+		return _evaluator.getEvalForCell(rowIx, colIx);
 	}
 
 	public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
 		AreaI area = new OffsetArea(getFirstRow(), getFirstColumn(),
 				relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
 
-		return new LazyAreaEval(area, _sheet, _evaluator);
+		return new LazyAreaEval(area, _evaluator);
 	}
 	public String toString() {
 		CellReference crA = new CellReference(getFirstRow(), getFirstColumn());
 		CellReference crB = new CellReference(getLastRow(), getLastColumn());
 		StringBuffer sb = new StringBuffer();
 		sb.append(getClass().getName()).append("[");
-		String sheetName = _evaluator.getSheetName(_sheet);
-		sb.append(sheetName);
+		sb.append(_evaluator.getSheetName());
 		sb.append('!');
 		sb.append(crA.formatAsString());
 		sb.append(':');

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyRefEval.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyRefEval.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyRefEval.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/LazyRefEval.java Tue Sep 23 14:52:49 2008
@@ -22,12 +22,8 @@
 import org.apache.poi.hssf.record.formula.RefPtg;
 import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
 import org.apache.poi.hssf.record.formula.eval.AreaEval;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.RefEvalBase;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.hssf.util.CellReference;
 
 /**
@@ -36,34 +32,19 @@
 */
 final class LazyRefEval extends RefEvalBase {
 
-	private final Sheet _sheet;
-	private final CellEvaluator _evaluator;
+	private final SheetRefEvaluator _evaluator;
 
-
-	public LazyRefEval(RefPtg ptg, Sheet sheet, CellEvaluator evaluator) {
+	public LazyRefEval(RefPtg ptg, SheetRefEvaluator sre) {
 		super(ptg.getRow(), ptg.getColumn());
-		_sheet = sheet;
-		_evaluator = evaluator;
+		_evaluator = sre;
 	}
-	public LazyRefEval(Ref3DPtg ptg, Sheet sheet, CellEvaluator evaluator) {
+	public LazyRefEval(Ref3DPtg ptg, SheetRefEvaluator sre) {
 		super(ptg.getRow(), ptg.getColumn());
-		_sheet = sheet;
-		_evaluator = evaluator;
+		_evaluator = sre;
 	}
 
 	public ValueEval getInnerValueEval() {
-		int rowIx = getRow();
-		int colIx = getColumn();
-		
-		Row row = _sheet.getRow(rowIx);
-		if (row == null) {
-			return BlankEval.INSTANCE;
-		}
-		Cell cell = row.getCell(colIx);
-		if (cell == null) {
-			return BlankEval.INSTANCE;
-		}
-		return _evaluator.getEvalForCell(cell);
+		return _evaluator.getEvalForCell(getRow(), getColumn());
 	}
 	
 	public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
@@ -71,15 +52,14 @@
 		AreaI area = new OffsetArea(getRow(), getColumn(),
 				relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
 
-		return new LazyAreaEval(area, _sheet, _evaluator);
+		return new LazyAreaEval(area, _evaluator);
 	}
 	
 	public String toString() {
 		CellReference cr = new CellReference(getRow(), getColumn());
 		StringBuffer sb = new StringBuffer();
 		sb.append(getClass().getName()).append("[");
-		String sheetName = _evaluator.getSheetName(_sheet);
-		sb.append(sheetName);
+		sb.append(_evaluator.getSheetName());
 		sb.append('!');
 		sb.append(cr.formatAsString());
 		sb.append("]");

Copied: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java (from r698047, poi/trunk/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java)
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java?p2=poi/branches/ooxml/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java&p1=poi/trunk/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java&r1=698047&r2=698370&rev=698370&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java Tue Sep 23 14:52:49 2008
@@ -18,7 +18,7 @@
 package org.apache.poi.ss.formula;
 
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.ss.usermodel.Sheet;
 /**
  * 
  * 
@@ -28,7 +28,7 @@
 
 	private final WorkbookEvaluator _bookEvaluator;
 	private final EvaluationTracker _tracker;
-	private final HSSFSheet _sheet;
+	private final Sheet _sheet;
 	private final int _sheetIndex;
 
 	public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker,

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java Tue Sep 23 14:52:49 2008
@@ -17,6 +17,8 @@
 
 package org.apache.poi.ss.formula;
 
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.Stack;
 
 import org.apache.poi.hssf.record.formula.Area3DPtg;
@@ -52,6 +54,7 @@
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.util.CellReference;
 import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
 
 /**
@@ -67,43 +70,28 @@
  */
 public class WorkbookEvaluator {
 
-	/**
-	 * used to track the number of evaluations
-	 */
-	private static final class Counter {
-		public int value;
-		public int depth;
-		public Counter() {
-			value = 0;
-		}
-	}
-
 	private final EvaluationWorkbook _workbook;
 	private final EvaluationCache _cache;
 
-	private Counter _evaluationCounter;
+	private final IEvaluationListener _evaluationListener;
+	private final Map _sheetIndexesBySheet;
 
 	public WorkbookEvaluator(EvaluationWorkbook workbook) {
+		this (workbook, null);
+	}
+	/* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener) {
 		_workbook = workbook;
-		_cache = new EvaluationCache();
-		_evaluationCounter = new Counter();
+		_evaluationListener = evaluationListener;
+		_cache = new EvaluationCache(evaluationListener);
+		_sheetIndexesBySheet = new IdentityHashMap();
 	}
 
 	/**
-	 * for debug use. Used in toString methods
+	 * also for debug use. Used in toString methods
 	 */
-	/* package */ String getSheetName(Sheet sheet) {
-		return getSheetName(getSheetIndex(sheet));
-	}
-	private String getSheetName(int sheetIndex) {
+	/* package */ String getSheetName(int sheetIndex) {
 		return _workbook.getSheetName(sheetIndex);
 	}
-	/**
-	 * for debug/test use
-	 */
-	public int getEvaluationCount() {
-		return _evaluationCounter.value;
-	}
 
 	private static boolean isDebugLogEnabled() {
 		return false;
@@ -121,56 +109,122 @@
 	 */
 	public void clearAllCachedResultValues() {
 		_cache.clear();
+		_sheetIndexesBySheet.clear();
 	}
 
-	public void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex) {
+	/**
+	 * 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.
+	 */
+	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(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
+
+	}
+	/**
+	 * 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 
+	 */
+	public void notifySetFormula(Sheet sheet, int rowIndex, int columnIndex) {
 		int sheetIndex = getSheetIndex(sheet);
-		_cache.clearValue(sheetIndex, rowIndex, columnIndex);
+		_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
 
 	}
 	private int getSheetIndex(Sheet sheet) {
-		// TODO cache sheet indexes too
-		return _workbook.getSheetIndex(sheet);
+		Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
+		if (result == null) {
+			result = new Integer(_workbook.getSheetIndex(sheet));
+			_sheetIndexesBySheet.put(sheet, result);
+		}
+		return result.intValue();
 	}
 
 	public ValueEval evaluate(Cell srcCell) {
-		return internalEvaluate(srcCell, new EvaluationTracker(_cache));
+		int sheetIndex = getSheetIndex(srcCell.getSheet());
+		CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
+		return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
 	}
 
 	/**
-	 * Dev. Note: Internal evaluate must be passed only a formula cell
-	 * else a runtime exception will be thrown somewhere inside the method.
-	 * (Hence this is a private method.)
 	 * @return never <code>null</code>, never {@link BlankEval}
 	 */
-	/* package */ ValueEval internalEvaluate(Cell srcCell, EvaluationTracker tracker) {
-		int srcRowNum = srcCell.getRowIndex();
-		int srcColNum = srcCell.getCellNum();
+	private ValueEval internalEvaluate(Cell srcCell, CellLocation cellLoc, EvaluationTracker tracker) {
+		int sheetIndex = cellLoc.getSheetIndex();
+		int rowIndex = cellLoc.getRowIndex();
+		int columnIndex = cellLoc.getColumnIndex();
 
 		ValueEval result;
 
-		int sheetIndex = getSheetIndex(srcCell.getSheet());
-		result = tracker.startEvaluate(sheetIndex, srcRowNum, srcColNum);
+		result = tracker.startEvaluate(cellLoc);
+		IEvaluationListener evalListener = _evaluationListener;
 		if (result != null) {
+			if(evalListener != null) {
+				evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, result);
+			}
 			return result;
 		}
-		_evaluationCounter.value++;
-		_evaluationCounter.depth++;
 
+		boolean isPlainFormulaCell = false;
 		try {
-			Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
-			result = evaluateCell(sheetIndex, srcRowNum, (short)srcColNum, ptgs, tracker);
+			result = getValueFromNonFormulaCell(srcCell);
+			if (result != null) {
+				isPlainFormulaCell = true;
+				if(evalListener != null) {
+					evalListener.onReadPlainValue(sheetIndex, rowIndex, columnIndex, result);
+				}
+			} else {
+				isPlainFormulaCell = false;
+				Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
+				if(evalListener == null) {
+					result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
+				} else {
+					evalListener.onStartEvaluate(sheetIndex, rowIndex, columnIndex, ptgs);
+					result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
+					evalListener.onEndEvaluate(sheetIndex, rowIndex, columnIndex, result);
+				}
+			}
 		} finally {
-			tracker.endEvaluate(sheetIndex, srcRowNum, srcColNum, result);
-			_evaluationCounter.depth--;
+			tracker.endEvaluate(cellLoc, result, isPlainFormulaCell);
 		}
 		if (isDebugLogEnabled()) {
 			String sheetName = getSheetName(sheetIndex);
-			CellReference cr = new CellReference(srcRowNum, srcColNum);
+			CellReference cr = new CellReference(rowIndex, columnIndex);
 			logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
 		}
 		return result;
 	}
+	/**
+	 * 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.
+	 */
+	private static ValueEval getValueFromNonFormulaCell(Cell 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());
+			case Cell.CELL_TYPE_BOOLEAN:
+				return BoolEval.valueOf(cell.getBooleanCellValue());
+			case Cell.CELL_TYPE_BLANK:
+				return BlankEval.INSTANCE;
+			case Cell.CELL_TYPE_ERROR:
+				return ErrorEval.valueOf(cell.getErrorCellValue());
+		}
+		throw new RuntimeException("Unexpected cell type (" + cellType + ")");
+	}
 	private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
 
 		Stack stack = new Stack();
@@ -268,10 +322,6 @@
 		return operation.evaluate(ops, srcRowNum, (short)srcColNum);
 	}
 
-	private Sheet getOtherSheet(int externSheetIndex) {
-		return _workbook.getSheetByExternSheetIndex(externSheetIndex);
-	}
-
 	/**
 	 * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
 	 * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,
@@ -311,23 +361,24 @@
 		if (ptg instanceof ErrPtg) {
 			return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode());
 		}
-		CellEvaluator ce = new CellEvaluator(this, tracker);
-		Sheet sheet = _workbook.getSheet(sheetIndex);
-		if (ptg instanceof RefPtg) {
-			return new LazyRefEval(((RefPtg) ptg), sheet, ce);
-		}
-		if (ptg instanceof AreaPtg) {
-			return new LazyAreaEval(((AreaPtg) ptg), sheet, ce);
-		}
 		if (ptg instanceof Ref3DPtg) {
 			Ref3DPtg refPtg = (Ref3DPtg) ptg;
-			Sheet xsheet = getOtherSheet(refPtg.getExternSheetIndex());
-			return new LazyRefEval(refPtg, xsheet, ce);
+			int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex());
+			SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+			return new LazyRefEval(refPtg, sre);
 		}
 		if (ptg instanceof Area3DPtg) {
-			Area3DPtg a3dp = (Area3DPtg) ptg;
-			Sheet xsheet = getOtherSheet(a3dp.getExternSheetIndex());
-			return new LazyAreaEval(a3dp, xsheet, ce);
+			Area3DPtg aptg = (Area3DPtg) ptg;
+			int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex());
+			SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+			return new LazyAreaEval(aptg, sre);
+		}
+		SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
+		if (ptg instanceof RefPtg) {
+			return new LazyRefEval(((RefPtg) ptg), sre);
+		}
+		if (ptg instanceof AreaPtg) {
+			return new LazyAreaEval(((AreaPtg) ptg), sre);
 		}
 
 		if (ptg instanceof UnknownPtg) {
@@ -345,4 +396,22 @@
 		}
 		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,
+			int columnIndex, EvaluationTracker tracker) {
+		
+		Row row = sheet.getRow(rowIndex);
+		Cell cell;
+		if (row == null) {
+			cell = null;
+		} else {
+			cell = row.getCell(columnIndex);
+ 		}
+		CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex);
+		tracker.acceptDependency(cellLoc);
+		return internalEvaluate(cell, cellLoc, tracker);
+	}
 }

Modified: poi/branches/ooxml/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java Tue Sep 23 14:52:49 2008
@@ -17,6 +17,14 @@
 
 package org.apache.poi.ss.usermodel;
 
+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.HSSFSheet;
+
 
 /**
  * Evaluates formula cells.<p/>
@@ -36,7 +44,21 @@
      * of the evaluate~ methods of this class
      */
     void clearAllCachedResultValues();
-    void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex);
+	/**
+	 * 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.
+	 * 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);
+	/**
+	 * 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);
     /**
      * If cell contains a formula, the formula is evaluated and returned,
      * else the CellValue simply copies the appropriate cell value from

Modified: poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java (original)
+++ poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java Tue Sep 23 14:52:49 2008
@@ -37,10 +37,16 @@
 		return externSheetIndex;
 	}
 	/**
-	 * @returns the external sheet index of the sheet with the given internal
-	 *          index, creating one if needed. Used by some of the more obscure
-	 *          formula and named range things. Fairly easy on XSSF (we
-	 *          think...) since the internal and external indicies are the same
+	 * @return the sheet index of the sheet with the given external index.
+	 */
+	public int convertFromExternSheetIndex(int externSheetIndex) {
+		return externSheetIndex;
+	}
+	/**
+	 * @return  the external sheet index of the sheet with the given internal
+	 * index. Used by some of the more obscure formula and named range things. 
+	 * Fairly easy on XSSF (we think...) since the internal and external 
+	 * indicies are the same
 	 */
 	private int convertToExternalSheetIndex(int sheetIndex) {
 		return sheetIndex;
@@ -73,10 +79,6 @@
 		return _uBook.getSheetName(sheetIndex);
 	}
 
-	public int getNameIndex(String name) {
-		return _uBook.getNameIndex(name);
-	}
-
 	public NameXPtg getNameXPtg(String name) {
 		// may require to return null to make tests pass
 		throw new RuntimeException("Not implemented yet");

Modified: poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java (original)
+++ poi/branches/ooxml/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java Tue Sep 23 14:52:49 2008
@@ -19,12 +19,14 @@
 
 import java.util.Iterator;
 
+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.HSSFSheet;
 import org.apache.poi.ss.formula.WorkbookEvaluator;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellValue;
@@ -51,14 +53,6 @@
 	}
 
 	/**
-	 * TODO for debug/test use
-	 */
-	/* package */ int getEvaluationCount() {
-		return _bookEvaluator.getEvaluationCount();
-	}
-
-
-	/**
 	 * Should be called whenever there are major changes (e.g. moving sheets) to input cells
 	 * in the evaluated workbook.
 	 * Failure to call this method after changing cell values will cause incorrect behaviour
@@ -67,13 +61,11 @@
 	public void clearAllCachedResultValues() {
 		_bookEvaluator.clearAllCachedResultValues();
 	}
-	/**
-	 * Should be called whenever there are changes to individual input cells in the evaluated workbook.
-	 * Failure to call this method after changing cell values will cause incorrect behaviour
-	 * of the evaluate~ methods of this class
-	 */
-	public void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex) {
-		_bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex);
+	public void setCachedPlainValue(Sheet sheet, int rowIndex, int columnIndex, ValueEval value) {
+		_bookEvaluator.setCachedPlainValue(sheet, rowIndex, columnIndex, value);
+	}
+	public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {
+		_bookEvaluator.notifySetFormula(sheet, rowIndex, columnIndex);
 	}
 
 	/**

Modified: poi/branches/ooxml/src/testcases/org/apache/poi/hssf/HSSFTests.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/testcases/org/apache/poi/hssf/HSSFTests.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/testcases/org/apache/poi/hssf/HSSFTests.java (original)
+++ poi/branches/ooxml/src/testcases/org/apache/poi/hssf/HSSFTests.java Tue Sep 23 14:52:49 2008
@@ -28,6 +28,7 @@
 import org.apache.poi.hssf.record.AllRecordTests;
 import org.apache.poi.hssf.usermodel.AllUserModelTests;
 import org.apache.poi.hssf.util.AllHSSFUtilTests;
+import org.apache.poi.ss.formula.TestEvaluationCache;
 
 /**
  * Test Suite for all sub-packages of org.apache.poi.hssf<br/>
@@ -52,6 +53,7 @@
         }
         suite.addTest(new TestSuite(TestEventRecordFactory.class));
         suite.addTest(new TestSuite(TestModelFactory.class));
+        suite.addTest(new TestSuite(TestEvaluationCache.class));
         return suite;
     }
 }

Modified: poi/branches/ooxml/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java (original)
+++ poi/branches/ooxml/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java Tue Sep 23 14:52:49 2008
@@ -71,8 +71,9 @@
 		// This formula should evaluate to the contents of B2,
 		testCell.setCellFormula("INDEX(A1:B4,2,2)");
 		// However the range A1:B4 also includes the current cell A4.  If the other parameters
-		// were 4 and 1, this would represent a circular reference.  Since POI 'fully' evaluates
-		// arguments before invoking operators, POI must handle such potential cycles gracefully.
+		// were 4 and 1, this would represent a circular reference.  Prior to v3.2 POI would 
+		// 'fully' evaluate ref arguments before invoking operators, which raised the possibility of
+		// cycles / StackOverflowErrors.
 		
 
 		CellValue cellValue = evaluateWithCycles(wb, testCell);

Modified: poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorBugs.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorBugs.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorBugs.java (original)
+++ poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorBugs.java Tue Sep 23 14:52:49 2008
@@ -31,6 +31,10 @@
 import org.apache.poi.hssf.record.formula.AreaPtg;
 import org.apache.poi.hssf.record.formula.FuncVarPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.EvaluationListener;
+import org.apache.poi.ss.formula.WorkbookEvaluator;
+import org.apache.poi.ss.formula.WorkbookEvaluatorTestHelper;
 
 /**
  * 
@@ -287,6 +291,29 @@
 		}
 	}
 	
+	private static final class EvalListener extends EvaluationListener {
+		private int _countCacheHits;
+		private int _countCacheMisses;
+
+		public EvalListener() {
+			_countCacheHits = 0;
+			_countCacheMisses = 0;
+		}
+		public int getCountCacheHits() {
+			return _countCacheHits;
+		}
+		public int getCountCacheMisses() {
+			return _countCacheMisses;
+		}
+
+		public void onCacheHit(int sheetIndex, int srcRowNum, int srcColNum, ValueEval result) {
+			_countCacheHits++;
+		}
+		public void onStartEvaluate(int sheetIndex, int rowIndex, int columnIndex, Ptg[] ptgs) {
+			_countCacheMisses++;
+		}
+	}
+	
 	/**
 	 * The HSSFFormula evaluator performance benefits greatly from caching of intermediate cell values
 	 */
@@ -312,16 +339,20 @@
 		
 		// Choose cell A9, so that the failing test case doesn't take too long to execute.
 		HSSFCell cell = row.getCell(8);
-		HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);
+		EvalListener evalListener = new EvalListener();
+		WorkbookEvaluator evaluator = WorkbookEvaluatorTestHelper.createEvaluator(wb, evalListener);
 		evaluator.evaluate(cell);
-		int evalCount = evaluator.getEvaluationCount();
-		// With caching, the evaluationCount is 8 which is a big improvement
-		assertTrue(evalCount > 0);  // make sure the counter is actually working
+		int evalCount = evalListener.getCountCacheMisses();
 		if (evalCount > 10) {
 			// Without caching, evaluating cell 'A9' takes 21845 evaluations which consumes
 			// much time (~3 sec on Core 2 Duo 2.2GHz)
 			System.err.println("Cell A9 took " + evalCount + " intermediate evaluations");
 			throw new AssertionFailedError("Identifed bug 45376 - Formula evaluator should cache values");
 		}
+		// With caching, the evaluationCount is 8 which is a big improvement
+		// Note - these expected values may change if the WorkbookEvaluator is 
+		// ever optimised to short circuit 'if' functions.
+		assertEquals(8, evalCount);
+		assertEquals(24, evalListener.getCountCacheHits());
 	}
 }
\ No newline at end of file

Modified: poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java?rev=698370&r1=698369&r2=698370&view=diff
==============================================================================
--- poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java (original)
+++ poi/branches/ooxml/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java Tue Sep 23 14:52:49 2008
@@ -79,4 +79,17 @@
 		}
 		row.createCell(colIndex).setCellValue(value);
 	}
+
+	/**
+	 * {@link HSSFFormulaEvaluator#evaluate(HSSFCell)} should behave the same whether the cell
+	 * is <code>null</code> or blank.
+	 */
+	public void testEvaluateBlank() {
+		HSSFWorkbook wb = new HSSFWorkbook();
+		HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+		assertNull(fe.evaluate(null));
+		HSSFSheet sheet = wb.createSheet("Sheet1");
+		HSSFCell cell = sheet.createRow(0).createCell(0);
+		assertNull(fe.evaluate(cell));
+	}
 }

Copied: poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java (from r698047, poi/trunk/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java)
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java?p2=poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java&p1=poi/trunk/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java&r1=698047&r2=698370&rev=698370&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java (original)
+++ poi/branches/ooxml/src/testcases/org/apache/poi/ss/formula/TestEvaluationCache.java Tue Sep 23 14:52:49 2008
@@ -77,7 +77,7 @@
 		private void log(String tag, int rowIndex, int columnIndex, Object value) {
 			StringBuffer sb = new StringBuffer(64);
 			sb.append(tag).append(' ');
-			sb.append(new CellReference(rowIndex, columnIndex).formatAsString());
+			sb.append(new CellReference(rowIndex, columnIndex, false, false).formatAsString());
 			if (value != null) {
 				sb.append(' ').append(formatValue(value));
 			}



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org