View | Details | Raw Unified | Return to bug 51171
Collapse All | Expand All

(-)src/java/org/apache/poi/hssf/record/aggregates/SharedValueManager.java (-69 / +29 lines)
Lines 18-25 Link Here
18
package org.apache.poi.hssf.record.aggregates;
18
package org.apache.poi.hssf.record.aggregates;
19
19
20
import java.util.ArrayList;
20
import java.util.ArrayList;
21
import java.util.Arrays;
22
import java.util.Comparator;
23
import java.util.HashMap;
21
import java.util.HashMap;
24
import java.util.List;
22
import java.util.List;
25
import java.util.Map;
23
import java.util.Map;
Lines 74-80 Link Here
74
		public void add(FormulaRecordAggregate agg) {
72
		public void add(FormulaRecordAggregate agg) {
75
			if (_numberOfFormulas == 0) {
73
			if (_numberOfFormulas == 0) {
76
				if (_firstCell.getRow() != agg.getRow() || _firstCell.getCol() != agg.getColumn()) {
74
				if (_firstCell.getRow() != agg.getRow() || _firstCell.getCol() != agg.getColumn()) {
77
					throw new IllegalStateException("shared formula coding error");
75
					throw new IllegalStateException("shared formula coding error: "+_firstCell.getCol()+'/'+_firstCell.getRow()+" != "+agg.getColumn()+'/'+agg.getRow());
78
				}
76
				}
79
			}
77
			}
80
			if (_numberOfFormulas >= _frAggs.length) {
78
			if (_numberOfFormulas >= _frAggs.length) {
Lines 100-115 Link Here
100
			sb.append("]");
98
			sb.append("]");
101
			return sb.toString();
99
			return sb.toString();
102
		}
100
		}
103
104
		/**
105
		 * Note - the 'first cell' of a shared formula group is not always the top-left cell
106
		 * of the enclosing range.
107
		 * @return <code>true</code> if the specified coordinates correspond to the 'first cell'
108
		 * of this shared formula group.
109
		 */
110
		public boolean isFirstCell(int row, int column) {
111
			return _firstCell.getRow() == row && _firstCell.getCol() == column;
112
		}
113
	}
101
	}
114
102
115
	/**
103
	/**
Lines 124-130 Link Here
124
	private final TableRecord[] _tableRecords;
112
	private final TableRecord[] _tableRecords;
125
	private final Map<SharedFormulaRecord, SharedFormulaGroup> _groupsBySharedFormulaRecord;
113
	private final Map<SharedFormulaRecord, SharedFormulaGroup> _groupsBySharedFormulaRecord;
126
	/** cached for optimization purposes */
114
	/** cached for optimization purposes */
127
	private SharedFormulaGroup[] _groups;
115
    private Map<Integer,SharedFormulaGroup> _groupsCache;
128
116
129
	private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords,
117
	private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords,
130
			CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
118
			CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
Lines 169-225 Link Here
169
	 * @return never <code>null</code>
157
	 * @return never <code>null</code>
170
	 */
158
	 */
171
	public SharedFormulaRecord linkSharedFormulaRecord(CellReference firstCell, FormulaRecordAggregate agg) {
159
	public SharedFormulaRecord linkSharedFormulaRecord(CellReference firstCell, FormulaRecordAggregate agg) {
172
160
		SharedFormulaGroup result = findFormulaGroupForCell(firstCell);
173
		SharedFormulaGroup result = findFormulaGroup(getGroups(), firstCell);
174
		result.add(agg);
161
		result.add(agg);
175
		return result.getSFR();
162
		return result.getSFR();
176
	}
163
	}
177
164
178
	private static SharedFormulaGroup findFormulaGroup(SharedFormulaGroup[] groups, CellReference firstCell) {
165
    private SharedFormulaGroup findFormulaGroupForCell(final CellReference cellRef) {
179
		int row = firstCell.getRow();
166
        if(null == _groupsCache) {
180
		int column = firstCell.getCol();
167
            _groupsCache = new HashMap<Integer,SharedFormulaGroup>(_groupsBySharedFormulaRecord.size());
181
		// Traverse the list of shared formulas and try to find the correct one for us
168
            for(SharedFormulaGroup group: _groupsBySharedFormulaRecord.values()) {
169
                _groupsCache.put(getKeyForCache(group._firstCell),group);
170
            }
171
        }
172
        SharedFormulaGroup sfg = _groupsCache.get(getKeyForCache(cellRef));
173
        if(null == sfg) {
174
            // TODO - fix file "15228.xls" so it opens in Excel after rewriting with POI
175
            throw new RuntimeException("Failed to find a matching shared formula record");
176
        }
177
        return sfg;
178
    }
182
179
183
		// perhaps this could be optimised to some kind of binary search
180
    private Integer getKeyForCache(final CellReference cellRef) {
184
		for (int i = 0; i < groups.length; i++) {
181
        // The HSSF has a max of 2^16 rows and 2^8 cols
185
			SharedFormulaGroup svg = groups[i];
182
        return new Integer((cellRef.getCol()+1)<<16 | cellRef.getRow());
186
			if (svg.isFirstCell(row, column)) {
183
    }
187
				return svg;
188
			}
189
		}
190
		// TODO - fix file "15228.xls" so it opens in Excel after rewriting with POI
191
		throw new RuntimeException("Failed to find a matching shared formula record");
192
	}
193
184
194
	private SharedFormulaGroup[] getGroups() {
195
		if (_groups == null) {
196
			SharedFormulaGroup[] groups = new SharedFormulaGroup[_groupsBySharedFormulaRecord.size()];
197
			_groupsBySharedFormulaRecord.values().toArray(groups);
198
			Arrays.sort(groups, SVGComparator); // make search behaviour more deterministic
199
			_groups = groups;
200
		}
201
		return _groups;
202
	}
203
204
	private static final Comparator<SharedFormulaGroup> SVGComparator = new Comparator<SharedFormulaGroup>() {
205
206
		public int compare(SharedFormulaGroup a, SharedFormulaGroup b) {
207
			CellRangeAddress8Bit rangeA = a.getSFR().getRange();
208
			CellRangeAddress8Bit rangeB = b.getSFR().getRange();
209
210
			int cmp;
211
			cmp = rangeA.getFirstRow() - rangeB.getFirstRow();
212
			if (cmp != 0) {
213
				return cmp;
214
			}
215
			cmp = rangeA.getFirstColumn() - rangeB.getFirstColumn();
216
			if (cmp != 0) {
217
				return cmp;
218
			}
219
			return 0;
220
		}
221
	};
222
223
	/**
185
	/**
224
	 * Gets the {@link SharedValueRecordBase} record if it should be encoded immediately after the
186
	 * Gets the {@link SharedValueRecordBase} record if it should be encoded immediately after the
225
	 * formula record contained in the specified {@link FormulaRecordAggregate} agg.  Note - the
187
	 * formula record contained in the specified {@link FormulaRecordAggregate} agg.  Note - the
Lines 248-263 Link Here
248
			// not the first formula cell in the group
210
			// not the first formula cell in the group
249
			return null;
211
			return null;
250
		}
212
		}
251
		SharedFormulaGroup[] groups = getGroups();
252
		for (int i = 0; i < groups.length; i++) {
253
			// note - logic for finding correct shared formula group is slightly
254
			// more complicated since the first cell
255
			SharedFormulaGroup sfg = groups[i];
256
			if (sfg.isFirstCell(row, column)) {
257
				return sfg.getSFR();
258
			}
259
		}
260
213
214
        if(!_groupsBySharedFormulaRecord.isEmpty()) {
215
            SharedFormulaGroup sfg = findFormulaGroupForCell(firstCell);
216
            if(null != sfg) {
217
                return sfg.getSFR();
218
            }
219
        }
220
261
		// Since arrays and tables cannot be sparse (all cells in range participate)
221
		// Since arrays and tables cannot be sparse (all cells in range participate)
262
		// The first cell will be the top left in the range.  So we can match the
222
		// The first cell will be the top left in the range.  So we can match the
263
		// ARRAY/TABLE record directly.
223
		// ARRAY/TABLE record directly.
Lines 284-290 Link Here
284
		if (svg == null) {
244
		if (svg == null) {
285
			throw new IllegalStateException("Failed to find formulas for shared formula");
245
			throw new IllegalStateException("Failed to find formulas for shared formula");
286
		}
246
		}
287
		_groups = null; // be sure to reset cached value
247
		_groupsCache = null; // be sure to reset cached value
288
		svg.unlinkSharedFormulas();
248
		svg.unlinkSharedFormulas();
289
	}
249
	}
290
250

Return to bug 51171