123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- /* ====================================================================
- 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.HashSet;
- import java.util.List;
- import java.util.Set;
-
- import org.apache.poi.hssf.record.formula.eval.BlankEval;
- import org.apache.poi.hssf.record.formula.eval.ErrorEval;
- import org.apache.poi.hssf.record.formula.eval.ValueEval;
- import org.apache.poi.hssf.usermodel.HSSFCell;
-
- /**
- * Instances of this class keep track of multiple dependent cell evaluations due
- * to recursive calls to {@link WorkbookEvaluator#evaluate(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<CellEvaluationFrame> _evaluationFrames;
- private final Set<FormulaCellCacheEntry> _currentlyEvaluatingCells;
- private final EvaluationCache _cache;
-
- public EvaluationTracker(EvaluationCache cache) {
- _cache = cache;
- _evaluationFrames = new ArrayList<CellEvaluationFrame>();
- _currentlyEvaluatingCells = new HashSet<FormulaCellCacheEntry>();
- }
-
- /**
- * 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>null</code> return code, the caller should
- * return an evaluation result of
- * <tt>ErrorEval.CIRCULAR_REF_ERROR<tt>, and not call <tt>endEvaluate()</tt>.
- * <br/>
- * @return <code>false</code> if the specified cell is already being evaluated
- */
- public boolean startEvaluate(FormulaCellCacheEntry cce) {
- if (cce == null) {
- throw new IllegalArgumentException("cellLoc must not be null");
- }
- if (_currentlyEvaluatingCells.contains(cce)) {
- return false;
- }
- _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");
- }
- CellEvaluationFrame frame = _evaluationFrames.get(nFrames-1);
- if (result == ErrorEval.CIRCULAR_REF_ERROR && nFrames > 1) {
- // Don't cache a circular ref error result if this cell is not the top evaluated cell.
- // A true circular ref error will propagate all the way around the loop. However, it's
- // possible to have parts of the formula tree (/ parts of the loop) to evaluate to
- // CIRCULAR_REF_ERROR, and that value not get used in the final cell result (see the
- // unit tests for a simple example). Thus, the only CIRCULAR_REF_ERROR result that can
- // safely be cached is that of the top evaluated cell.
- return;
- }
-
- 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
- * 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.
- */
- public void endEvaluate(CellCacheEntry cce) {
-
- int nFrames = _evaluationFrames.size();
- if (nFrames < 1) {
- throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
- }
-
- nFrames--;
- CellEvaluationFrame frame = _evaluationFrames.get(nFrames);
- if (cce != frame.getCCE()) {
- throw new IllegalStateException("Wrong cell specified. ");
- }
- // else - no problems so pop current frame
- _evaluationFrames.remove(nFrames);
- _currentlyEvaluatingCells.remove(cce);
- }
-
- 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) {
- // Top level frame, there is no 'cell' above this frame that is using the current cell
- } else {
- CellEvaluationFrame consumingFrame = _evaluationFrames.get(prevFrameIndex);
- consumingFrame.addSensitiveInputCell(cce);
- }
- }
-
- 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 = _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);
- }
- }
- }
- }
|