jheight 2004/10/06 20:37:16 Modified: src/java/org/apache/poi/hssf/model Sheet.java src/java/org/apache/poi/hssf/record DBCellRecord.java IndexRecord.java src/java/org/apache/poi/hssf/record/aggregates RowRecordsAggregate.java ValueRecordsAggregate.java Log: Reapplied DBCell etc etc patch to head after it had been clobbered during the move from the rel_2_branch to head. Refixes bug 9576 Revision Changes Path 1.48 +85 -190 jakarta-poi/src/java/org/apache/poi/hssf/model/Sheet.java Index: Sheet.java =================================================================== RCS file: /home/cvs/jakarta-poi/src/java/org/apache/poi/hssf/model/Sheet.java,v retrieving revision 1.47 retrieving revision 1.48 diff -u -r1.47 -r1.48 --- Sheet.java 23 Aug 2004 08:52:28 -0000 1.47 +++ Sheet.java 7 Oct 2004 03:37:15 -0000 1.48 @@ -46,7 +46,7 @@ * @author Andrew C. Oliver (acoliver at apache dot org) * @author Glen Stampoultzis (glens at apache.org) * @author Shawn Laubach (slaubach at apache dot org) Gridlines, Headers, Footers, and PrintSetup - * @author Jason Height (jheight at chariot dot net dot au) Clone support + * @author Jason Height (jheight at chariot dot net dot au) Clone support. DBCell & Index Record writing support * @author Brian Sanders (kestrel at burdell dot org) Active Cell support * * @see org.apache.poi.hssf.model.Workbook @@ -272,6 +272,15 @@ { retval.windowTwo = (WindowTwoRecord) rec; } + else if ( rec.getSid() == DBCellRecord.sid ) + { + rec = null; + } + else if ( rec.getSid() == IndexRecord.sid ) + { + rec = null; + } + else if ( rec.getSid() == ProtectRecord.sid ) { retval.protect = (ProtectRecord) rec; @@ -723,51 +732,6 @@ * Serializes all records in the sheet into one big byte array. Use this to write * the sheet out. * - * @return byte[] array containing the binary representation of the records in this sheet - * - */ - - public byte [] serialize() - { - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "Sheet.serialize"); - - // addDBCellRecords(); - byte[] retval = null; - - // ArrayList bytes = new ArrayList(4096); - int arraysize = getSize(); - int pos = 0; - - // for (int k = 0; k < records.size(); k++) - // { - // bytes.add((( Record ) records.get(k)).serialize()); - // - // } - // for (int k = 0; k < bytes.size(); k++) - // { - // arraysize += (( byte [] ) bytes.get(k)).length; - // POILogger.DEBUG((new StringBuffer("arraysize=")).append(arraysize) - // .toString()); - // } - retval = new byte[ arraysize ]; - for (int k = 0; k < records.size(); k++) - { - - // byte[] rec = (( byte [] ) bytes.get(k)); - // System.arraycopy(rec, 0, retval, pos, rec.length); - pos += (( Record ) records.get(k)).serialize(pos, - retval); // rec.length; - } - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "Sheet.serialize returning " + retval); - return retval; - } - - /** - * Serializes all records in the sheet into one big byte array. Use this to write - * the sheet out. - * * @param offset to begin write at * @param data array containing the binary representation of the records in this sheet * @@ -778,47 +742,75 @@ if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "Sheet.serialize using offsets"); - // addDBCellRecords(); - // ArrayList bytes = new ArrayList(4096); - // int arraysize = getSize(); // 0; - int pos = 0; - - // for (int k = 0; k < records.size(); k++) - // { - // bytes.add((( Record ) records.get(k)).serialize()); - // - // } - // for (int k = 0; k < bytes.size(); k++) - // { - // arraysize += (( byte [] ) bytes.get(k)).length; - // POILogger.DEBUG((new StringBuffer("arraysize=")).append(arraysize) - // .toString()); - // } + int pos = offset; + boolean haveSerializedIndex = false; + for (int k = 0; k < records.size(); k++) { -// byte[] rec = (( byte [] ) bytes.get(k)); - // System.arraycopy(rec, 0, data, offset + pos, rec.length); Record record = (( Record ) records.get(k)); + + int startPos = pos; + //Once the rows have been found in the list of records, start + //writing out the blocked row information. This includes the DBCell references + if (record instanceof RowRecordsAggregate) { + pos += ((RowRecordsAggregate)record).serialize(pos, data, cells); // rec.length; + } else if (record instanceof ValueRecordsAggregate) { + //Do nothing here. The records were serialized during the RowRecordAggregate block serialization + } else { + pos += record.serialize(pos, data ); // rec.length; + } + //If the BOF record was just serialized then add the IndexRecord + if (record.getSid() == BOFRecord.sid) { + //Can there be more than one BOF for a sheet? If not then we can + //remove this guard. So be safe it is left here. + if (!haveSerializedIndex) { + haveSerializedIndex = true; + pos += serializeIndexRecord(k, pos, data); + } + } - //// uncomment to test record sizes //// -// System.out.println( record.getClass().getName() ); -// byte[] data2 = new byte[record.getRecordSize()]; -// record.serialize(0, data2 ); // rec.length; -// if (LittleEndian.getUShort(data2, 2) != record.getRecordSize() - 4 -// && record instanceof RowRecordsAggregate == false -// && record instanceof ValueRecordsAggregate == false -// && record instanceof EscherAggregate == false) -// { -// throw new RuntimeException("Blah!!! Size off by " + ( LittleEndian.getUShort(data2, 2) - record.getRecordSize() - 4) + " records."); -// } - - pos += record.serialize(pos + offset, data ); // rec.length; } if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "Sheet.serialize returning "); - return pos; + return pos-offset; } + + private int serializeIndexRecord(final int BOFRecordIndex, final int offset, byte[] data) { + IndexRecord index = new IndexRecord(); + index.setFirstRow(rows.getFirstRowNum()); + index.setLastRowAdd1(rows.getLastRowNum()+1); + //Calculate the size of the records from the end of the BOF + //and up to the RowRecordsAggregate... + int sheetRecSize = 0; + for (int j = BOFRecordIndex+1; j < records.size(); j++) + { + Record tmpRec = (( Record ) records.get(j)); + if (tmpRec instanceof RowRecordsAggregate) + break; + sheetRecSize+= tmpRec.getRecordSize(); + } + //Add the references to the DBCells in the IndexRecord (one for each block) + int blockCount = rows.getRowBlockCount(); + //Calculate the size of this IndexRecord + int indexRecSize = index.getRecordSizeForBlockCount(blockCount); + + int rowBlockOffset = 0; + int cellBlockOffset = 0; + int dbCellOffset = 0; + for (int block=0;block<blockCount;block++) { + rowBlockOffset += rows.getRowBlockSize(block); + cellBlockOffset += cells.getRowCellBlockSize(rows.getStartRowNumberForBlock(block), + rows.getEndRowNumberForBlock(block)); + //Note: The offsets are relative to the Workbook BOF. Assume that this is + //0 for now..... + index.addDbcell(offset + indexRecSize + sheetRecSize + dbCellOffset + rowBlockOffset + cellBlockOffset); + //Add space required to write the dbcell record(s) (whose references were just added). + dbCellOffset += (8 + (rows.getRowCountForBlock(block) * 2)); + } + return index.serialize(offset, data); + } + /** * Create a row record. (does not add it to the records contained in this sheet) @@ -1379,118 +1371,6 @@ } /** - * Not currently used method to calculate and add dbcell records - * - */ - - public void addDBCellRecords() - { - int offset = 0; - int recnum = 0; - int rownum = 0; - //int lastrow = 0; - //long lastrowoffset = 0; - IndexRecord index = null; - - // ArrayList rowOffsets = new ArrayList(); - IntList rowOffsets = new IntList(); - - for (recnum = 0; recnum < records.size(); recnum++) - { - Record rec = ( Record ) records.get(recnum); - - if (rec.getSid() == IndexRecord.sid) - { - index = ( IndexRecord ) rec; - } - if (rec.getSid() != RowRecord.sid) - { - offset += rec.serialize().length; - } - else - { - break; - } - } - - // First Row Record - for (; recnum < records.size(); recnum++) - { - Record rec = ( Record ) records.get(recnum); - - if (rec.getSid() == RowRecord.sid) - { - rownum++; - rowOffsets.add(offset); - if ((rownum % 32) == 0) - { - - // if this is the last rec in a dbcell block - // find the next row or last value record - for (int rn = recnum; rn < records.size(); rn++) - { - rec = ( Record ) records.get(rn); - if ((!rec.isInValueSection()) - || (rec.getSid() == RowRecord.sid)) - { - - // here is the next row or last value record - records.add(rn, - createDBCell(offset, rowOffsets, - index)); - recnum = rn; - break; - } - } - } - else - { - } - } - if (!rec.isInValueSection()) - { - records.add(recnum, createDBCell(offset, rowOffsets, index)); - break; - } - offset += rec.serialize().length; - } - } - - /** not currently used */ - - private DBCellRecord createDBCell(int offset, IntList rowoffsets, - IndexRecord index) - { - DBCellRecord rec = new DBCellRecord(); - - rec.setRowOffset(offset - rowoffsets.get(0)); - - // test hack - rec.addCellOffset(( short ) 0x0); - - // end test hack - addDbCellToIndex(offset, index); - return rec; - } - - /** not currently used */ - - private void addDbCellToIndex(int offset, IndexRecord index) - { - int numdbcells = index.getNumDbcells() + 1; - - index.addDbcell(offset + preoffset); - - // stupid but whenever we add an offset that causes everything to be shifted down 4 - for (int k = 0; k < numdbcells; k++) - { - int dbval = index.getDbcellAt(k); - - index.setDbcell(k, dbval + 4); - } - } - - /** * creates the BOF record * @see org.apache.poi.hssf.record.BOFRecord * @see org.apache.poi.hssf.record.Record @@ -2147,6 +2027,21 @@ for (int k = 0; k < records.size(); k++) { retval += (( Record ) records.get(k)).getRecordSize(); + } + //Add space for the IndexRecord + final int blocks = rows.getRowBlockCount(); + retval += IndexRecord.getRecordSizeForBlockCount(blocks); + + //Add space for the DBCell records + //Once DBCell per block. + //8 bytes per DBCell (non variable section) + //2 bytes per row reference + int startRetVal = retval; + retval += (8 * blocks); + for (Iterator itr = rows.getIterator(); itr.hasNext();) { + RowRecord row = (RowRecord)itr.next(); + if (cells.rowHasCells(row.getRowNumber())) + retval += 2; } return retval; } 1.9 +10 -3 jakarta-poi/src/java/org/apache/poi/hssf/record/DBCellRecord.java Index: DBCellRecord.java =================================================================== RCS file: /home/cvs/jakarta-poi/src/java/org/apache/poi/hssf/record/DBCellRecord.java,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- DBCellRecord.java 23 Aug 2004 08:52:31 -0000 1.8 +++ DBCellRecord.java 7 Oct 2004 03:37:16 -0000 1.9 @@ -21,16 +21,18 @@ import org.apache.poi.util.LittleEndian; /** - * Title: DBCell Record (Currently read only. Not required.) - * Description: Used to find rows in blocks...TODO<P> + * Title: DBCell Record + * Description: Used by Excel and other MS apps to quickly find rows in the sheets.<P> * REFERENCE: PG 299/440 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P> * @author Andrew C. Oliver (acoliver at apache dot org) + * @author Jason Height * @version 2.0-pre */ public class DBCellRecord extends Record { + public final static int BLOCK_SIZE = 32; public final static short sid = 0xd7; private int field_1_row_offset; private short[] field_2_cell_offsets; @@ -180,7 +182,7 @@ LittleEndian.putInt(data, 4 + offset, getRowOffset()); for (int k = 0; k < getNumCellOffsets(); k++) { - LittleEndian.putShort(data, 8 + k + offset, getCellOffsetAt(k)); + LittleEndian.putShort(data, 8 + 2*k + offset, getCellOffsetAt(k)); } return getRecordSize(); } @@ -188,6 +190,11 @@ public int getRecordSize() { return 8 + (getNumCellOffsets() * 2); + } + + /** Returns the size of a DBCellRecord when it needs to reference a certain number of rows*/ + public static int getRecordSizeForRows(int rows) { + return 8 + (rows * 2); } public short getSid() 1.8 +7 -0 jakarta-poi/src/java/org/apache/poi/hssf/record/IndexRecord.java Index: IndexRecord.java =================================================================== RCS file: /home/cvs/jakarta-poi/src/java/org/apache/poi/hssf/record/IndexRecord.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- IndexRecord.java 23 Aug 2004 08:52:32 -0000 1.7 +++ IndexRecord.java 7 Oct 2004 03:37:16 -0000 1.8 @@ -184,6 +184,13 @@ { return 20 + (getNumDbcells() * 4); } + + /** Returns the size of an INdexRecord when it needs to index the specified number of blocks + * + */ + public static int getRecordSizeForBlockCount(int blockCount) { + return 20 + (4 * blockCount); + } public short getSid() { 1.11 +113 -28 jakarta-poi/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java Index: RowRecordsAggregate.java =================================================================== RCS file: /home/cvs/jakarta-poi/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- RowRecordsAggregate.java 23 Aug 2004 08:52:28 -0000 1.10 +++ RowRecordsAggregate.java 7 Oct 2004 03:37:16 -0000 1.11 @@ -18,6 +18,7 @@ package org.apache.poi.hssf.record.aggregates; +import org.apache.poi.hssf.record.DBCellRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RowRecord; @@ -94,30 +95,90 @@ { return lastrow; } + + /** Returns the number of row blocks. + * <p/>The row blocks are goupings of rows that contain the DBCell record + * after them + */ + public int getRowBlockCount() { + int size = records.size()/DBCellRecord.BLOCK_SIZE; + if ((records.size() % DBCellRecord.BLOCK_SIZE) != 0) + size++; + return size; + } + + public int getRowBlockSize(int block) { + return 20 * getRowCountForBlock(block); + } + + /** Returns the number of physical rows within a block*/ + public int getRowCountForBlock(int block) { + int startIndex = block * DBCellRecord.BLOCK_SIZE; + int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1; + if (endIndex >= records.size()) + endIndex = records.size()-1; + + return endIndex-startIndex+1; + } + + /** Returns the physical row number of the first row in a block*/ + public int getStartRowNumberForBlock(int block) { + //JMH Given that we basically iterate through the rows in order, + //For a performance improvement, it would be better to return an instance of + //an iterator and use that instance throughout, rather than recreating one and + //having to move it to the right position. + int startIndex = block * DBCellRecord.BLOCK_SIZE; + Iterator rowIter = records.values().iterator(); + RowRecord row = null; + //Position the iterator at the start of the block + for (int i=0; i<=startIndex;i++) { + row = (RowRecord)rowIter.next(); + } + + return row.getRowNumber(); + } + + /** Returns the physical row number of the end row in a block*/ + public int getEndRowNumberForBlock(int block) { + int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1; + if (endIndex >= records.size()) + endIndex = records.size()-1; + + Iterator rowIter = records.values().iterator(); + RowRecord row = null; + for (int i=0; i<=endIndex;i++) { + row = (RowRecord)rowIter.next(); + } + return row.getRowNumber(); + } + + + /** Serializes a block of the rows */ + private int serializeRowBlock(final int block, final int offset, byte[] data) { + final int startIndex = block*DBCellRecord.BLOCK_SIZE; + final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; + + Iterator rowIterator = records.values().iterator(); + int pos = offset; + + //JMH Given that we basically iterate through the rows in order, + //For a performance improvement, it would be better to return an instance of + //an iterator and use that instance throughout, rather than recreating one and + //having to move it to the right position. + int i=0; + for (;i<startIndex;i++) + rowIterator.next(); + while(rowIterator.hasNext() && (i++ < endIndex)) { + RowRecord row = (RowRecord)rowIterator.next(); + pos += row.serialize(pos, data); + } + return pos - offset; + } - /* - * No need to go through all the records as we're just collecting RowRecords - - public int construct(int offset, List records) - { - int k = 0; - - for (k = offset; k < records.size(); k++) - { - Record rec = ( Record ) records.get(k); - - if (!rec.isInValueSection() && !(rec instanceof UnknownRecord)) - { - break; - } - if (rec.getSid() == RowRecord.sid) - { - insertRow(( RowRecord ) rec); - } - } - return k; + public int serialize(int offset, byte [] data) { + throw new RuntimeException("The serialize method that passes in cells should be used"); } - */ + /** * called by the class that is responsible for writing this sucker. @@ -129,14 +190,38 @@ * @return number of bytes written */ - public int serialize(int offset, byte [] data) + public int serialize(int offset, byte [] data, ValueRecordsAggregate cells) { - Iterator itr = records.values().iterator(); - int pos = offset; + int pos = offset; + + //DBCells are serialized before row records. + final int blockCount = getRowBlockCount(); + for (int block=0;block<blockCount;block++) { + //Serialize a block of rows. + //Hold onto the position of the first row in the block + final int rowStartPos = pos; + //Hold onto the size of this block that was serialized + final int rowBlockSize = serializeRowBlock(block, pos, data); + pos += rowBlockSize; + //Serialize a block of cells for those rows + final int startRowNumber = getStartRowNumberForBlock(block); + final int endRowNumber = getEndRowNumberForBlock(block); + DBCellRecord cellRecord = new DBCellRecord(); + //Note: Cell references start from the second row... + int cellRefOffset = (rowBlockSize-20); + for (int row=startRowNumber;row<=endRowNumber;row++) { + if (cells.rowHasCells(row)) { + final int rowCellSize = cells.serializeCellRow(row, pos, data); + pos += rowCellSize; + //Add the offset to the first cell for the row into the DBCellRecord. + cellRecord.addCellOffset((short)cellRefOffset); + cellRefOffset = rowCellSize; + } + } + //Calculate Offset from the start of a DBCellRecord to the first Row + cellRecord.setRowOffset(pos - rowStartPos); + pos += cellRecord.serialize(pos, data); - while (itr.hasNext()) - { - pos += (( Record ) itr.next()).serialize(pos, data); } return pos - offset; } 1.17 +344 -272 jakarta-poi/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java Index: ValueRecordsAggregate.java =================================================================== RCS file: /home/cvs/jakarta-poi/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java,v retrieving revision 1.16 retrieving revision 1.17 diff -u -r1.16 -r1.17 --- ValueRecordsAggregate.java 23 Aug 2004 08:52:28 -0000 1.16 +++ ValueRecordsAggregate.java 7 Oct 2004 03:37:16 -0000 1.17 @@ -1,272 +1,344 @@ - -/* ==================================================================== - Copyright 2002-2004 Apache Software Foundation - - Licensed 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 org.apache.poi.hssf.record.*; - -import java.util.Iterator; -import java.util.List; -import java.util.TreeMap; - -/** - * - * Aggregate value records together. Things are easier to handle that way. - * - * @author andy - * @author Glen Stampoultzis (glens at apache.org) - * @author Jason Height (jheight at chariot dot net dot au) - */ - -public class ValueRecordsAggregate - extends Record -{ - public final static short sid = -1000; - int firstcell = -1; - int lastcell = -1; - TreeMap records = null; -// int size = 0; - - /** Creates a new instance of ValueRecordsAggregate */ - - public ValueRecordsAggregate() - { - records = new TreeMap(); - } - - public void insertCell(CellValueRecordInterface cell) - { -/* if (records.get(cell) == null) - { - size += (( Record ) cell).getRecordSize(); - } - else - { - size += (( Record ) cell).getRecordSize() - - (( Record ) records.get(cell)).getRecordSize(); - }*/ - - // XYLocator xy = new XYLocator(cell.getRow(), cell.getColumn()); - Object o = records.put(cell, cell); - - if ((cell.getColumn() < firstcell) || (firstcell == -1)) - { - firstcell = cell.getColumn(); - } - if ((cell.getColumn() > lastcell) || (lastcell == -1)) - { - lastcell = cell.getColumn(); - } - } - - public void removeCell(CellValueRecordInterface cell) - { - // size -= (( Record ) cell).getRecordSize(); - - // XYLocator xy = new XYLocator(cell.getRow(), cell.getColumn()); - records.remove(cell); - } - - public int getPhysicalNumberOfCells() - { - return records.size(); - } - - public int getFirstCellNum() - { - return firstcell; - } - - public int getLastCellNum() - { - return lastcell; - } - - public int construct(int offset, List records) - { - int k = 0; - - FormulaRecordAggregate lastFormulaAggregate = null; - - for (k = offset; k < records.size(); k++) - { - Record rec = ( Record ) records.get(k); - - if (rec instanceof StringRecord == false && !rec.isInValueSection() && !(rec instanceof UnknownRecord)) - { - break; - } - if (rec instanceof FormulaRecord) - { - lastFormulaAggregate = new FormulaRecordAggregate((FormulaRecord)rec, null); - insertCell( lastFormulaAggregate ); - } - else if (rec instanceof StringRecord) - { - lastFormulaAggregate.setStringRecord((StringRecord)rec); - } - else if (rec instanceof SharedFormulaRecord) - { - //these follow the first formula in a group - lastFormulaAggregate.setSharedFormulaRecord((SharedFormulaRecord)rec); - } - else if (rec.isValue()) - { - insertCell(( CellValueRecordInterface ) rec); - } - } - return k; - } - - /** - * called by the class that is responsible for writing this sucker. - * Subclasses should implement this so that their data is passed back in a - * byte array. - * - * @param offset to begin writing at - * @param data byte array containing instance data - * @return number of bytes written - */ - - public int serialize(int offset, byte [] data) - { - Iterator itr = records.values().iterator(); - int pos = offset; - - while (itr.hasNext()) - { - pos += (( Record ) itr.next()).serialize(pos, data); - } - return pos - offset; - } - /** - * called by the constructor, should set class level fields. Should throw - * runtime exception for bad/icomplete data. - * - * @param data raw data - * @param size size of data - * @param offset of the record's data (provided a big array of the file) - */ - - protected void fillFields(byte [] data, short size, int offset) - { - } - - /** - * called by constructor, should throw runtime exception in the event of a - * record passed with a differing ID. - * - * @param id alleged id for this record - */ - - protected void validateSid(short id) - { - } - - /** - * return the non static version of the id for this record. - */ - - public short getSid() - { - return sid; - } - - public int getRecordSize() { - - int size = 0; - Iterator irecs = records.values().iterator(); - - while (irecs.hasNext()) { - size += (( Record ) irecs.next()).getRecordSize(); - } - - return size; -// return size; - } - - public Iterator getIterator() - { - return records.values().iterator(); - } - - /** Performs a deep clone of the record*/ - public Object clone() { - ValueRecordsAggregate rec = new ValueRecordsAggregate(); - for (Iterator valIter = getIterator(); valIter.hasNext();) { - CellValueRecordInterface val = (CellValueRecordInterface)((CellValueRecordInterface)valIter.next()).clone(); - rec.insertCell(val); - } - return rec; - } -} - -/* - * class XYLocator implements Comparable { - * private int row = 0; - * private int col = 0; - * public XYLocator(int row, int col) { - * this.row = row; - * this.col = col; - * } - * - * public int getRow() { - * return row; - * } - * - * public int getCol() { - * return col; - * } - * - * public int compareTo(Object obj) { - * XYLocator loc = (XYLocator)obj; - * - * if (this.getRow() == loc.getRow() && - * this.getCol() == loc.getCol() ) - * return 0; - * - * if (this.getRow() < loc.getRow()) - * return -1; - * - * if (this.getRow() > loc.getRow()) - * return 1; - * - * if (this.getCol() < loc.getCol()) - * return -1; - * - * if (this.getCol() > loc.getCol()) - * return 1; - * - * return -1; - * - * } - * - * public boolean equals(Object obj) { - * if (!(obj instanceof XYLocator)) return false; - * - * XYLocator loc = (XYLocator)obj; - * if (this.getRow() == loc.getRow() - * && - * this.getCol() == loc.getCol() - * ) return true; - * return false; - * } - * - * - * } - */ +
+/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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 org.apache.poi.hssf.record.*; + +import java.util.Iterator; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + + +/** + * + * Aggregate value records together. Things are easier to handle that way. + * + * @author andy + * @author Glen Stampoultzis (glens at apache.org) + * @author Jason Height (jheight at chariot dot net dot au) + */ + +public class ValueRecordsAggregate + extends Record +{ + public final static short sid = -1000; + int firstcell = -1; + int lastcell = -1; + TreeMap records = null; + + /** This class is used to find a row in the TreeMap. + * + * This instance of which is used by the rowHasCells method as the key. + */ + private class RowComparator implements CellValueRecordInterface, Comparable { + private int row; + + public void setRow(int row) { + this.row = row; + } + + public int compareTo(Object obj) { + CellValueRecordInterface cell = (CellValueRecordInterface) obj; + + if (row == cell.getRow()) { + return 0; + } + else if (row < cell.getRow()) { + return -1; + } + else if (row > cell.getRow()){ + return 1; + } + return -1; + } + public int getRow() { return row;} + public short getColumn() { return 0;} + public void setColumn(short col){} + public void setXFIndex(short xf){} + public short getXFIndex(){return 0;} + public boolean isBefore(CellValueRecordInterface i){ return false; } + public boolean isAfter(CellValueRecordInterface i){ return false; } + public boolean isEqual(CellValueRecordInterface i){ return false; } + public Object clone(){ return null;} + } + + /** + * Iterates the cell records that exist between the startRow and endRow (inclusive). + * + * User must ensure that hasNext & next are called insequence for correct + * operation. Could fix, but since this is only used internally to the + * ValueRecordsAggregate class there doesnt seem much point. + */ + private class RowCellIterator implements Iterator { + private int startRow; + private int endRow; + private Iterator internalIterator; + private CellValueRecordInterface atCell; + + public class RowCellComparator extends RowComparator { + public int compareTo(Object obj) { + CellValueRecordInterface cell = (CellValueRecordInterface) obj; + + if (getRow() == cell.getRow() && cell.getColumn() == 0) { + return 0; + } + else if (getRow() < cell.getRow()) { + return -1; + } + else if (getRow() > cell.getRow()){ + return 1; + } + if (cell.getColumn() > 0) + { + return -1; + } + if (cell.getColumn() < 0) + { + return 1; + } + return -1; + } + } + + private RowCellComparator rowCellCompare; + + + public RowCellIterator(int startRow, int endRow) { + this.startRow = startRow; + this.endRow = endRow; + rowCellCompare = new RowCellComparator(); + rowCellCompare.setRow(startRow); + } + + public boolean hasNext() { + if (internalIterator == null) { + internalIterator = records.tailMap(rowCellCompare).values().iterator(); + } + if (internalIterator.hasNext()) { + atCell = (CellValueRecordInterface) internalIterator.next(); + return (atCell.getRow() <= endRow); + } else return false; + } + + public Object next() { + return atCell; + } + + public void remove() { + //Do Nothing (Not called) + } + } + + //Only need a single instance of this class, but the row fields + //will probably change each use. Instance is only used in the rowHasCells method. + public final RowComparator compareRow = new RowComparator(); + + /** Creates a new instance of ValueRecordsAggregate */ + + public ValueRecordsAggregate() + { + records = new TreeMap(); + } + + public void insertCell(CellValueRecordInterface cell) + { + Object o = records.put(cell, cell); + + if ((cell.getColumn() < firstcell) || (firstcell == -1)) + { + firstcell = cell.getColumn(); + } + if ((cell.getColumn() > lastcell) || (lastcell == -1)) + { + lastcell = cell.getColumn(); + } + } + + public void removeCell(CellValueRecordInterface cell) + { + records.remove(cell); + } + + public int getPhysicalNumberOfCells() + { + return records.size(); + } + + public int getFirstCellNum() + { + return firstcell; + } + + public int getLastCellNum() + { + return lastcell; + } + + public int construct(int offset, List records) + { + int k = 0; + + FormulaRecordAggregate lastFormulaAggregate = null; + + for (k = offset; k < records.size(); k++) + { + Record rec = ( Record ) records.get(k); + + if (rec instanceof StringRecord == false && !rec.isInValueSection() && !(rec instanceof UnknownRecord)) + { + break; + } + if (rec instanceof FormulaRecord) + { + lastFormulaAggregate = new FormulaRecordAggregate((FormulaRecord)rec, null); + insertCell( lastFormulaAggregate ); + } + else if (rec instanceof StringRecord) + { + lastFormulaAggregate.setStringRecord((StringRecord)rec); + } + else if (rec instanceof SharedFormulaRecord) + { + //these follow the first formula in a group + lastFormulaAggregate.setSharedFormulaRecord((SharedFormulaRecord)rec); + } + else if (rec.isValue()) + { + insertCell(( CellValueRecordInterface ) rec); + } + } + return k; + } + + /** + * called by the class that is responsible for writing this sucker. + * Subclasses should implement this so that their data is passed back in a + * byte array. + * + * @param offset to begin writing at + * @param data byte array containing instance data + * @return number of bytes written + */ + + public int serialize(int offset, byte [] data) + { + throw new RuntimeException("This method shouldnt be called. ValueRecordsAggregate.serializeCellRow() should be called from RowRecordsAggregate."); + } + + /** Tallies a count of the size of the cell records + * that are attached to the rows in the range specified. + */ + public int getRowCellBlockSize(int startRow, int endRow) { + RowCellIterator itr = new RowCellIterator(startRow, endRow); + int size = 0; + while (itr.hasNext()) { + CellValueRecordInterface cell = (CellValueRecordInterface)itr.next(); + int row = cell.getRow(); + if (row > endRow) + break; + if ((row >=startRow) && (row <= endRow)) + size += ((Record)cell).getRecordSize(); + } + return size; + } + + /** Returns true if the row has cells attached to it */ + public boolean rowHasCells(int row) { + compareRow.setRow(row); + return records.containsKey(compareRow); + } + + /** Serializes the cells that are allocated to a certain row range*/ + public int serializeCellRow(final int row, int offset, byte [] data) + { + RowCellIterator itr = new RowCellIterator(row, row); + int pos = offset; + + while (itr.hasNext()) + { + CellValueRecordInterface cell = (CellValueRecordInterface)itr.next(); + if (cell.getRow() != row) + break; + pos += (( Record ) cell).serialize(pos, data); + } + return pos - offset; + } + + + /** + * called by the constructor, should set class level fields. Should throw + * runtime exception for bad/icomplete data. + * + * @param data raw data + * @param size size of data + * @param offset of the record's data (provided a big array of the file) + */ + + protected void fillFields(byte [] data, short size, int offset) + { + } + + /** + * called by constructor, should throw runtime exception in the event of a + * record passed with a differing ID. + * + * @param id alleged id for this record + */ + + protected void validateSid(short id) + { + } + + /** + * return the non static version of the id for this record. + */ + + public short getSid() + { + return sid; + } + + public int getRecordSize() { + + int size = 0; + Iterator irecs = records.values().iterator(); + + while (irecs.hasNext()) { + size += (( Record ) irecs.next()).getRecordSize(); + } + + return size; + } + + public Iterator getIterator() + { + return records.values().iterator(); + } + + /** Performs a deep clone of the record*/ + public Object clone() { + ValueRecordsAggregate rec = new ValueRecordsAggregate(); + for (Iterator valIter = getIterator(); valIter.hasNext();) { + CellValueRecordInterface val = (CellValueRecordInterface)((CellValueRecordInterface)valIter.next()).clone(); + rec.insertCell(val); + } + return rec; + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]