001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hssf.record.aggregates;
019:
020: import org.apache.poi.hssf.record.DBCellRecord;
021: import org.apache.poi.hssf.record.Record;
022: import org.apache.poi.hssf.record.RecordInputStream;
023: import org.apache.poi.hssf.record.RowRecord;
024:
025: import java.util.Iterator;
026: import java.util.Map;
027: import java.util.TreeMap;
028:
029: /**
030: *
031: * @author andy
032: * @author Jason Height (jheight at chariot dot net dot au)
033: */
034:
035: public class RowRecordsAggregate extends Record {
036: int firstrow = -1;
037: int lastrow = -1;
038: Map records = null;
039: int size = 0;
040:
041: /** Creates a new instance of ValueRecordsAggregate */
042:
043: public RowRecordsAggregate() {
044: records = new TreeMap();
045: }
046:
047: public void insertRow(RowRecord row) {
048: size += row.getRecordSize();
049:
050: // Integer integer = new Integer(row.getRowNumber());
051: records.put(row, row);
052: if ((row.getRowNumber() < firstrow) || (firstrow == -1)) {
053: firstrow = row.getRowNumber();
054: }
055: if ((row.getRowNumber() > lastrow) || (lastrow == -1)) {
056: lastrow = row.getRowNumber();
057: }
058: }
059:
060: public void removeRow(RowRecord row) {
061: size -= row.getRecordSize();
062:
063: // Integer integer = new Integer(row.getRowNumber());
064: records.remove(row);
065: }
066:
067: public RowRecord getRow(int rownum) {
068: // Row must be between 0 and 65535
069: if (rownum < 0 || rownum > 65535) {
070: throw new IllegalArgumentException(
071: "The row number must be between 0 and 65535");
072: }
073:
074: RowRecord row = new RowRecord();
075: row.setRowNumber(rownum);
076: return (RowRecord) records.get(row);
077: }
078:
079: public int getPhysicalNumberOfRows() {
080: return records.size();
081: }
082:
083: public int getFirstRowNum() {
084: return firstrow;
085: }
086:
087: public int getLastRowNum() {
088: return lastrow;
089: }
090:
091: /** Returns the number of row blocks.
092: * <p/>The row blocks are goupings of rows that contain the DBCell record
093: * after them
094: */
095: public int getRowBlockCount() {
096: int size = records.size() / DBCellRecord.BLOCK_SIZE;
097: if ((records.size() % DBCellRecord.BLOCK_SIZE) != 0)
098: size++;
099: return size;
100: }
101:
102: public int getRowBlockSize(int block) {
103: return 20 * getRowCountForBlock(block);
104: }
105:
106: /** Returns the number of physical rows within a block*/
107: public int getRowCountForBlock(int block) {
108: int startIndex = block * DBCellRecord.BLOCK_SIZE;
109: int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1;
110: if (endIndex >= records.size())
111: endIndex = records.size() - 1;
112:
113: return endIndex - startIndex + 1;
114: }
115:
116: /** Returns the physical row number of the first row in a block*/
117: public int getStartRowNumberForBlock(int block) {
118: //Given that we basically iterate through the rows in order,
119: //For a performance improvement, it would be better to return an instance of
120: //an iterator and use that instance throughout, rather than recreating one and
121: //having to move it to the right position.
122: int startIndex = block * DBCellRecord.BLOCK_SIZE;
123: Iterator rowIter = records.values().iterator();
124: RowRecord row = null;
125: //Position the iterator at the start of the block
126: for (int i = 0; i <= startIndex; i++) {
127: row = (RowRecord) rowIter.next();
128: }
129:
130: return row.getRowNumber();
131: }
132:
133: /** Returns the physical row number of the end row in a block*/
134: public int getEndRowNumberForBlock(int block) {
135: int endIndex = ((block + 1) * DBCellRecord.BLOCK_SIZE) - 1;
136: if (endIndex >= records.size())
137: endIndex = records.size() - 1;
138:
139: Iterator rowIter = records.values().iterator();
140: RowRecord row = null;
141: for (int i = 0; i <= endIndex; i++) {
142: row = (RowRecord) rowIter.next();
143: }
144: return row.getRowNumber();
145: }
146:
147: /** Serializes a block of the rows */
148: private int serializeRowBlock(final int block, final int offset,
149: byte[] data) {
150: final int startIndex = block * DBCellRecord.BLOCK_SIZE;
151: final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE;
152:
153: Iterator rowIterator = records.values().iterator();
154: int pos = offset;
155:
156: //Given that we basically iterate through the rows in order,
157: //For a performance improvement, it would be better to return an instance of
158: //an iterator and use that instance throughout, rather than recreating one and
159: //having to move it to the right position.
160: int i = 0;
161: for (; i < startIndex; i++)
162: rowIterator.next();
163: while (rowIterator.hasNext() && (i++ < endIndex)) {
164: RowRecord row = (RowRecord) rowIterator.next();
165: pos += row.serialize(pos, data);
166: }
167: return pos - offset;
168: }
169:
170: public int serialize(int offset, byte[] data) {
171: throw new RuntimeException(
172: "The serialize method that passes in cells should be used");
173: }
174:
175: /**
176: * called by the class that is responsible for writing this sucker.
177: * Subclasses should implement this so that their data is passed back in a
178: * byte array.
179: *
180: * @param offset offset to begin writing at
181: * @param data byte array containing instance data
182: * @return number of bytes written
183: */
184:
185: public int serialize(int offset, byte[] data,
186: ValueRecordsAggregate cells) {
187: int pos = offset;
188:
189: //DBCells are serialized before row records.
190: final int blockCount = getRowBlockCount();
191: for (int block = 0; block < blockCount; block++) {
192: //Serialize a block of rows.
193: //Hold onto the position of the first row in the block
194: final int rowStartPos = pos;
195: //Hold onto the size of this block that was serialized
196: final int rowBlockSize = serializeRowBlock(block, pos, data);
197: pos += rowBlockSize;
198: //Serialize a block of cells for those rows
199: final int startRowNumber = getStartRowNumberForBlock(block);
200: final int endRowNumber = getEndRowNumberForBlock(block);
201: DBCellRecord cellRecord = new DBCellRecord();
202: //Note: Cell references start from the second row...
203: int cellRefOffset = (rowBlockSize - 20);
204: for (int row = startRowNumber; row <= endRowNumber; row++) {
205: if (null != cells && cells.rowHasCells(row)) {
206: final int rowCellSize = cells.serializeCellRow(row,
207: pos, data);
208: pos += rowCellSize;
209: //Add the offset to the first cell for the row into the DBCellRecord.
210: cellRecord.addCellOffset((short) cellRefOffset);
211: cellRefOffset = rowCellSize;
212: }
213: }
214: //Calculate Offset from the start of a DBCellRecord to the first Row
215: cellRecord.setRowOffset(pos - rowStartPos);
216: pos += cellRecord.serialize(pos, data);
217:
218: }
219: return pos - offset;
220: }
221:
222: /**
223: * You never fill an aggregate
224: */
225: protected void fillFields(RecordInputStream in) {
226: }
227:
228: /**
229: * called by constructor, should throw runtime exception in the event of a
230: * record passed with a differing ID.
231: *
232: * @param id alleged id for this record
233: */
234:
235: protected void validateSid(short id) {
236: }
237:
238: /**
239: * return the non static version of the id for this record.
240: */
241:
242: public short getSid() {
243: return -1000;
244: }
245:
246: public int getRecordSize() {
247: return size;
248: }
249:
250: public Iterator getIterator() {
251: return records.values().iterator();
252: }
253:
254: /**
255: * Performs a deep clone of the record
256: */
257: public Object clone() {
258: RowRecordsAggregate rec = new RowRecordsAggregate();
259: for (Iterator rowIter = getIterator(); rowIter.hasNext();) {
260: //return the cloned Row Record & insert
261: RowRecord row = (RowRecord) ((RowRecord) rowIter.next())
262: .clone();
263: rec.insertRow(row);
264: }
265: return rec;
266: }
267:
268: public int findStartOfRowOutlineGroup(int row) {
269: // Find the start of the group.
270: RowRecord rowRecord = this .getRow(row);
271: int level = rowRecord.getOutlineLevel();
272: int currentRow = row;
273: while (this .getRow(currentRow) != null) {
274: rowRecord = this .getRow(currentRow);
275: if (rowRecord.getOutlineLevel() < level)
276: return currentRow + 1;
277: currentRow--;
278: }
279:
280: return currentRow + 1;
281: }
282:
283: public int findEndOfRowOutlineGroup(int row) {
284: int level = getRow(row).getOutlineLevel();
285: int currentRow;
286: for (currentRow = row; currentRow < this .getLastRowNum(); currentRow++) {
287: if (getRow(currentRow) == null
288: || getRow(currentRow).getOutlineLevel() < level) {
289: break;
290: }
291: }
292:
293: return currentRow - 1;
294: }
295:
296: public int writeHidden(RowRecord rowRecord, int row, boolean hidden) {
297: int level = rowRecord.getOutlineLevel();
298: while (rowRecord != null
299: && this .getRow(row).getOutlineLevel() >= level) {
300: rowRecord.setZeroHeight(hidden);
301: row++;
302: rowRecord = this .getRow(row);
303: }
304: return row - 1;
305: }
306:
307: public void collapseRow(int rowNumber) {
308:
309: // Find the start of the group.
310: int startRow = findStartOfRowOutlineGroup(rowNumber);
311: RowRecord rowRecord = (RowRecord) getRow(startRow);
312:
313: // Hide all the columns until the end of the group
314: int lastRow = writeHidden(rowRecord, startRow, true);
315:
316: // Write collapse field
317: if (getRow(lastRow + 1) != null) {
318: getRow(lastRow + 1).setColapsed(true);
319: } else {
320: RowRecord row = createRow(lastRow + 1);
321: row.setColapsed(true);
322: insertRow(row);
323: }
324: }
325:
326: /**
327: * Create a row record.
328: *
329: * @param row number
330: * @return RowRecord created for the passed in row number
331: * @see org.apache.poi.hssf.record.RowRecord
332: */
333: public static RowRecord createRow(int row) {
334: RowRecord rowrec = new RowRecord();
335:
336: //rowrec.setRowNumber(( short ) row);
337: rowrec.setRowNumber(row);
338: rowrec.setHeight((short) 0xff);
339: rowrec.setOptimize((short) 0x0);
340: rowrec.setOptionFlags((short) 0x100); // seems necessary for outlining
341: rowrec.setXFIndex((short) 0xf);
342: return rowrec;
343: }
344:
345: public boolean isRowGroupCollapsed(int row) {
346: int collapseRow = findEndOfRowOutlineGroup(row) + 1;
347:
348: if (getRow(collapseRow) == null)
349: return false;
350: else
351: return getRow(collapseRow).getColapsed();
352: }
353:
354: public void expandRow(int rowNumber) {
355: int idx = rowNumber;
356: if (idx == -1)
357: return;
358:
359: // If it is already expanded do nothing.
360: if (!isRowGroupCollapsed(idx))
361: return;
362:
363: // Find the start of the group.
364: int startIdx = findStartOfRowOutlineGroup(idx);
365: RowRecord row = getRow(startIdx);
366:
367: // Find the end of the group.
368: int endIdx = findEndOfRowOutlineGroup(idx);
369:
370: // expand:
371: // colapsed bit must be unset
372: // hidden bit gets unset _if_ surrounding groups are expanded you can determine
373: // this by looking at the hidden bit of the enclosing group. You will have
374: // to look at the start and the end of the current group to determine which
375: // is the enclosing group
376: // hidden bit only is altered for this outline level. ie. don't uncollapse contained groups
377: if (!isRowGroupHiddenByParent(idx)) {
378: for (int i = startIdx; i <= endIdx; i++) {
379: if (row.getOutlineLevel() == getRow(i)
380: .getOutlineLevel())
381: getRow(i).setZeroHeight(false);
382: else if (!isRowGroupCollapsed(i))
383: getRow(i).setZeroHeight(false);
384: }
385: }
386:
387: // Write collapse field
388: getRow(endIdx + 1).setColapsed(false);
389: }
390:
391: public boolean isRowGroupHiddenByParent(int row) {
392: // Look out outline details of end
393: int endLevel;
394: boolean endHidden;
395: int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row);
396: if (getRow(endOfOutlineGroupIdx + 1) == null) {
397: endLevel = 0;
398: endHidden = false;
399: } else {
400: endLevel = getRow(endOfOutlineGroupIdx + 1)
401: .getOutlineLevel();
402: endHidden = getRow(endOfOutlineGroupIdx + 1)
403: .getZeroHeight();
404: }
405:
406: // Look out outline details of start
407: int startLevel;
408: boolean startHidden;
409: int startOfOutlineGroupIdx = findStartOfRowOutlineGroup(row);
410: if (startOfOutlineGroupIdx - 1 < 0
411: || getRow(startOfOutlineGroupIdx - 1) == null) {
412: startLevel = 0;
413: startHidden = false;
414: } else {
415: startLevel = getRow(startOfOutlineGroupIdx - 1)
416: .getOutlineLevel();
417: startHidden = getRow(startOfOutlineGroupIdx - 1)
418: .getZeroHeight();
419: }
420:
421: if (endLevel > startLevel) {
422: return endHidden;
423: } else {
424: return startHidden;
425: }
426: }
427:
428: }
|