1
2 /* ====================================================================
3 Licensed to the Apache Software Foundation (ASF) under one or more
4 contributor license agreements. See the NOTICE file distributed with
5 this work for additional information regarding copyright ownership.
6 The ASF licenses this file to You under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with
8 the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================== */
18
19
20 package org.apache.poi.hssf.record.aggregates;
21
22 import org.apache.poi.hssf.record;
23
24 import java.util.Iterator;
25 import java.util.List;
26
27
28 /**
29 *
30 * Aggregate value records together. Things are easier to handle that way.
31 *
32 * @author andy
33 * @author Glen Stampoultzis (glens at apache.org)
34 * @author Jason Height (jheight at chariot dot net dot au)
35 */
36
37 public final class ValueRecordsAggregate
38 extends Record
39 {
40 public final static short sid = -1001; // 1000 clashes with RowRecordsAggregate
41 int firstcell = -1;
42 int lastcell = -1;
43 CellValueRecordInterface[][] records;
44
45 /** Creates a new instance of ValueRecordsAggregate */
46
47 public ValueRecordsAggregate()
48 {
49 records = new CellValueRecordInterface[30][]; // We start with 30 Rows.
50 }
51
52 public void insertCell(CellValueRecordInterface cell) {
53 short column = cell.getColumn();
54 int row = cell.getRow();
55 if (row >= records.length) {
56 CellValueRecordInterface[][] oldRecords = records;
57 int newSize = oldRecords.length * 2;
58 if(newSize<row+1) newSize=row+1;
59 records = new CellValueRecordInterface[newSize][];
60 System.arraycopy(oldRecords, 0, records, 0, oldRecords.length);
61 }
62 CellValueRecordInterface[] rowCells = records[row];
63 if (rowCells == null) {
64 int newSize = column + 1;
65 if(newSize<10) newSize=10;
66 rowCells = new CellValueRecordInterface[newSize];
67 records[row] = rowCells;
68 }
69 if (column >= rowCells.length) {
70 CellValueRecordInterface[] oldRowCells = rowCells;
71 int newSize = oldRowCells.length * 2;
72 if(newSize<column+1) newSize=column+1;
73 // if(newSize>257) newSize=257; // activate?
74 rowCells = new CellValueRecordInterface[newSize];
75 System.arraycopy(oldRowCells, 0, rowCells, 0, oldRowCells.length);
76 records[row] = rowCells;
77 }
78 rowCells[column] = cell;
79
80 if ((column < firstcell) || (firstcell == -1)) {
81 firstcell = column;
82 }
83 if ((column > lastcell) || (lastcell == -1)) {
84 lastcell = column;
85 }
86 }
87
88 public void removeCell(CellValueRecordInterface cell)
89 {
90 if (cell != null) {
91 short column = cell.getColumn();
92 int row = cell.getRow();
93 if(row>=records.length) return;
94 CellValueRecordInterface[] rowCells=records[row];
95 if(rowCells==null) return;
96 if(column>=rowCells.length) return;
97 rowCells[column]=null;
98 }
99 }
100
101 public int getPhysicalNumberOfCells()
102 {
103 int count=0;
104 for(int r=0;r<records.length;r++) {
105 CellValueRecordInterface[] rowCells=records[r];
106 if (rowCells != null)
107 for(short c=0;c<rowCells.length;c++) {
108 if(rowCells[c]!=null) count++;
109 }
110 }
111 return count;
112 }
113
114 public int getFirstCellNum()
115 {
116 return firstcell;
117 }
118
119 public int getLastCellNum()
120 {
121 return lastcell;
122 }
123
124 public int construct(int offset, List records)
125 {
126 int k = 0;
127
128 FormulaRecordAggregate lastFormulaAggregate = null;
129
130 // First up, locate all the shared formulas for this sheet
131 List sharedFormulas = new java.util.ArrayList();
132 for (k = offset; k < records.size(); k++)
133 {
134 Record rec = ( Record ) records.get(k);
135 if (rec instanceof SharedFormulaRecord) {
136 sharedFormulas.add(rec);
137 }
138 if(rec instanceof EOFRecord) {
139 // End of current sheet. Ignore all subsequent shared formula records (Bugzilla 44449)
140 break;
141 }
142 }
143
144 // Now do the main processing sweep
145 for (k = offset; k < records.size(); k++)
146 {
147 Record rec = ( Record ) records.get(k);
148
149 if (rec instanceof StringRecord == false && !rec.isInValueSection() && !(rec instanceof UnknownRecord))
150 {
151 break;
152 } else if (rec instanceof SharedFormulaRecord) {
153 // Already handled, not to worry
154 } else if (rec instanceof FormulaRecord)
155 {
156 FormulaRecord formula = (FormulaRecord)rec;
157 if (formula.isSharedFormula()) {
158 // Traverse the list of shared formulas in
159 // reverse order, and try to find the correct one
160 // for us
161 boolean found = false;
162 for (int i=sharedFormulas.size()-1;i>=0;i--) {
163 // TODO - there is no junit test case to justify this reversed loop
164 // perhaps it could just run in the normal direction?
165 SharedFormulaRecord shrd = (SharedFormulaRecord)sharedFormulas.get(i);
166 if (shrd.isFormulaInShared(formula)) {
167 shrd.convertSharedFormulaRecord(formula);
168 found = true;
169 break;
170 }
171 }
172 if (!found) {
173 handleMissingSharedFormulaRecord(formula);
174 }
175 }
176
177 lastFormulaAggregate = new FormulaRecordAggregate((FormulaRecord)rec, null);
178 insertCell( lastFormulaAggregate );
179 }
180 else if (rec instanceof StringRecord)
181 {
182 lastFormulaAggregate.setStringRecord((StringRecord)rec);
183 }
184 else if (rec.isValue())
185 {
186 insertCell(( CellValueRecordInterface ) rec);
187 }
188 }
189 return k;
190 }
191
192 /**
193 * Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no
194 * call to <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the
195 * <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/>
196 * As it turns out, this is not a problem, because in these circumstances, the existing value
197 * for <tt>parsedExpression</tt> is perfectly OK.<p/>
198 *
199 * This method may also be used for setting breakpoints to help diagnose issues regarding the
200 * abnormally-set 'shared formula' flags.
201 * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
202 *
203 * The method currently does nothing but do not delete it without finding a nice home for this
204 * comment.
205 */
206 private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
207 // could log an info message here since this is a fairly unusual occurrence.
208 }
209
210 /**
211 * called by the class that is responsible for writing this sucker.
212 * Subclasses should implement this so that their data is passed back in a
213 * byte array.
214 *
215 * @param offset to begin writing at
216 * @param data byte array containing instance data
217 * @return number of bytes written
218 */
219
220 public int serialize(int offset, byte [] data)
221 {
222 throw new RuntimeException("This method shouldnt be called. ValueRecordsAggregate.serializeCellRow() should be called from RowRecordsAggregate.");
223 }
224
225 /** Tallies a count of the size of the cell records
226 * that are attached to the rows in the range specified.
227 */
228 public int getRowCellBlockSize(int startRow, int endRow) {
229 MyIterator itr = new MyIterator(startRow, endRow);
230 int size = 0;
231 while (itr.hasNext()) {
232 CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
233 int row = cell.getRow();
234 if (row > endRow)
235 break;
236 if ((row >=startRow) && (row <= endRow))
237 size += ((Record)cell).getRecordSize();
238 }
239 return size;
240 }
241
242 /** Returns true if the row has cells attached to it */
243 public boolean rowHasCells(int row) {
244 if (row > records.length-1) //previously this said row > records.length which means if
245 return false; // if records.length == 60 and I pass "60" here I get array out of bounds
246 CellValueRecordInterface[] rowCells=records[row]; //because a 60 length array has the last index = 59
247 if(rowCells==null) return false;
248 for(int col=0;col<rowCells.length;col++) {
249 if(rowCells[col]!=null) return true;
250 }
251 return false;
252 }
253
254 /** Serializes the cells that are allocated to a certain row range*/
255 public int serializeCellRow(final int row, int offset, byte [] data)
256 {
257 MyIterator itr = new MyIterator(row, row);
258 int pos = offset;
259
260 while (itr.hasNext())
261 {
262 CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
263 if (cell.getRow() != row)
264 break;
265 pos += (( Record ) cell).serialize(pos, data);
266 }
267 return pos - offset;
268 }
269
270
271 /**
272 * You never fill an aggregate
273 */
274 protected void fillFields(RecordInputStream in)
275 {
276 }
277
278 /**
279 * called by constructor, should throw runtime exception in the event of a
280 * record passed with a differing ID.
281 *
282 * @param id alleged id for this record
283 */
284
285 protected void validateSid(short id)
286 {
287 }
288
289 /**
290 * return the non static version of the id for this record.
291 */
292
293 public short getSid()
294 {
295 return sid;
296 }
297
298 public int getRecordSize() {
299
300 int size = 0;
301 Iterator irecs = this.getIterator();
302
303 while (irecs.hasNext()) {
304 size += (( Record ) irecs.next()).getRecordSize();
305 }
306
307 return size;
308 }
309
310 public Iterator getIterator()
311 {
312 return new MyIterator();
313 }
314
315 /** Performs a deep clone of the record*/
316 public Object clone() {
317 ValueRecordsAggregate rec = new ValueRecordsAggregate();
318 for (Iterator valIter = getIterator(); valIter.hasNext();) {
319 CellValueRecordInterface val = (CellValueRecordInterface)((CellValueRecordInterface)valIter.next()).clone();
320 rec.insertCell(val);
321 }
322 return rec;
323 }
324
325 private final class MyIterator implements Iterator {
326 short nextColumn=-1;
327 int nextRow,lastRow;
328
329 public MyIterator()
330 {
331 this.nextRow=0;
332 this.lastRow=records.length-1;
333 findNext();
334 }
335
336 public MyIterator(int firstRow,int lastRow)
337 {
338 this.nextRow=firstRow;
339 this.lastRow=lastRow;
340 findNext();
341 }
342
343 public boolean hasNext() {
344 return nextRow<=lastRow;
345 }
346 public Object next() {
347 Object o=records[nextRow][nextColumn];
348 findNext();
349 return o;
350 }
351 public void remove() {
352 throw new UnsupportedOperationException("gibt's noch nicht");
353 }
354
355 private void findNext() {
356 nextColumn++;
357 for(;nextRow<=lastRow;nextRow++) {
358 //previously this threw array out of bounds...
359 CellValueRecordInterface[] rowCells=(nextRow < records.length) ? records[nextRow] : null;
360 if(rowCells==null) { // This row is empty
361 nextColumn=0;
362 continue;
363 }
364 for(;nextColumn<rowCells.length;nextColumn++) {
365 if(rowCells[nextColumn]!=null) return;
366 }
367 nextColumn=0;
368 }
369 }
370
371 }
372 }