1 /* ====================================================================
2 Licensed to the Apache Software Foundation (ASF) under one or more
3 contributor license agreements. See the NOTICE file distributed with
4 this work for additional information regarding copyright ownership.
5 The ASF licenses this file to You under the Apache License, Version 2.0
6 (the "License"); you may not use this file except in compliance with
7 the License. You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 ==================================================================== */
17
18 package org.apache.poi.hssf.usermodel;
19
20 import java.util.Iterator;
21 import java.util.NoSuchElementException;
22
23 import org.apache.poi.hssf.model.Sheet;
24 import org.apache.poi.hssf.record.CellValueRecordInterface;
25 import org.apache.poi.hssf.record.RowRecord;
26
27 /**
28 * High level representation of a row of a spreadsheet.
29 *
30 * Only rows that have cells should be added to a Sheet.
31 * @version 1.0-pre
32 * @author Andrew C. Oliver (acoliver at apache dot org)
33 * @author Glen Stampoultzis (glens at apache.org)
34 */
35 public final class HSSFRow implements Comparable {
36
37 // used for collections
38 public final static int INITIAL_CAPACITY = 5;
39
40 private int rowNum;
41 private HSSFCell[] cells=new HSSFCell[INITIAL_CAPACITY];
42
43 /**
44 * reference to low level representation
45 */
46
47 private RowRecord row;
48
49 /**
50 * reference to containing low level Workbook
51 */
52
53 private HSSFWorkbook book;
54
55 /**
56 * reference to containing Sheet
57 */
58
59 private Sheet sheet;
60
61 // TODO - ditch this constructor
62 HSSFRow()
63 {
64 }
65
66 /**
67 * Creates new HSSFRow from scratch. Only HSSFSheet should do this.
68 *
69 * @param book low-level Workbook object containing the sheet that contains this row
70 * @param sheet low-level Sheet object that contains this Row
71 * @param rowNum the row number of this row (0 based)
72 * @see org.apache.poi.hssf.usermodel.HSSFSheet#createRow(int)
73 */
74 HSSFRow(HSSFWorkbook book, Sheet sheet, int rowNum)
75 {
76 this.rowNum = rowNum;
77 this.book = book;
78 this.sheet = sheet;
79 row = new RowRecord(rowNum);
80
81 setRowNum(rowNum);
82 }
83
84 /**
85 * Creates an HSSFRow from a low level RowRecord object. Only HSSFSheet should do
86 * this. HSSFSheet uses this when an existing file is read in.
87 *
88 * @param book low-level Workbook object containing the sheet that contains this row
89 * @param sheet low-level Sheet object that contains this Row
90 * @param record the low level api object this row should represent
91 * @see org.apache.poi.hssf.usermodel.HSSFSheet#createRow(int)
92 */
93 HSSFRow(HSSFWorkbook book, Sheet sheet, RowRecord record)
94 {
95 this.book = book;
96 this.sheet = sheet;
97 row = record;
98
99 setRowNum(record.getRowNumber());
100 }
101
102 /**
103 * Use this to create new cells within the row and return it.
104 * <p>
105 * The cell that is returned is a CELL_TYPE_BLANK. The type can be changed
106 * either through calling <code>setCellValue</code> or <code>setCellType</code>.
107 *
108 * @param column - the column number this cell represents
109 *
110 * @return HSSFCell a high level representation of the created cell.
111 */
112
113 public HSSFCell createCell(short column)
114 {
115 return this.createCell(column,HSSFCell.CELL_TYPE_BLANK);
116 }
117
118 /**
119 * Use this to create new cells within the row and return it.
120 * <p>
121 * The cell that is returned is a CELL_TYPE_BLANK. The type can be changed
122 * either through calling setCellValue or setCellType.
123 *
124 * @param column - the column number this cell represents
125 *
126 * @return HSSFCell a high level representation of the created cell.
127 */
128
129 public HSSFCell createCell(short column, int type)
130 {
131 HSSFCell cell = new HSSFCell(book, sheet, getRowNum(), column, type);
132
133 addCell(cell);
134 sheet.addValueRecord(getRowNum(), cell.getCellValueRecord());
135 return cell;
136 }
137
138 /**
139 * remove the HSSFCell from this row.
140 * @param cell to remove
141 */
142 public void removeCell(HSSFCell cell) {
143 if(cell == null) {
144 throw new IllegalArgumentException("cell must not be null");
145 }
146 removeCell(cell, true);
147 }
148 private void removeCell(HSSFCell cell, boolean alsoRemoveRecords) {
149
150 short column=cell.getCellNum();
151 if(column < 0) {
152 throw new RuntimeException("Negative cell indexes not allowed");
153 }
154 if(column >= cells.length || cell != cells[column]) {
155 throw new RuntimeException("Specified cell is not from this row");
156 }
157 cells[column]=null;
158
159 if(alsoRemoveRecords) {
160 CellValueRecordInterface cval = cell.getCellValueRecord();
161 sheet.removeValueRecord(getRowNum(), cval);
162 }
163
164 if (cell.getCellNum()+1 == row.getLastCol()) {
165 row.setLastCol((short) (findLastCell(row.getLastCol())+1));
166 }
167 if (cell.getCellNum() == row.getFirstCol()) {
168 row.setFirstCol(findFirstCell(row.getFirstCol()));
169 }
170 }
171
172 /**
173 * create a high level HSSFCell object from an existing low level record. Should
174 * only be called from HSSFSheet or HSSFRow itself.
175 * @param cell low level cell to create the high level representation from
176 * @return HSSFCell representing the low level record passed in
177 */
178
179 protected HSSFCell createCellFromRecord(CellValueRecordInterface cell)
180 {
181 HSSFCell hcell = new HSSFCell(book, sheet, getRowNum(), cell);
182
183 addCell(hcell);
184
185 // sheet.addValueRecord(getRowNum(),cell.getCellValueRecord());
186 return hcell;
187 }
188
189 /**
190 * set the row number of this row.
191 * @param rowNum the row number (0-based)
192 * @throws IndexOutOfBoundsException if the row number is not within the range 0-65535.
193 */
194 public void setRowNum(int rowNum) {
195 if ((rowNum < 0) || (rowNum > RowRecord.MAX_ROW_NUMBER)) {
196 throw new IllegalArgumentException("Invalid row number (" + rowNum
197 + ") outside allowable range (0.." + RowRecord.MAX_ROW_NUMBER + ")");
198 }
199 this.rowNum = rowNum;
200 if (row != null)
201 {
202 row.setRowNumber(rowNum); // used only for KEY comparison (HSSFRow)
203 }
204 }
205
206 /**
207 * get row number this row represents
208 * @return the row number (0 based)
209 */
210 public int getRowNum()
211 {
212 return rowNum;
213 }
214
215 /**
216 * Returns the rows outline level. Increased as you
217 * put it into more groups (outlines), reduced as
218 * you take it out of them.
219 * TODO - Should this really be public?
220 */
221 protected int getOutlineLevel() {
222 return row.getOutlineLevel();
223 }
224
225 /**
226 * Moves the supplied cell to a new column, which
227 * must not already have a cell there!
228 * @param cell The cell to move
229 * @param newColumn The new column number (0 based)
230 */
231 public void moveCell(HSSFCell cell, short newColumn) {
232 // Ensure the destination is free
233 if(cells.length > newColumn && cells[newColumn] != null) {
234 throw new IllegalArgumentException("Asked to move cell to column " + newColumn + " but there's already a cell there");
235 }
236
237 // Check it's one of ours
238 if(! cells[cell.getCellNum()].equals(cell)) {
239 throw new IllegalArgumentException("Asked to move a cell, but it didn't belong to our row");
240 }
241
242 // Move the cell to the new position
243 // (Don't remove the records though)
244 removeCell(cell, false);
245 cell.updateCellNum(newColumn);
246 addCell(cell);
247 }
248
249 /**
250 * used internally to add a cell.
251 */
252 private void addCell(HSSFCell cell) {
253
254 short column=cell.getCellNum();
255 // re-allocate cells array as required.
256 if(column>=cells.length) {
257 HSSFCell[] oldCells=cells;
258 int newSize=oldCells.length*2;
259 if(newSize<column+1) {
260 newSize=column+1;
261 }
262 cells=new HSSFCell[newSize];
263 System.arraycopy(oldCells,0,cells,0,oldCells.length);
264 }
265 cells[column]=cell;
266
267 // fix up firstCol and lastCol indexes
268 if (row.getFirstCol() == -1 || column < row.getFirstCol()) {
269 row.setFirstCol(column);
270 }
271
272 if (row.getLastCol() == -1 || column >= row.getLastCol()) {
273 row.setLastCol((short) (column+1)); // +1 -> for one past the last index
274 }
275 }
276
277 /**
278 * Get the hssfcell representing a given column (logical cell)
279 * 0-based. If you ask for a cell that is not defined, then
280 * you get a null.
281 * This is the basic call, with no policies applied
282 *
283 * @param cellnum 0 based column number
284 * @return HSSFCell representing that column or null if undefined.
285 */
286 private HSSFCell retrieveCell(int cellnum) {
287 if(cellnum<0||cellnum>=cells.length) return null;
288 return cells[cellnum];
289 }
290
291 /**
292 * Get the hssfcell representing a given column (logical cell)
293 * 0-based. If you ask for a cell that is not defined then
294 * you get a null, unless you have set a different
295 * {@link MissingCellPolicy} on the base workbook.
296 * Short method signature provided to retain binary
297 * compatibility.
298 *
299 * @param cellnum 0 based column number
300 * @return HSSFCell representing that column or null if undefined.
301 */
302 public HSSFCell getCell(short cellnum) {
303 int ushortCellNum = cellnum & 0x0000FFFF; // avoid sign extension
304 return getCell(ushortCellNum);
305 }
306
307 /**
308 * Get the hssfcell representing a given column (logical cell)
309 * 0-based. If you ask for a cell that is not defined then
310 * you get a null, unless you have set a different
311 * {@link MissingCellPolicy} on the base workbook.
312 *
313 * @param cellnum 0 based column number
314 * @return HSSFCell representing that column or null if undefined.
315 */
316 public HSSFCell getCell(int cellnum) {
317 return getCell(cellnum, book.getMissingCellPolicy());
318 }
319
320 /**
321 * Get the hssfcell representing a given column (logical cell)
322 * 0-based. If you ask for a cell that is not defined, then
323 * your supplied policy says what to do
324 *
325 * @param cellnum 0 based column number
326 * @param policy Policy on blank / missing cells
327 * @return representing that column or null if undefined + policy allows.
328 */
329 public HSSFCell getCell(int cellnum, MissingCellPolicy policy) {
330 HSSFCell cell = retrieveCell(cellnum);
331 if(policy == RETURN_NULL_AND_BLANK) {
332 return cell;
333 }
334 if(policy == RETURN_BLANK_AS_NULL) {
335 if(cell == null) return cell;
336 if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
337 return null;
338 }
339 return cell;
340 }
341 if(policy == CREATE_NULL_AS_BLANK) {
342 if(cell == null) {
343 return createCell((short)cellnum, HSSFCell.CELL_TYPE_BLANK);
344 }
345 return cell;
346 }
347 throw new IllegalArgumentException("Illegal policy " + policy + " (" + policy.id + ")");
348 }
349
350 /**
351 * get the number of the first cell contained in this row.
352 * @return short representing the first logical cell in the row, or -1 if the row does not contain any cells.
353 */
354 public short getFirstCellNum()
355 {
356 if (getPhysicalNumberOfCells() == 0)
357 return -1;
358 else
359 return row.getFirstCol();
360 }
361
362 /**
363 * Gets the index of the last cell contained in this row <b>PLUS ONE</b>. The result also
364 * happens to be the 1-based column number of the last cell. This value can be used as a
365 * standard upper bound when iterating over cells:
366 * <pre>
367 * short minColIx = row.getFirstCellNum();
368 * short maxColIx = row.getLastCellNum();
369 * for(short colIx=minColIx; colIx<maxColIx; colIx++) {
370 * HSSFCell cell = row.getCell(colIx);
371 * if(cell == null) {
372 * continue;
373 * }
374 * //... do something with cell
375 * }
376 * </pre>
377 *
378 * @return short representing the last logical cell in the row <b>PLUS ONE</b>, or -1 if the
379 * row does not contain any cells.
380 */
381 public short getLastCellNum() {
382 if (getPhysicalNumberOfCells() == 0) {
383 return -1;
384 }
385 return row.getLastCol();
386 }
387
388
389 /**
390 * gets the number of defined cells (NOT number of cells in the actual row!).
391 * That is to say if only columns 0,4,5 have values then there would be 3.
392 * @return int representing the number of defined cells in the row.
393 */
394
395 public int getPhysicalNumberOfCells()
396 {
397 int count=0;
398 for(int i=0;i<cells.length;i++)
399 {
400 if(cells[i]!=null) count++;
401 }
402 return count;
403 }
404
405 /**
406 * set the row's height or set to ff (-1) for undefined/default-height. Set the height in "twips" or
407 * 1/20th of a point.
408 * @param height rowheight or 0xff for undefined (use sheet default)
409 */
410
411 public void setHeight(short height)
412 {
413
414 // row.setOptionFlags(
415 row.setBadFontHeight(true);
416 row.setHeight(height);
417 }
418
419 /**
420 * set whether or not to display this row with 0 height
421 * @param zHeight height is zero or not.
422 */
423 public void setZeroHeight(boolean zHeight) {
424 row.setZeroHeight(zHeight);
425 }
426
427 /**
428 * get whether or not to display this row with 0 height
429 * @return - zHeight height is zero or not.
430 */
431 public boolean getZeroHeight() {
432 return row.getZeroHeight();
433 }
434
435 /**
436 * set the row's height in points.
437 * @param height row height in points
438 */
439
440 public void setHeightInPoints(float height)
441 {
442
443 // row.setOptionFlags(
444 row.setBadFontHeight(true);
445 row.setHeight((short) (height * 20));
446 }
447
448 /**
449 * get the row's height or ff (-1) for undefined/default-height in twips (1/20th of a point)
450 * @return rowheight or 0xff for undefined (use sheet default)
451 */
452
453 public short getHeight()
454 {
455 return row.getHeight();
456 }
457
458 /**
459 * get the row's height or ff (-1) for undefined/default-height in points (20*getHeight())
460 * @return rowheight or 0xff for undefined (use sheet default)
461 */
462
463 public float getHeightInPoints()
464 {
465 return (row.getHeight() / 20);
466 }
467
468 /**
469 * get the lowlevel RowRecord represented by this object - should only be called
470 * by other parts of the high level API
471 *
472 * @return RowRecord this row represents
473 */
474
475 protected RowRecord getRowRecord()
476 {
477 return row;
478 }
479
480 /**
481 * used internally to refresh the "last cell" when the last cell is removed.
482 */
483
484 private short findLastCell(short lastcell)
485 {
486 short cellnum = (short) (lastcell - 1);
487 HSSFCell r = getCell(cellnum);
488
489 while (r == null && cellnum >= 0)
490 {
491 r = getCell(--cellnum);
492 }
493 return cellnum;
494 }
495
496 /**
497 * used internally to refresh the "first cell" when the first cell is removed.
498 */
499
500 private short findFirstCell(short firstcell)
501 {
502 short cellnum = (short) (firstcell + 1);
503 HSSFCell r = getCell(cellnum);
504
505 while (r == null && cellnum <= getLastCellNum())
506 {
507 r = getCell(++cellnum);
508 }
509 if (cellnum > getLastCellNum())
510 return -1;
511 return cellnum;
512 }
513
514 /**
515 * Used to specify the different possible policies
516 * if for the case of null and blank cells
517 */
518 public static class MissingCellPolicy {
519 private static int NEXT_ID = 1;
520 private final int id;
521 private MissingCellPolicy() {
522 this.id = NEXT_ID++;
523 }
524 }
525
526 /** Missing cells are returned as null, Blank cells are returned as normal */
527 public static final MissingCellPolicy RETURN_NULL_AND_BLANK = new MissingCellPolicy();
528 /** Missing cells are returned as null, as are blank cells */
529 public static final MissingCellPolicy RETURN_BLANK_AS_NULL = new MissingCellPolicy();
530 /** A new, blank cell is created for missing cells. Blank cells are returned as normal */
531 public static final MissingCellPolicy CREATE_NULL_AS_BLANK = new MissingCellPolicy();
532
533
534 /**
535 * @return cell iterator of the physically defined cells.
536 * Note that the 4th element might well not be cell 4, as the iterator
537 * will not return un-defined (null) cells.
538 * Call getCellNum() on the returned cells to know which cell they are.
539 */
540 public Iterator cellIterator()
541 {
542 return new CellIterator();
543 }
544 /**
545 * Alias for {@link CellIterator} to allow
546 * foreach loops
547 */
548 public Iterator iterator() {
549 return cellIterator();
550 }
551
552 /**
553 * An iterator over the (physical) cells in the row.
554 */
555 private class CellIterator implements Iterator
556 {
557 int thisId=-1;
558 int nextId=-1;
559
560 public CellIterator()
561 {
562 findNext();
563 }
564
565 public boolean hasNext() {
566 return nextId<cells.length;
567 }
568
569 public Object next() {
570 if (!hasNext())
571 throw new NoSuchElementException("At last element");
572 HSSFCell cell=cells[nextId];
573 thisId=nextId;
574 findNext();
575 return cell;
576 }
577
578 public void remove() {
579 if (thisId == -1)
580 throw new IllegalStateException("remove() called before next()");
581 cells[thisId]=null;
582 }
583
584 private void findNext()
585 {
586 int i=nextId+1;
587 for(;i<cells.length;i++)
588 {
589 if(cells[i]!=null) break;
590 }
591 nextId=i;
592 }
593
594 }
595
596 public int compareTo(Object obj)
597 {
598 HSSFRow loc = (HSSFRow) obj;
599
600 if (this.getRowNum() == loc.getRowNum())
601 {
602 return 0;
603 }
604 if (this.getRowNum() < loc.getRowNum())
605 {
606 return -1;
607 }
608 if (this.getRowNum() > loc.getRowNum())
609 {
610 return 1;
611 }
612 return -1;
613 }
614
615 public boolean equals(Object obj)
616 {
617 if (!(obj instanceof HSSFRow))
618 {
619 return false;
620 }
621 HSSFRow loc = (HSSFRow) obj;
622
623 if (this.getRowNum() == loc.getRowNum())
624 {
625 return true;
626 }
627 return false;
628 }
629 }