123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /* ====================================================================
- 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.record.aggregates;
-
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.apache.poi.hssf.record.ArrayRecord;
- import org.apache.poi.hssf.record.FormulaRecord;
- import org.apache.poi.hssf.record.SharedFormulaRecord;
- import org.apache.poi.hssf.record.SharedValueRecordBase;
- import org.apache.poi.hssf.record.TableRecord;
- import org.apache.poi.ss.formula.ptg.ExpPtg;
- import org.apache.poi.hssf.util.CellRangeAddress8Bit;
- import org.apache.poi.ss.util.CellReference;
-
- /**
- * Manages various auxiliary records while constructing a
- * {@link RowRecordsAggregate}:
- * <ul>
- * <li>{@link SharedFormulaRecord}s</li>
- * <li>{@link ArrayRecord}s</li>
- * <li>{@link TableRecord}s</li>
- * </ul>
- *
- * @author Josh Micich
- * @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - handling of ArrayRecords
- */
- public final class SharedValueManager {
-
- private static final class SharedFormulaGroup {
- private final SharedFormulaRecord _sfr;
- private final FormulaRecordAggregate[] _frAggs;
- private int _numberOfFormulas;
- /**
- * Coordinates of the first cell having a formula that uses this shared formula.
- * This is often <i>but not always</i> the top left cell in the range covered by
- * {@link #_sfr}
- */
- private final CellReference _firstCell;
-
- public SharedFormulaGroup(SharedFormulaRecord sfr, CellReference firstCell) {
- if (!sfr.isInRange(firstCell.getRow(), firstCell.getCol())) {
- throw new IllegalArgumentException("First formula cell " + firstCell.formatAsString()
- + " is not shared formula range " + sfr.getRange().toString() + ".");
- }
- _sfr = sfr;
- _firstCell = firstCell;
- int width = sfr.getLastColumn() - sfr.getFirstColumn() + 1;
- int height = sfr.getLastRow() - sfr.getFirstRow() + 1;
- _frAggs = new FormulaRecordAggregate[width * height];
- _numberOfFormulas = 0;
- }
-
- public void add(FormulaRecordAggregate agg) {
- if (_numberOfFormulas == 0) {
- if (_firstCell.getRow() != agg.getRow() || _firstCell.getCol() != agg.getColumn()) {
- throw new IllegalStateException("shared formula coding error: "+_firstCell.getCol()+'/'+_firstCell.getRow()+" != "+agg.getColumn()+'/'+agg.getRow());
- }
- }
- if (_numberOfFormulas >= _frAggs.length) {
- throw new RuntimeException("Too many formula records for shared formula group");
- }
- _frAggs[_numberOfFormulas++] = agg;
- }
-
- public void unlinkSharedFormulas() {
- for (int i = 0; i < _numberOfFormulas; i++) {
- _frAggs[i].unlinkSharedFormula();
- }
- }
-
- public SharedFormulaRecord getSFR() {
- return _sfr;
- }
-
- public final String toString() {
- StringBuffer sb = new StringBuffer(64);
- sb.append(getClass().getName()).append(" [");
- sb.append(_sfr.getRange().toString());
- sb.append("]");
- return sb.toString();
- }
- }
-
- /**
- * @return a new empty {@link SharedValueManager}.
- */
- public static SharedValueManager createEmpty() {
- // Note - must create distinct instances because they are assumed to be mutable.
- return new SharedValueManager(
- new SharedFormulaRecord[0], new CellReference[0], new ArrayRecord[0], new TableRecord[0]);
- }
- private final List<ArrayRecord> _arrayRecords;
- private final TableRecord[] _tableRecords;
- private final Map<SharedFormulaRecord, SharedFormulaGroup> _groupsBySharedFormulaRecord;
- /** cached for optimization purposes */
- private Map<Integer,SharedFormulaGroup> _groupsCache;
-
- private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords,
- CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
- int nShF = sharedFormulaRecords.length;
- if (nShF != firstCells.length) {
- throw new IllegalArgumentException("array sizes don't match: " + nShF + "!=" + firstCells.length + ".");
- }
- _arrayRecords = toList(arrayRecords);
- _tableRecords = tableRecords;
- Map<SharedFormulaRecord, SharedFormulaGroup> m = new HashMap<SharedFormulaRecord, SharedFormulaGroup>(nShF * 3 / 2);
- for (int i = 0; i < nShF; i++) {
- SharedFormulaRecord sfr = sharedFormulaRecords[i];
- m.put(sfr, new SharedFormulaGroup(sfr, firstCells[i]));
- }
- _groupsBySharedFormulaRecord = m;
- }
-
- /**
- * @return a modifiable list, independent of the supplied array
- */
- private static <Z> List<Z> toList(Z[] zz) {
- List<Z> result = new ArrayList<Z>(zz.length);
- for (int i = 0; i < zz.length; i++) {
- result.add(zz[i]);
- }
- return result;
- }
-
- /**
- */
- public static SharedValueManager create(SharedFormulaRecord[] sharedFormulaRecords,
- CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
- if (sharedFormulaRecords.length + firstCells.length + arrayRecords.length + tableRecords.length < 1) {
- return createEmpty();
- }
- return new SharedValueManager(sharedFormulaRecords, firstCells, arrayRecords, tableRecords);
- }
-
-
- /**
- * @param firstCell as extracted from the {@link ExpPtg} from the cell's formula.
- * @return never <code>null</code>
- */
- public SharedFormulaRecord linkSharedFormulaRecord(CellReference firstCell, FormulaRecordAggregate agg) {
- SharedFormulaGroup result = findFormulaGroupForCell(firstCell);
- if(null == result) {
- throw new RuntimeException("Failed to find a matching shared formula record");
- }
- result.add(agg);
- return result.getSFR();
- }
-
- private SharedFormulaGroup findFormulaGroupForCell(final CellReference cellRef) {
- if(null == _groupsCache) {
- _groupsCache = new HashMap<Integer,SharedFormulaGroup>(_groupsBySharedFormulaRecord.size());
- for(SharedFormulaGroup group: _groupsBySharedFormulaRecord.values()) {
- _groupsCache.put(getKeyForCache(group._firstCell),group);
- }
- }
- SharedFormulaGroup sfg = _groupsCache.get(getKeyForCache(cellRef));
- return sfg;
- }
-
- private Integer getKeyForCache(final CellReference cellRef) {
- // The HSSF has a max of 2^16 rows and 2^8 cols
- return new Integer((cellRef.getCol()+1)<<16 | cellRef.getRow());
- }
-
- /**
- * Gets the {@link SharedValueRecordBase} record if it should be encoded immediately after the
- * formula record contained in the specified {@link FormulaRecordAggregate} agg. Note - the
- * shared value record always appears after the first formula record in the group. For arrays
- * and tables the first formula is always the in the top left cell. However, since shared
- * formula groups can be sparse and/or overlap, the first formula may not actually be in the
- * top left cell.
- *
- * @return the SHRFMLA, TABLE or ARRAY record for the formula cell, if it is the first cell of
- * a table or array region. <code>null</code> if the formula cell is not shared/array/table,
- * or if the specified formula is not the the first in the group.
- */
- public SharedValueRecordBase getRecordForFirstCell(FormulaRecordAggregate agg) {
- CellReference firstCell = agg.getFormulaRecord().getFormula().getExpReference();
- // perhaps this could be optimised by consulting the (somewhat unreliable) isShared flag
- // and/or distinguishing between tExp and tTbl.
- if (firstCell == null) {
- // not a shared/array/table formula
- return null;
- }
-
-
- int row = firstCell.getRow();
- int column = firstCell.getCol();
- if (agg.getRow() != row || agg.getColumn() != column) {
- // not the first formula cell in the group
- return null;
- }
-
- if(!_groupsBySharedFormulaRecord.isEmpty()) {
- SharedFormulaGroup sfg = findFormulaGroupForCell(firstCell);
- if(null != sfg) {
- return sfg.getSFR();
- }
- }
-
- // Since arrays and tables cannot be sparse (all cells in range participate)
- // The first cell will be the top left in the range. So we can match the
- // ARRAY/TABLE record directly.
-
- for (TableRecord tr : _tableRecords) {
- if (tr.isFirstCell(row, column)) {
- return tr;
- }
- }
- for (ArrayRecord ar : _arrayRecords) {
- if (ar.isFirstCell(row, column)) {
- return ar;
- }
- }
- return null;
- }
-
- /**
- * Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt>
- * to plain unshared formulas
- */
- public void unlink(SharedFormulaRecord sharedFormulaRecord) {
- SharedFormulaGroup svg = _groupsBySharedFormulaRecord.remove(sharedFormulaRecord);
- if (svg == null) {
- throw new IllegalStateException("Failed to find formulas for shared formula");
- }
- _groupsCache = null; // be sure to reset cached value
- svg.unlinkSharedFormulas();
- }
-
- /**
- * Add specified Array Record.
- */
- public void addArrayRecord(ArrayRecord ar) {
- // could do a check here to make sure none of the ranges overlap
- _arrayRecords.add(ar);
- }
-
- /**
- * Removes the {@link ArrayRecord} for the cell group containing the specified cell.
- * The caller should clear (set blank) all cells in the returned range.
- * @return the range of the array formula which was just removed. Never <code>null</code>.
- */
- public CellRangeAddress8Bit removeArrayFormula(int rowIndex, int columnIndex) {
- for (ArrayRecord ar : _arrayRecords) {
- if (ar.isInRange(rowIndex, columnIndex)) {
- _arrayRecords.remove(ar);
- return ar.getRange();
- }
- }
- String ref = new CellReference(rowIndex, columnIndex, false, false).formatAsString();
- throw new IllegalArgumentException("Specified cell " + ref
- + " is not part of an array formula.");
- }
-
- /**
- * @return the shared ArrayRecord identified by (firstRow, firstColumn). never <code>null</code>.
- */
- public ArrayRecord getArrayRecord(int firstRow, int firstColumn) {
- for(ArrayRecord ar : _arrayRecords) {
- if(ar.isFirstCell(firstRow, firstColumn)) {
- return ar;
- }
- }
- return null;
- }
- }
|