diff options
author | Nick Burch <nick@apache.org> | 2008-03-29 19:36:53 +0000 |
---|---|---|
committer | Nick Burch <nick@apache.org> | 2008-03-29 19:36:53 +0000 |
commit | 4bc2e0eb3e2c448ef5e47e2bff8866d1f2670c5b (patch) | |
tree | acffbbe7865f4d2ddd3981cdee74542c806ebf1c /src/java/org/apache/poi/hssf/usermodel | |
parent | 59faaa7434413fba7ec247d3a9d8b36352a9c919 (diff) | |
download | poi-4bc2e0eb3e2c448ef5e47e2bff8866d1f2670c5b.tar.gz poi-4bc2e0eb3e2c448ef5e47e2bff8866d1f2670c5b.zip |
Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-639241,639243-639253,639255-639486,639488-639601,639603-639835,639837-639917,639919-640056,640058-640710,640712-641156,641158-641184,641186-641795,641797-641798,641800-641933,641935-641963,641965-641966,641968-641995,641997-642230,642232-642562,642564-642566,642568-642574,642576 via svnmerge from
https://svn.apache.org/repos/asf/poi/trunk
........
r642566 | nick | 2008-03-29 17:45:28 +0000 (Sat, 29 Mar 2008) | 1 line
Move the missing record aware eventusermodel code out of scratchpad
........
r642571 | nick | 2008-03-29 18:11:48 +0000 (Sat, 29 Mar 2008) | 1 line
Merge several bug tests into one file
........
r642574 | nick | 2008-03-29 18:23:33 +0000 (Sat, 29 Mar 2008) | 1 line
Move the FormulaEvaluator code out of scratchpad
........
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@642591 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/poi/hssf/usermodel')
4 files changed, 1139 insertions, 0 deletions
diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java b/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java new file mode 100755 index 0000000000..90f5807ff5 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java @@ -0,0 +1,150 @@ +/* ==================================================================== + 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 java.util.ArrayList; +import java.util.List; + +/** + * Instances of this class keep track of multiple dependent cell evaluations due + * to recursive calls to <tt>HSSFFormulaEvaluator.internalEvaluate()</tt>. + * 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 EvaluationCycleDetector { + + /** + * Stores the parameters that identify the evaluation of one cell.<br/> + */ + private static final class CellEvaluationFrame { + + private final HSSFWorkbook _workbook; + private final HSSFSheet _sheet; + private final int _srcRowNum; + private final int _srcColNum; + + public CellEvaluationFrame(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { + if (workbook == null) { + throw new IllegalArgumentException("workbook must not be null"); + } + if (sheet == null) { + throw new IllegalArgumentException("sheet must not be null"); + } + _workbook = workbook; + _sheet = sheet; + _srcRowNum = srcRowNum; + _srcColNum = srcColNum; + } + + public boolean equals(Object obj) { + CellEvaluationFrame other = (CellEvaluationFrame) obj; + if (_workbook != other._workbook) { + return false; + } + if (_sheet != other._sheet) { + return false; + } + if (_srcRowNum != other._srcRowNum) { + return false; + } + if (_srcColNum != other._srcColNum) { + return false; + } + return true; + } + + /** + * @return human readable string for debug purposes + */ + public String formatAsString() { + return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _workbook.getSheetIndex(_sheet); + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } + } + + private final List _evaluationFrames; + + public EvaluationCycleDetector() { + _evaluationFrames = new ArrayList(); + } + + /** + * 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 + * return an evaluation result of + * <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. + */ + public boolean startEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { + CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); + if (_evaluationFrames.contains(cef)) { + return false; + } + _evaluationFrames.add(cef); + return true; + } + + /** + * 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(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { + 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(workbook, sheet, srcRowNum, srcColNum); + if (!cefActual.equals(cefExpected)) { + throw new RuntimeException("Wrong cell specified. " + + "Corresponding startEvaluate() call was for cell {" + + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {" + + cefActual.formatAsString() + "}"); + } + // else - no problems so pop current frame + _evaluationFrames.remove(nFrames); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java b/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java new file mode 100755 index 0000000000..a06cd201e2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java @@ -0,0 +1,46 @@ +/* ==================================================================== + 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; + +/** + * This class makes an <tt>EvaluationCycleDetector</tt> instance available to + * each thread via a <tt>ThreadLocal</tt> in order to avoid adding a parameter + * to a few protected methods within <tt>HSSFFormulaEvaluator</tt>. + * + * @author Josh Micich + */ +final class EvaluationCycleDetectorManager { + + ThreadLocal tl = null; + private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() { + protected synchronized Object initialValue() { + return new EvaluationCycleDetector(); + } + }; + + /** + * @return + */ + public static EvaluationCycleDetector getTracker() { + return (EvaluationCycleDetector) _tlEvaluationTracker.get(); + } + + private EvaluationCycleDetectorManager() { + // no instances of this class + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java new file mode 100644 index 0000000000..bb16fdfadd --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -0,0 +1,778 @@ +/* +* 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 java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Stack; + +import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.Workbook; +import org.apache.poi.hssf.record.formula.Area3DPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.AttrPtg; +import org.apache.poi.hssf.record.formula.BoolPtg; +import org.apache.poi.hssf.record.formula.ControlPtg; +import org.apache.poi.hssf.record.formula.IntPtg; +import org.apache.poi.hssf.record.formula.MemErrPtg; +import org.apache.poi.hssf.record.formula.MissingArgPtg; +import org.apache.poi.hssf.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.NumberPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.ParenthesisPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.record.formula.ReferencePtg; +import org.apache.poi.hssf.record.formula.StringPtg; +import org.apache.poi.hssf.record.formula.UnionPtg; +import org.apache.poi.hssf.record.formula.UnknownPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.Area3DEval; +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.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.FunctionEval; +import org.apache.poi.hssf.record.formula.eval.NameEval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; +import org.apache.poi.hssf.record.formula.eval.Ref2DEval; +import org.apache.poi.hssf.record.formula.eval.Ref3DEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +/** + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + * + */ +public class HSSFFormulaEvaluator { + + // params to lookup the right constructor using reflection + private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; + + private static final Class[] AREA3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval[].class }; + + private static final Class[] REFERENCE_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval.class }; + + private static final Class[] REF3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval.class }; + + // Maps for mapping *Eval to *Ptg + private static final Map VALUE_EVALS_MAP = new HashMap(); + + /* + * Following is the mapping between the Ptg tokens returned + * by the FormulaParser and the *Eval classes that are used + * by the FormulaEvaluator + */ + static { + VALUE_EVALS_MAP.put(BoolPtg.class, BoolEval.class); + VALUE_EVALS_MAP.put(IntPtg.class, NumberEval.class); + VALUE_EVALS_MAP.put(NumberPtg.class, NumberEval.class); + VALUE_EVALS_MAP.put(StringPtg.class, StringEval.class); + + } + + + protected HSSFRow row; + protected HSSFSheet sheet; + protected HSSFWorkbook workbook; + + public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { + this.sheet = sheet; + this.workbook = workbook; + } + + public void setCurrentRow(HSSFRow row) { + this.row = row; + } + + + /** + * Returns an underlying FormulaParser, for the specified + * Formula String and HSSFWorkbook. + * This will allow you to generate the Ptgs yourself, if + * your needs are more complex than just having the + * formula evaluated. + */ + public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) { + return new FormulaParser(formula, workbook.getWorkbook()); + } + + /** + * If cell contains a formula, the formula is evaluated and returned, + * else the CellValue simply copies the appropriate cell value from + * 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 + */ + public CellValue evaluate(HSSFCell cell) { + CellValue retval = null; + if (cell != null) { + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_BLANK: + retval = new CellValue(HSSFCell.CELL_TYPE_BLANK); + break; + case HSSFCell.CELL_TYPE_BOOLEAN: + retval = new CellValue(HSSFCell.CELL_TYPE_BOOLEAN); + retval.setBooleanValue(cell.getBooleanCellValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + retval = new CellValue(HSSFCell.CELL_TYPE_ERROR); + retval.setErrorValue(cell.getErrorCellValue()); + break; + case HSSFCell.CELL_TYPE_FORMULA: + retval = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); + break; + case HSSFCell.CELL_TYPE_NUMERIC: + retval = new CellValue(HSSFCell.CELL_TYPE_NUMERIC); + retval.setNumberValue(cell.getNumericCellValue()); + break; + case HSSFCell.CELL_TYPE_STRING: + retval = new CellValue(HSSFCell.CELL_TYPE_STRING); + retval.setRichTextStringValue(cell.getRichStringCellValue()); + break; + } + } + return retval; + } + + + /** + * If cell contains formula, it evaluates the formula, + * and saves the result of the formula. The cell + * remains as a formula cell. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the type of the formula result is returned, + * so you know what kind of value is also stored with + * the formula. + * <pre> + * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); + * </pre> + * Be aware that your cell will hold both the formula, + * and the result. If you want the cell replaced with + * the result of the formula, use {@link #evaluateInCell(HSSFCell)} + * @param cell The cell to evaluate + * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) + */ + public int evaluateFormulaCell(HSSFCell cell) { + if (cell != null) { + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_FORMULA: + CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); + switch (cv.getCellType()) { + case HSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + cell.setCellValue(cv.getErrorValue()); + break; + case HSSFCell.CELL_TYPE_NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case HSSFCell.CELL_TYPE_STRING: + cell.setCellValue(cv.getRichTextStringValue()); + break; + case HSSFCell.CELL_TYPE_BLANK: + break; + case HSSFCell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula + break; + } + return cv.getCellType(); + } + } + return -1; + } + + /** + * If cell contains formula, it evaluates the formula, and + * puts the formula result back into the cell, in place + * of the old formula. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the same instance of HSSFCell is returned to + * allow chained calls like: + * <pre> + * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); + * </pre> + * Be aware that your cell value will be changed to hold the + * result of the formula. If you simply want the formula + * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)} + * @param cell + */ + public HSSFCell evaluateInCell(HSSFCell cell) { + if (cell != null) { + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_FORMULA: + CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); + switch (cv.getCellType()) { + case HSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN); + cell.setCellValue(cv.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + cell.setCellType(HSSFCell.CELL_TYPE_ERROR); + cell.setCellValue(cv.getErrorValue()); + break; + case HSSFCell.CELL_TYPE_NUMERIC: + cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC); + cell.setCellValue(cv.getNumberValue()); + break; + case HSSFCell.CELL_TYPE_STRING: + cell.setCellType(HSSFCell.CELL_TYPE_STRING); + cell.setCellValue(cv.getRichTextStringValue()); + break; + case HSSFCell.CELL_TYPE_BLANK: + break; + case HSSFCell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula + break; + } + } + } + return cell; + } + + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(HSSFWorkbook wb) { + for(int i=0; i<wb.getNumberOfSheets(); i++) { + HSSFSheet sheet = wb.getSheetAt(i); + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + + for (Iterator rit = sheet.rowIterator(); rit.hasNext();) { + HSSFRow r = (HSSFRow)rit.next(); + evaluator.setCurrentRow(r); + + for (Iterator cit = r.cellIterator(); cit.hasNext();) { + HSSFCell c = (HSSFCell)cit.next(); + if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) + evaluator.evaluateFormulaCell(c); + } + } + } + } + + + /** + * Returns a CellValue wrapper around the supplied ValueEval instance. + * @param eval + */ + protected static CellValue getCellValueForEval(ValueEval eval) { + CellValue retval = null; + if (eval != null) { + if (eval instanceof NumberEval) { + NumberEval ne = (NumberEval) eval; + retval = new CellValue(HSSFCell.CELL_TYPE_NUMERIC); + retval.setNumberValue(ne.getNumberValue()); + } + else if (eval instanceof BoolEval) { + BoolEval be = (BoolEval) eval; + retval = new CellValue(HSSFCell.CELL_TYPE_BOOLEAN); + retval.setBooleanValue(be.getBooleanValue()); + } + else if (eval instanceof StringEval) { + StringEval ne = (StringEval) eval; + retval = new CellValue(HSSFCell.CELL_TYPE_STRING); + retval.setStringValue(ne.getStringValue()); + } + else if (eval instanceof BlankEval) { + retval = new CellValue(HSSFCell.CELL_TYPE_BLANK); + } + else if (eval instanceof ErrorEval) { + retval = new CellValue(HSSFCell.CELL_TYPE_ERROR); + retval.setErrorValue((byte)((ErrorEval)eval).getErrorCode()); +// retval.setRichTextStringValue(new HSSFRichTextString("#An error occurred. check cell.getErrorCode()")); + } + else { + retval = new CellValue(HSSFCell.CELL_TYPE_ERROR); + } + } + return retval; + } + + /** + * 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.) + */ + private static ValueEval internalEvaluate(HSSFCell srcCell, HSSFRow srcRow, HSSFSheet sheet, HSSFWorkbook workbook) { + int srcRowNum = srcRow.getRowNum(); + short srcColNum = srcCell.getCellNum(); + + + EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker(); + + if(!tracker.startEvaluate(workbook, sheet, srcRowNum, srcColNum)) { + return ErrorEval.CIRCULAR_REF_ERROR; + } + try { + return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula()); + } finally { + tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum); + } + } + private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet, + int srcRowNum, short srcColNum, String cellFormulaText) { + FormulaParser parser = new FormulaParser(cellFormulaText, workbook.getWorkbook()); + parser.parse(); + Ptg[] ptgs = parser.getRPNPtg(); + // -- parsing over -- + + + Stack stack = new Stack(); + for (int i = 0, iSize = ptgs.length; i < iSize; i++) { + + // since we don't know how to handle these yet :( + Ptg ptg = ptgs[i]; + if (ptg instanceof ControlPtg) { continue; } + if (ptg instanceof MemErrPtg) { continue; } + if (ptg instanceof MissingArgPtg) { continue; } + if (ptg instanceof NamePtg) { + // named ranges, macro functions + NamePtg namePtg = (NamePtg) ptg; + stack.push(new NameEval(namePtg.getIndex())); + continue; + } + if (ptg instanceof NameXPtg) { + // TODO - external functions + continue; + } + if (ptg instanceof UnknownPtg) { continue; } + + if (ptg instanceof OperationPtg) { + OperationPtg optg = (OperationPtg) ptg; + + // parens can be ignored since we have RPN tokens + if (optg instanceof ParenthesisPtg) { continue; } + if (optg instanceof AttrPtg) { continue; } + if (optg instanceof UnionPtg) { continue; } + + OperationEval operation = OperationEvaluatorFactory.create(optg); + + int numops = operation.getNumberOfOperands(); + Eval[] ops = new Eval[numops]; + + // storing the ops in reverse order since they are popping + for (int j = numops - 1; j >= 0; j--) { + Eval p = (Eval) stack.pop(); + ops[j] = p; + } + Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet); + stack.push(opresult); + } + else if (ptg instanceof ReferencePtg) { + ReferencePtg refPtg = (ReferencePtg) ptg; + int colIx = refPtg.getColumn(); + int rowIx = refPtg.getRow(); + HSSFRow row = sheet.getRow(rowIx); + HSSFCell cell = (row != null) ? row.getCell(colIx) : null; + stack.push(createRef2DEval(refPtg, cell, row, sheet, workbook)); + } + else if (ptg instanceof Ref3DPtg) { + Ref3DPtg refPtg = (Ref3DPtg) ptg; + int colIx = refPtg.getColumn(); + int rowIx = refPtg.getRow(); + Workbook wb = workbook.getWorkbook(); + HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex())); + HSSFRow row = xsheet.getRow(rowIx); + HSSFCell cell = (row != null) ? row.getCell(colIx) : null; + stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook)); + } + else if (ptg instanceof AreaPtg) { + AreaPtg ap = (AreaPtg) ptg; + AreaEval ae = evaluateAreaPtg(sheet, workbook, ap); + stack.push(ae); + } + else if (ptg instanceof Area3DPtg) { + Area3DPtg a3dp = (Area3DPtg) ptg; + AreaEval ae = evaluateArea3dPtg(workbook, a3dp); + stack.push(ae); + } + else { + Eval ptgEval = getEvalForPtg(ptg); + stack.push(ptgEval); + } + } + + ValueEval value = ((ValueEval) stack.pop()); + if (!stack.isEmpty()) { + throw new IllegalStateException("evaluation stack not empty"); + } + value = dereferenceValue(value, srcRowNum, srcColNum); + if (value instanceof BlankEval) { + // Note Excel behaviour here. A blank final final value is converted to zero. + return NumberEval.ZERO; + // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to + // blank, the actual value is empty string. This can be verified with ISBLANK(). + } + return value; + } + + /** + * Dereferences a single value from any AreaEval or RefEval evaluation result. + * If the supplied evaluationResult is just a plain value, it is returned as-is. + * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, + * <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>. + */ + private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) { + if (evaluationResult instanceof RefEval) { + RefEval rv = (RefEval) evaluationResult; + return rv.getInnerValueEval(); + } + if (evaluationResult instanceof AreaEval) { + AreaEval ae = (AreaEval) evaluationResult; + if (ae.isRow()) { + if(ae.isColumn()) { + return ae.getValues()[0]; + } + return ae.getValueAt(ae.getFirstRow(), srcColNum); + } + if (ae.isColumn()) { + return ae.getValueAt(srcRowNum, ae.getFirstColumn()); + } + return ErrorEval.VALUE_INVALID; + } + return evaluationResult; + } + + private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum, + HSSFWorkbook workbook, HSSFSheet sheet) { + + if(operation instanceof FunctionEval) { + FunctionEval fe = (FunctionEval) operation; + if(fe.isFreeRefFunction()) { + return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet); + } + } + return operation.evaluate(ops, srcRowNum, srcColNum); + } + + public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) { + int row0 = ap.getFirstRow(); + int col0 = ap.getFirstColumn(); + int row1 = ap.getLastRow(); + int col1 = ap.getLastColumn(); + + // If the last row is -1, then the + // reference is for the rest of the column + // (eg C:C) + // TODO: Handle whole column ranges properly + if(row1 == -1 && row0 >= 0) { + row1 = (short)sheet.getLastRowNum(); + } + ValueEval[] values = evalArea(workbook, sheet, row0, col0, row1, col1); + return new Area2DEval(ap, values); + } + + public static AreaEval evaluateArea3dPtg(HSSFWorkbook workbook, Area3DPtg a3dp) { + int row0 = a3dp.getFirstRow(); + int col0 = a3dp.getFirstColumn(); + int row1 = a3dp.getLastRow(); + int col1 = a3dp.getLastColumn(); + Workbook wb = workbook.getWorkbook(); + HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex())); + + // If the last row is -1, then the + // reference is for the rest of the column + // (eg C:C) + // TODO: Handle whole column ranges properly + if(row1 == -1 && row0 >= 0) { + row1 = (short)xsheet.getLastRowNum(); + } + + ValueEval[] values = evalArea(workbook, xsheet, row0, col0, row1, col1); + return new Area3DEval(a3dp, values); + } + + private static ValueEval[] evalArea(HSSFWorkbook workbook, HSSFSheet sheet, + int row0, int col0, int row1, int col1) { + ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; + for (int x = row0; sheet != null && x < row1 + 1; x++) { + HSSFRow row = sheet.getRow(x); + for (int y = col0; y < col1 + 1; y++) { + ValueEval cellEval; + if(row == null) { + cellEval = BlankEval.INSTANCE; + } else { + cellEval = getEvalForCell(row.getCell(y), row, sheet, workbook); + } + values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = cellEval; + } + } + return values; + } + + /** + * returns an appropriate Eval impl instance for the Ptg. The Ptg must be + * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg, + * StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be + * passed here! + * + * @param ptg + */ + protected static Eval getEvalForPtg(Ptg ptg) { + Eval retval = null; + + Class clazz = (Class) VALUE_EVALS_MAP.get(ptg.getClass()); + try { + if (ptg instanceof Area3DPtg) { + Constructor constructor = clazz.getConstructor(AREA3D_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else if (ptg instanceof AreaPtg) { + Constructor constructor = clazz.getConstructor(AREA3D_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else if (ptg instanceof ReferencePtg) { + Constructor constructor = clazz.getConstructor(REFERENCE_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else if (ptg instanceof Ref3DPtg) { + Constructor constructor = clazz.getConstructor(REF3D_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else { + if (ptg instanceof IntPtg || ptg instanceof NumberPtg || ptg instanceof StringPtg + || ptg instanceof BoolPtg) { + Constructor constructor = clazz.getConstructor(VALUE_CONTRUCTOR_CLASS_ARRAY); + retval = (ValueEval) constructor.newInstance(new Ptg[] { ptg }); + } + } + } + catch (Exception e) { + throw new RuntimeException("Fatal Error: ", e); + } + return retval; + + } + + /** + * Given a cell, find its type and from that create an appropriate ValueEval + * impl instance and return that. Since the cell could be an external + * reference, we need the sheet that this belongs to. + * Non existent cells are treated as empty. + * @param cell + * @param sheet + * @param workbook + */ + protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { + + if (cell == null) { + return BlankEval.INSTANCE; + } + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_NUMERIC: + return new NumberEval(cell.getNumericCellValue()); + case HSSFCell.CELL_TYPE_STRING: + return new StringEval(cell.getRichStringCellValue().getString()); + case HSSFCell.CELL_TYPE_FORMULA: + return internalEvaluate(cell, row, sheet, workbook); + case HSSFCell.CELL_TYPE_BOOLEAN: + return BoolEval.valueOf(cell.getBooleanCellValue()); + case HSSFCell.CELL_TYPE_BLANK: + return BlankEval.INSTANCE; + case HSSFCell.CELL_TYPE_ERROR: + return ErrorEval.valueOf(cell.getErrorCellValue()); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + /** + * Creates a Ref2DEval for ReferencePtg. + * Non existent cells are treated as RefEvals containing BlankEval. + */ + private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell, + HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { + if (cell == null) { + return new Ref2DEval(ptg, BlankEval.INSTANCE); + } + + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_NUMERIC: + return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue())); + case HSSFCell.CELL_TYPE_STRING: + return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); + case HSSFCell.CELL_TYPE_FORMULA: + return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); + case HSSFCell.CELL_TYPE_BOOLEAN: + return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); + case HSSFCell.CELL_TYPE_BLANK: + return new Ref2DEval(ptg, BlankEval.INSTANCE); + case HSSFCell.CELL_TYPE_ERROR: + return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + /** + * create a Ref3DEval for Ref3DPtg. + */ + private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell, + HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { + if (cell == null) { + return new Ref3DEval(ptg, BlankEval.INSTANCE); + } + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_NUMERIC: + return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue())); + case HSSFCell.CELL_TYPE_STRING: + return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); + case HSSFCell.CELL_TYPE_FORMULA: + return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); + case HSSFCell.CELL_TYPE_BOOLEAN: + return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); + case HSSFCell.CELL_TYPE_BLANK: + return new Ref3DEval(ptg, BlankEval.INSTANCE); + case HSSFCell.CELL_TYPE_ERROR: + return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + /** + * Mimics the 'data view' of a cell. This allows formula evaluator + * to return a CellValue instead of precasting the value to String + * or Number or boolean type. + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + */ + public static final class CellValue { + private int cellType; + private HSSFRichTextString richTextStringValue; + private double numberValue; + private boolean booleanValue; + private byte errorValue; + + /** + * CellType should be one of the types defined in HSSFCell + * @param cellType + */ + public CellValue(int cellType) { + super(); + this.cellType = cellType; + } + /** + * @return Returns the booleanValue. + */ + public boolean getBooleanValue() { + return booleanValue; + } + /** + * @param booleanValue The booleanValue to set. + */ + public void setBooleanValue(boolean booleanValue) { + this.booleanValue = booleanValue; + } + /** + * @return Returns the numberValue. + */ + public double getNumberValue() { + return numberValue; + } + /** + * @param numberValue The numberValue to set. + */ + public void setNumberValue(double numberValue) { + this.numberValue = numberValue; + } + /** + * @return Returns the stringValue. This method is deprecated, use + * getRichTextStringValue instead + * @deprecated + */ + public String getStringValue() { + return richTextStringValue.getString(); + } + /** + * @param stringValue The stringValue to set. This method is deprecated, use + * getRichTextStringValue instead. + * @deprecated + */ + public void setStringValue(String stringValue) { + this.richTextStringValue = new HSSFRichTextString(stringValue); + } + /** + * @return Returns the cellType. + */ + public int getCellType() { + return cellType; + } + /** + * @return Returns the errorValue. + */ + public byte getErrorValue() { + return errorValue; + } + /** + * @param errorValue The errorValue to set. + */ + public void setErrorValue(byte errorValue) { + this.errorValue = errorValue; + } + /** + * @return Returns the richTextStringValue. + */ + public HSSFRichTextString getRichTextStringValue() { + return richTextStringValue; + } + /** + * @param richTextStringValue The richTextStringValue to set. + */ + public void setRichTextStringValue(HSSFRichTextString richTextStringValue) { + this.richTextStringValue = richTextStringValue; + } + } + + /** + * debug method + * + * @param formula + * @param sheet + * @param workbook + */ + void inspectPtgs(String formula) { + FormulaParser fp = new FormulaParser(formula, workbook.getWorkbook()); + fp.parse(); + Ptg[] ptgs = fp.getRPNPtg(); + System.out.println("<ptg-group>"); + for (int i = 0, iSize = ptgs.length; i < iSize; i++) { + System.out.println("<ptg>"); + System.out.println(ptgs[i]); + if (ptgs[i] instanceof OperationPtg) { + System.out.println("numoperands: " + ((OperationPtg) ptgs[i]).getNumberOfOperands()); + } + System.out.println("</ptg>"); + } + System.out.println("</ptg-group>"); + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java b/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java new file mode 100755 index 0000000000..1292009699 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java @@ -0,0 +1,165 @@ +/* ==================================================================== + 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 java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.ConcatPtg; +import org.apache.poi.hssf.record.formula.DividePtg; +import org.apache.poi.hssf.record.formula.EqualPtg; +import org.apache.poi.hssf.record.formula.ExpPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; +import org.apache.poi.hssf.record.formula.GreaterEqualPtg; +import org.apache.poi.hssf.record.formula.GreaterThanPtg; +import org.apache.poi.hssf.record.formula.LessEqualPtg; +import org.apache.poi.hssf.record.formula.LessThanPtg; +import org.apache.poi.hssf.record.formula.MultiplyPtg; +import org.apache.poi.hssf.record.formula.NotEqualPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.PowerPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.SubtractPtg; +import org.apache.poi.hssf.record.formula.UnaryMinusPtg; +import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.record.formula.eval.AddEval; +import org.apache.poi.hssf.record.formula.eval.ConcatEval; +import org.apache.poi.hssf.record.formula.eval.DivideEval; +import org.apache.poi.hssf.record.formula.eval.EqualEval; +import org.apache.poi.hssf.record.formula.eval.FuncVarEval; +import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; +import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; +import org.apache.poi.hssf.record.formula.eval.LessEqualEval; +import org.apache.poi.hssf.record.formula.eval.LessThanEval; +import org.apache.poi.hssf.record.formula.eval.MultiplyEval; +import org.apache.poi.hssf.record.formula.eval.NotEqualEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; +import org.apache.poi.hssf.record.formula.eval.PercentEval; +import org.apache.poi.hssf.record.formula.eval.PowerEval; +import org.apache.poi.hssf.record.formula.eval.SubtractEval; +import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; +import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; + +/** + * This class creates <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt> + * formula tokens. + * + * @author Josh Micich + */ +final class OperationEvaluatorFactory { + private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; + + private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); + + private OperationEvaluatorFactory() { + // no instances of this class + } + + private static Map initialiseConstructorsMap() { + Map m = new HashMap(32); + add(m, AddPtg.class, AddEval.class); + add(m, ConcatPtg.class, ConcatEval.class); + add(m, DividePtg.class, DivideEval.class); + add(m, EqualPtg.class, EqualEval.class); + add(m, FuncPtg.class, FuncVarEval.class); + add(m, FuncVarPtg.class, FuncVarEval.class); + add(m, GreaterEqualPtg.class, GreaterEqualEval.class); + add(m, GreaterThanPtg.class, GreaterThanEval.class); + add(m, LessEqualPtg.class, LessEqualEval.class); + add(m, LessThanPtg.class, LessThanEval.class); + add(m, MultiplyPtg.class, MultiplyEval.class); + add(m, NotEqualPtg.class, NotEqualEval.class); + add(m, PercentPtg.class, PercentEval.class); + add(m, PowerPtg.class, PowerEval.class); + add(m, SubtractPtg.class, SubtractEval.class); + add(m, UnaryMinusPtg.class, UnaryMinusEval.class); + add(m, UnaryPlusPtg.class, UnaryPlusEval.class); + return m; + } + + private static void add(Map m, Class ptgClass, Class evalClass) { + + // perform some validation now, to keep later exception handlers simple + if(!Ptg.class.isAssignableFrom(ptgClass)) { + throw new IllegalArgumentException("Expected Ptg subclass"); + } + if(!OperationEval.class.isAssignableFrom(evalClass)) { + throw new IllegalArgumentException("Expected OperationEval subclass"); + } + if (!Modifier.isPublic(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must be public"); + } + if (Modifier.isAbstract(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must not be abstract"); + } + + Constructor constructor; + try { + constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Missing constructor"); + } + if (!Modifier.isPublic(constructor.getModifiers())) { + throw new RuntimeException("Eval constructor must be public"); + } + m.put(ptgClass, constructor); + } + + /** + * returns the OperationEval concrete impl instance corresponding + * to the supplied operationPtg + */ + public static OperationEval create(OperationPtg ptg) { + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + + Class ptgClass = ptg.getClass(); + + Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); + if(constructor == null) { + if(ptgClass == ExpPtg.class) { + // ExpPtg is used for array formulas and shared formulas. + // it is currently unsupported, and may not even get implemented here + throw new RuntimeException("ExpPtg currently not supported"); + } + throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); + } + + Object result; + Object[] initargs = { ptg }; + try { + result = constructor.newInstance(initargs); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + return (OperationEval) result; + } +} |