From d05c02b3c0b8d9e15aa27fb797f70c20f2d13399 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Thu, 4 Dec 2008 02:48:24 +0000 Subject: [PATCH] Initial add of ForkedEvaluator functionality git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@723194 13f79535-47bb-0310-9956-ffa450edef68 --- .../eval/forked/ForkedEvaluationCell.java | 132 ++++++++++++++++ .../eval/forked/ForkedEvaluationSheet.java | 129 ++++++++++++++++ .../eval/forked/ForkedEvaluationWorkbook.java | 144 ++++++++++++++++++ .../formula/eval/forked/ForkedEvaluator.java | 134 ++++++++++++++++ 4 files changed, 539 insertions(+) create mode 100644 src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java create mode 100644 src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationSheet.java create mode 100644 src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java create mode 100644 src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java diff --git a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java new file mode 100644 index 0000000000..3563904b1a --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java @@ -0,0 +1,132 @@ +/* ==================================================================== + 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.eval.forked; + +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.ss.formula.EvaluationCell; +import org.apache.poi.ss.formula.EvaluationSheet; +import org.apache.poi.ss.usermodel.Cell; + +/** + * Represents a cell being used for forked evaluation that has had a value set different from the + * corresponding cell in the shared master workbook. + * + * @author Josh Micich + */ +final class ForkedEvaluationCell implements EvaluationCell { + + private final EvaluationSheet _sheet; + /** corresponding cell from master workbook */ + private final EvaluationCell _masterCell; + private boolean _booleanValue; + private int _cellType; + private int _errorValue; + private double _numberValue; + private String _stringValue; + + public ForkedEvaluationCell(ForkedEvaluationSheet sheet, EvaluationCell masterCell) { + _sheet = sheet; + _masterCell = masterCell; + // start with value blank, but expect construction to be immediately + setValue(BlankEval.INSTANCE); // followed by a proper call to setValue() + } + + public Object getIdentityKey() { + return _masterCell.getIdentityKey(); + } + + public void setValue(ValueEval value) { + Class cls = value.getClass(); + + if (cls == NumberEval.class) { + _cellType = HSSFCell.CELL_TYPE_NUMERIC; + _numberValue = ((NumberEval)value).getNumberValue(); + return; + } + if (cls == StringEval.class) { + _cellType = HSSFCell.CELL_TYPE_STRING; + _stringValue = ((StringEval)value).getStringValue(); + return; + } + if (cls == BoolEval.class) { + _cellType = HSSFCell.CELL_TYPE_BOOLEAN; + _booleanValue = ((BoolEval)value).getBooleanValue(); + return; + } + if (cls == ErrorEval.class) { + _cellType = HSSFCell.CELL_TYPE_ERROR; + _errorValue = ((ErrorEval)value).getErrorCode(); + return; + } + if (cls == BlankEval.class) { + _cellType = HSSFCell.CELL_TYPE_BLANK; + return; + } + throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")"); + } + public void copyValue(Cell destCell) { + switch (_cellType) { + case Cell.CELL_TYPE_BLANK: destCell.setCellType(Cell.CELL_TYPE_BLANK); return; + case Cell.CELL_TYPE_NUMERIC: destCell.setCellValue(_numberValue); return; + case Cell.CELL_TYPE_BOOLEAN: destCell.setCellValue(_booleanValue); return; + case Cell.CELL_TYPE_STRING: destCell.setCellValue(_stringValue); return; + case Cell.CELL_TYPE_ERROR: destCell.setCellErrorValue((byte)_errorValue); return; + } + throw new IllegalStateException("Unexpected data type (" + _cellType + ")"); + } + + private void checkCellType(int expectedCellType) { + if (_cellType != expectedCellType) { + throw new RuntimeException("Wrong data type (" + _cellType + ")"); + } + } + public int getCellType() { + return _cellType; + } + public boolean getBooleanCellValue() { + checkCellType(HSSFCell.CELL_TYPE_BOOLEAN); + return _booleanValue; + } + public int getErrorCellValue() { + checkCellType(HSSFCell.CELL_TYPE_ERROR); + return _errorValue; + } + public double getNumericCellValue() { + checkCellType(HSSFCell.CELL_TYPE_NUMERIC); + return _numberValue; + } + public String getStringCellValue() { + checkCellType(HSSFCell.CELL_TYPE_STRING); + return _stringValue; + } + public EvaluationSheet getSheet() { + return _sheet; + } + public int getRowIndex() { + return _masterCell.getRowIndex(); + } + public int getColumnIndex() { + return _masterCell.getColumnIndex(); + } +} diff --git a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationSheet.java b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationSheet.java new file mode 100644 index 0000000000..837ba932e4 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationSheet.java @@ -0,0 +1,129 @@ +/* ==================================================================== + 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.eval.forked; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.ss.formula.EvaluationCell; +import org.apache.poi.ss.formula.EvaluationSheet; +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * Represents a sheet being used for forked evaluation. Initially, objects of this class contain + * only the cells from the master workbook. By calling {@link #getOrCreateUpdatableCell(int, int)}, + * the master cell object is logically replaced with a {@link ForkedEvaluationCell} instance, which + * will be used in all subsequent evaluations. + * + * @author Josh Micich + */ +final class ForkedEvaluationSheet implements EvaluationSheet { + + private final EvaluationSheet _masterSheet; + /** + * Only cells which have been split are put in this map. (This has been done to conserve memory). + */ + private final Map _sharedCellsByRowCol; + + public ForkedEvaluationSheet(EvaluationSheet masterSheet) { + _masterSheet = masterSheet; + _sharedCellsByRowCol = new HashMap(); + } + + public EvaluationCell getCell(int rowIndex, int columnIndex) { + RowColKey key = new RowColKey(rowIndex, columnIndex); + + ForkedEvaluationCell result = _sharedCellsByRowCol.get(key); + if (result == null) { + return _masterSheet.getCell(rowIndex, columnIndex); + } + return result; + } + + public ForkedEvaluationCell getOrCreateUpdatableCell(int rowIndex, int columnIndex) { + RowColKey key = new RowColKey(rowIndex, columnIndex); + + ForkedEvaluationCell result = _sharedCellsByRowCol.get(key); + if (result == null) { + EvaluationCell mcell = _masterSheet.getCell(rowIndex, columnIndex); + result = new ForkedEvaluationCell(this, mcell); + _sharedCellsByRowCol.put(key, result); + } + return result; + } + + public void copyUpdatedCells(Sheet sheet) { + RowColKey[] keys = new RowColKey[_sharedCellsByRowCol.size()]; + _sharedCellsByRowCol.keySet().toArray(keys); + Arrays.sort(keys); + for (int i = 0; i < keys.length; i++) { + RowColKey key = keys[i]; + Row row = sheet.getRow(key.getRowIndex()); + if (row == null) { + row = sheet.createRow(key.getRowIndex()); + } + Cell destCell = row.getCell(key.getColumnIndex()); + if (destCell == null) { + destCell = row.createCell(key.getColumnIndex()); + } + + ForkedEvaluationCell srcCell = _sharedCellsByRowCol.get(key); + srcCell.copyValue(destCell); + } + } + + public int getSheetIndex(EvaluationWorkbook mewb) { + return mewb.getSheetIndex(_masterSheet); + } + + private static final class RowColKey implements Comparable{ + private final int _rowIndex; + private final int _columnIndex; + + public RowColKey(int rowIndex, int columnIndex) { + _rowIndex = rowIndex; + _columnIndex = columnIndex; + } + @Override + public boolean equals(Object obj) { + RowColKey other = (RowColKey) obj; + return _rowIndex == other._rowIndex && _columnIndex == other._columnIndex; + } + @Override + public int hashCode() { + return _rowIndex ^ _columnIndex; + } + public int compareTo(RowColKey o) { + int cmp = _rowIndex - o._rowIndex; + if (cmp != 0) { + return cmp; + } + return _columnIndex - o._columnIndex; + } + public int getRowIndex() { + return _rowIndex; + } + public int getColumnIndex() { + return _columnIndex; + } + } +} diff --git a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java new file mode 100644 index 0000000000..d7e158f6ff --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java @@ -0,0 +1,144 @@ +/* ==================================================================== + 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.eval.forked; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.hssf.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.ss.formula.EvaluationCell; +import org.apache.poi.ss.formula.EvaluationName; +import org.apache.poi.ss.formula.EvaluationSheet; +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * Represents a workbook being used for forked evaluation. Most operations are delegated to the + * shared master workbook, except those that potentially involve cell values that may have been + * updated after a call to {@link #getOrCreateUpdatableCell(String, int, int)}. + * + * @author Josh Micich + */ +final class ForkedEvaluationWorkbook implements EvaluationWorkbook { + + private final EvaluationWorkbook _masterBook; + private final Map _sharedSheetsByName; + + public ForkedEvaluationWorkbook(EvaluationWorkbook master) { + _masterBook = master; + _sharedSheetsByName = new HashMap(); + } + + public ForkedEvaluationCell getOrCreateUpdatableCell(String sheetName, int rowIndex, + int columnIndex) { + ForkedEvaluationSheet sheet = getSharedSheet(sheetName); + return sheet.getOrCreateUpdatableCell(rowIndex, columnIndex); + } + + public EvaluationCell getEvaluationCell(String sheetName, int rowIndex, int columnIndex) { + ForkedEvaluationSheet sheet = getSharedSheet(sheetName); + return sheet.getCell(rowIndex, columnIndex); + } + + private ForkedEvaluationSheet getSharedSheet(String sheetName) { + ForkedEvaluationSheet result = _sharedSheetsByName.get(sheetName); + if (result == null) { + result = new ForkedEvaluationSheet(_masterBook.getSheet(_masterBook + .getSheetIndex(sheetName))); + _sharedSheetsByName.put(sheetName, result); + } + return result; + } + + public void copyUpdatedCells(Workbook workbook) { + String[] sheetNames = new String[_sharedSheetsByName.size()]; + _sharedSheetsByName.keySet().toArray(sheetNames); + OrderedSheet[] oss = new OrderedSheet[sheetNames.length]; + for (int i = 0; i < sheetNames.length; i++) { + String sheetName = sheetNames[i]; + oss[i] = new OrderedSheet(sheetName, _masterBook.getSheetIndex(sheetName)); + } + for (int i = 0; i < oss.length; i++) { + String sheetName = oss[i].getSheetName(); + ForkedEvaluationSheet sheet = _sharedSheetsByName.get(sheetName); + sheet.copyUpdatedCells(workbook.getSheet(sheetName)); + } + } + + public int convertFromExternSheetIndex(int externSheetIndex) { + return _masterBook.convertFromExternSheetIndex(externSheetIndex); + } + + public ExternalSheet getExternalSheet(int externSheetIndex) { + return _masterBook.getExternalSheet(externSheetIndex); + } + + public Ptg[] getFormulaTokens(EvaluationCell cell) { + if (cell instanceof ForkedEvaluationCell) { + // doesn't happen yet because formulas cannot be modified from the master workbook + throw new RuntimeException("Updated formulas not supported yet"); + } + return _masterBook.getFormulaTokens(cell); + } + + public EvaluationName getName(NamePtg namePtg) { + return _masterBook.getName(namePtg); + } + + public EvaluationSheet getSheet(int sheetIndex) { + return getSharedSheet(getSheetName(sheetIndex)); + } + + public int getSheetIndex(EvaluationSheet sheet) { + if (sheet instanceof ForkedEvaluationSheet) { + ForkedEvaluationSheet mes = (ForkedEvaluationSheet) sheet; + return mes.getSheetIndex(_masterBook); + } + return _masterBook.getSheetIndex(sheet); + } + + public int getSheetIndex(String sheetName) { + return _masterBook.getSheetIndex(sheetName); + } + + public String getSheetName(int sheetIndex) { + return _masterBook.getSheetName(sheetIndex); + } + + public String resolveNameXText(NameXPtg ptg) { + return _masterBook.resolveNameXText(ptg); + } + + private static final class OrderedSheet implements Comparable { + private final String _sheetName; + private final int _index; + + public OrderedSheet(String sheetName, int index) { + _sheetName = sheetName; + _index = index; + } + public String getSheetName() { + return _sheetName; + } + public int compareTo(OrderedSheet o) { + return _index - o._index; + } + } +} diff --git a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java new file mode 100644 index 0000000000..3111dda218 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java @@ -0,0 +1,134 @@ +/* ==================================================================== + 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.eval.forked; + +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.HSSFEvaluationWorkbook; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; +import org.apache.poi.ss.formula.EvaluationCell; +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.formula.IStabilityClassifier; +import org.apache.poi.ss.formula.WorkbookEvaluator; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +/** + * An alternative workbook evaluator that saves memory in situations where a single workbook is + * concurrently and independently evaluated many times. With standard formula evaluation, around + * 90% of memory consumption is due to loading of the {@link HSSFWorkbook} or {@link XSSFWorkbook}. + * This class enables a 'master workbook' to be loaded just once and shared between many evaluation + * clients. Each evaluation client creates its own {@link ForkedEvaluator} and can set cell values + * that will be used for local evaluations (and don't disturb evaluations on other evaluators). + * + * @author Josh Micich + */ +public final class ForkedEvaluator { + + private WorkbookEvaluator _evaluator; + private ForkedEvaluationWorkbook _sewb; + + private ForkedEvaluator(EvaluationWorkbook masterWorkbook, IStabilityClassifier stabilityClassifier) { + _sewb = new ForkedEvaluationWorkbook(masterWorkbook); + _evaluator = new WorkbookEvaluator(_sewb, stabilityClassifier); + } + private static EvaluationWorkbook createEvaluationWorkbook(Workbook wb) { + if (wb instanceof HSSFWorkbook) { + return HSSFEvaluationWorkbook.create((HSSFWorkbook) wb); + } +// TODO rearrange POI build to allow this +// if (wb instanceof XSSFWorkbook) { +// return XSSFEvaluationWorkbook.create((XSSFWorkbook) wb); +// } + throw new IllegalArgumentException("Unexpected workbook type (" + wb.getClass().getName() + ")"); + } + public static ForkedEvaluator create(Workbook wb, IStabilityClassifier stabilityClassifier) { + return new ForkedEvaluator(createEvaluationWorkbook(wb), stabilityClassifier); + } + + /** + * Sets the specified cell to the supplied value + * @param sheetName the name of the sheet containing the cell + * @param rowIndex zero based + * @param columnIndex zero based + */ + public void updateCell(String sheetName, int rowIndex, int columnIndex, ValueEval value) { + + ForkedEvaluationCell cell = _sewb.getOrCreateUpdatableCell(sheetName, rowIndex, columnIndex); + cell.setValue(value); + _evaluator.notifyUpdateCell(cell); + } + /** + * Copies the values of all updated cells (modified by calls to {@link + * #updateCell(String, int, int, ValueEval)}) to the supplied workbook.
+ * Typically, the supplied workbook is a writable copy of the 'master workbook', + * but at the very least it must contain sheets with the same names. + */ + public void copyUpdatedCells(Workbook workbook) { + _sewb.copyUpdatedCells(workbook); + } + + /** + * 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 may be null signifying that the cell is not present (or blank) + * @return null if the supplied cell is null or blank + */ + public ValueEval evaluate(String sheetName, int rowIndex, int columnIndex) { + EvaluationCell cell = _sewb.getEvaluationCell(sheetName, rowIndex, columnIndex); + + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_BOOLEAN: + return BoolEval.valueOf(cell.getBooleanCellValue()); + case HSSFCell.CELL_TYPE_ERROR: + return ErrorEval.valueOf(cell.getErrorCellValue()); + case HSSFCell.CELL_TYPE_FORMULA: + return _evaluator.evaluate(cell); + case HSSFCell.CELL_TYPE_NUMERIC: + return new NumberEval(cell.getNumericCellValue()); + case HSSFCell.CELL_TYPE_STRING: + return new StringEval(cell.getStringCellValue()); + case HSSFCell.CELL_TYPE_BLANK: + return null; + } + throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); + } + /** + * Coordinates several formula evaluators together so that formulas that involve external + * references can be evaluated. + * @param workbookNames the simple file names used to identify the workbooks in formulas + * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") + * @param evaluators all evaluators for the full set of workbooks required by the formulas. + */ + public static void setupEnvironment(String[] workbookNames, ForkedEvaluator[] evaluators) { + WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; + for (int i = 0; i < wbEvals.length; i++) { + wbEvals[i] = evaluators[i]._evaluator; + } + CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); + } +} -- 2.39.5