Fix for bug 46312 - ValueRecordsAggregate should handle removal of new empty row

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@722206 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-12-01 19:59:46 +00:00
parent 886de704ff
commit 2014a596be
4 changed files with 515 additions and 468 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.5-beta5" date="2008-??-??"> <release version="3.5-beta5" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">46312 - Fixed ValueRecordsAggregate to handle removal of new empty row</action>
<action dev="POI-DEVELOPERS" type="add">46269 - Improved error message when attempting to read BIFF2 file</action> <action dev="POI-DEVELOPERS" type="add">46269 - Improved error message when attempting to read BIFF2 file</action>
<action dev="POI-DEVELOPERS" type="fix">46206 - Fixed Sheet to tolerate missing DIMENSION records</action> <action dev="POI-DEVELOPERS" type="fix">46206 - Fixed Sheet to tolerate missing DIMENSION records</action>
<action dev="POI-DEVELOPERS" type="add">46301 - added pivot table records: SXDI, SXVDEX, SXPI, SXIDSTM, SXVIEW, SXVD, SXVS, et al</action> <action dev="POI-DEVELOPERS" type="add">46301 - added pivot table records: SXDI, SXVDEX, SXPI, SXIDSTM, SXVIEW, SXVD, SXVS, et al</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.5-beta5" date="2008-??-??"> <release version="3.5-beta5" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">46312 - Fixed ValueRecordsAggregate to handle removal of new empty row</action>
<action dev="POI-DEVELOPERS" type="add">46269 - Improved error message when attempting to read BIFF2 file</action> <action dev="POI-DEVELOPERS" type="add">46269 - Improved error message when attempting to read BIFF2 file</action>
<action dev="POI-DEVELOPERS" type="fix">46206 - Fixed Sheet to tolerate missing DIMENSION records</action> <action dev="POI-DEVELOPERS" type="fix">46206 - Fixed Sheet to tolerate missing DIMENSION records</action>
<action dev="POI-DEVELOPERS" type="add">46301 - added pivot table records: SXDI, SXVDEX, SXPI, SXIDSTM, SXVIEW, SXVD, SXVS, et al</action> <action dev="POI-DEVELOPERS" type="add">46301 - added pivot table records: SXDI, SXVDEX, SXPI, SXIDSTM, SXVIEW, SXVD, SXVS, et al</action>

View File

@ -40,279 +40,291 @@ import org.apache.poi.hssf.record.formula.Ptg;
* @author Jason Height (jheight at chariot dot net dot au) * @author Jason Height (jheight at chariot dot net dot au)
*/ */
public final class ValueRecordsAggregate { public final class ValueRecordsAggregate {
private int firstcell = -1; private static final int MAX_ROW_INDEX = 0XFFFF;
private int lastcell = -1; private int firstcell = -1;
private CellValueRecordInterface[][] records; private int lastcell = -1;
private CellValueRecordInterface[][] records;
/** Creates a new instance of ValueRecordsAggregate */ /** Creates a new instance of ValueRecordsAggregate */
public ValueRecordsAggregate() public ValueRecordsAggregate() {
{ records = new CellValueRecordInterface[30][]; // We start with 30 Rows.
records = new CellValueRecordInterface[30][]; // We start with 30 Rows. }
}
public void insertCell(CellValueRecordInterface cell) { public void insertCell(CellValueRecordInterface cell) {
short column = cell.getColumn(); short column = cell.getColumn();
int row = cell.getRow(); int row = cell.getRow();
if (row >= records.length) { if (row >= records.length) {
CellValueRecordInterface[][] oldRecords = records; CellValueRecordInterface[][] oldRecords = records;
int newSize = oldRecords.length * 2; int newSize = oldRecords.length * 2;
if(newSize<row+1) newSize=row+1; if (newSize < row + 1)
records = new CellValueRecordInterface[newSize][]; newSize = row + 1;
System.arraycopy(oldRecords, 0, records, 0, oldRecords.length); records = new CellValueRecordInterface[newSize][];
} System.arraycopy(oldRecords, 0, records, 0, oldRecords.length);
CellValueRecordInterface[] rowCells = records[row]; }
if (rowCells == null) { CellValueRecordInterface[] rowCells = records[row];
int newSize = column + 1; if (rowCells == null) {
if(newSize<10) newSize=10; int newSize = column + 1;
rowCells = new CellValueRecordInterface[newSize]; if (newSize < 10)
records[row] = rowCells; newSize = 10;
} rowCells = new CellValueRecordInterface[newSize];
if (column >= rowCells.length) { records[row] = rowCells;
CellValueRecordInterface[] oldRowCells = rowCells; }
int newSize = oldRowCells.length * 2; if (column >= rowCells.length) {
if(newSize<column+1) newSize=column+1; CellValueRecordInterface[] oldRowCells = rowCells;
// if(newSize>257) newSize=257; // activate? int newSize = oldRowCells.length * 2;
rowCells = new CellValueRecordInterface[newSize]; if (newSize < column + 1)
System.arraycopy(oldRowCells, 0, rowCells, 0, oldRowCells.length); newSize = column + 1;
records[row] = rowCells; // if(newSize>257) newSize=257; // activate?
} rowCells = new CellValueRecordInterface[newSize];
rowCells[column] = cell; System.arraycopy(oldRowCells, 0, rowCells, 0, oldRowCells.length);
records[row] = rowCells;
}
rowCells[column] = cell;
if ((column < firstcell) || (firstcell == -1)) { if ((column < firstcell) || (firstcell == -1)) {
firstcell = column; firstcell = column;
} }
if ((column > lastcell) || (lastcell == -1)) { if ((column > lastcell) || (lastcell == -1)) {
lastcell = column; lastcell = column;
} }
} }
public void removeCell(CellValueRecordInterface cell) { public void removeCell(CellValueRecordInterface cell) {
if (cell == null) { if (cell == null) {
throw new IllegalArgumentException("cell must not be null"); throw new IllegalArgumentException("cell must not be null");
} }
int row = cell.getRow(); int row = cell.getRow();
if (row >= records.length) { if (row >= records.length) {
throw new RuntimeException("cell row is out of range"); throw new RuntimeException("cell row is out of range");
} }
CellValueRecordInterface[] rowCells = records[row]; CellValueRecordInterface[] rowCells = records[row];
if (rowCells == null) { if (rowCells == null) {
throw new RuntimeException("cell row is already empty"); throw new RuntimeException("cell row is already empty");
} }
short column = cell.getColumn(); short column = cell.getColumn();
if (column >= rowCells.length) { if (column >= rowCells.length) {
throw new RuntimeException("cell column is out of range"); throw new RuntimeException("cell column is out of range");
} }
rowCells[column] = null; rowCells[column] = null;
} }
public void removeAllCellsValuesForRow(int rowIndex) { public void removeAllCellsValuesForRow(int rowIndex) {
if (rowIndex >= records.length) { if (rowIndex < 0 || rowIndex > MAX_ROW_INDEX) {
throw new IllegalArgumentException("Specified rowIndex " + rowIndex throw new IllegalArgumentException("Specified rowIndex " + rowIndex
+ " is outside the allowable range (0.." +records.length + ")"); + " is outside the allowable range (0.." +MAX_ROW_INDEX + ")");
} }
records[rowIndex] = null; if (rowIndex >= records.length) {
} // this can happen when the client code has created a row,
// and then removes/replaces it before adding any cells. (see bug 46312)
return;
}
records[rowIndex] = null;
}
public int getPhysicalNumberOfCells() public int getPhysicalNumberOfCells() {
{ int count = 0;
int count=0; for (int r = 0; r < records.length; r++) {
for(int r=0;r<records.length;r++) { CellValueRecordInterface[] rowCells = records[r];
CellValueRecordInterface[] rowCells=records[r]; if (rowCells != null) {
if (rowCells != null) for (int c = 0; c < rowCells.length; c++) {
for(short c=0;c<rowCells.length;c++) { if (rowCells[c] != null)
if(rowCells[c]!=null) count++; count++;
} }
} }
return count; }
} return count;
}
public int getFirstCellNum() public int getFirstCellNum() {
{ return firstcell;
return firstcell; }
}
public int getLastCellNum() public int getLastCellNum() {
{ return lastcell;
return lastcell; }
}
/** /**
* Processes a single cell value record * Processes a single cell value record
* @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet * @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet
*/ */
public void construct(CellValueRecordInterface rec, RecordStream rs, SharedValueManager sfh) { public void construct(CellValueRecordInterface rec, RecordStream rs, SharedValueManager sfh) {
if (rec instanceof FormulaRecord) { if (rec instanceof FormulaRecord) {
FormulaRecord formulaRec = (FormulaRecord)rec; FormulaRecord formulaRec = (FormulaRecord)rec;
// read optional cached text value // read optional cached text value
StringRecord cachedText; StringRecord cachedText;
Class nextClass = rs.peekNextClass(); Class nextClass = rs.peekNextClass();
if (nextClass == StringRecord.class) { if (nextClass == StringRecord.class) {
cachedText = (StringRecord) rs.getNext(); cachedText = (StringRecord) rs.getNext();
} else { } else {
cachedText = null; cachedText = null;
} }
insertCell(new FormulaRecordAggregate(formulaRec, cachedText, sfh)); insertCell(new FormulaRecordAggregate(formulaRec, cachedText, sfh));
} else { } else {
insertCell(rec); insertCell(rec);
} }
} }
/** Tallies a count of the size of the cell records /** Tallies a count of the size of the cell records
* that are attached to the rows in the range specified. * that are attached to the rows in the range specified.
*/ */
public int getRowCellBlockSize(int startRow, int endRow) { public int getRowCellBlockSize(int startRow, int endRow) {
MyIterator itr = new MyIterator(startRow, endRow); MyIterator itr = new MyIterator(records, startRow, endRow);
int size = 0; int size = 0;
while (itr.hasNext()) { while (itr.hasNext()) {
CellValueRecordInterface cell = (CellValueRecordInterface)itr.next(); CellValueRecordInterface cell = (CellValueRecordInterface) itr.next();
int row = cell.getRow(); int row = cell.getRow();
if (row > endRow) if (row > endRow) {
break; break;
if ((row >=startRow) && (row <= endRow)) }
size += ((RecordBase)cell).getRecordSize(); if ((row >= startRow) && (row <= endRow)) {
} size += ((RecordBase) cell).getRecordSize();
return size; }
} }
return size;
}
/** Returns true if the row has cells attached to it */ /** Returns true if the row has cells attached to it */
public boolean rowHasCells(int row) { public boolean rowHasCells(int row) {
if (row > records.length-1) //previously this said row > records.length which means if if (row >= records.length) {
return false; // if records.length == 60 and I pass "60" here I get array out of bounds return false;
CellValueRecordInterface[] rowCells=records[row]; //because a 60 length array has the last index = 59 }
if(rowCells==null) return false; CellValueRecordInterface[] rowCells=records[row];
for(int col=0;col<rowCells.length;col++) { if(rowCells==null) return false;
if(rowCells[col]!=null) return true; for(int col=0;col<rowCells.length;col++) {
} if(rowCells[col]!=null) return true;
return false; }
} return false;
}
/** Serializes the cells that are allocated to a certain row range*/ /** Serializes the cells that are allocated to a certain row range*/
public int serializeCellRow(final int row, int offset, byte [] data) public int serializeCellRow(final int row, int offset, byte [] data)
{ {
MyIterator itr = new MyIterator(row, row); MyIterator itr = new MyIterator(records, row, row);
int pos = offset; int pos = offset;
while (itr.hasNext()) while (itr.hasNext())
{ {
CellValueRecordInterface cell = (CellValueRecordInterface)itr.next(); CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
if (cell.getRow() != row) if (cell.getRow() != row)
break; break;
pos += (( RecordBase ) cell).serialize(pos, data); pos += (( RecordBase ) cell).serialize(pos, data);
} }
return pos - offset; return pos - offset;
} }
public void visitCellsForRow(int rowIndex, RecordVisitor rv) { public void visitCellsForRow(int rowIndex, RecordVisitor rv) {
CellValueRecordInterface[] cellRecs = records[rowIndex]; CellValueRecordInterface[] cellRecs = records[rowIndex];
if (cellRecs != null) { if (cellRecs != null) {
for (int i = 0; i < cellRecs.length; i++) { for (int i = 0; i < cellRecs.length; i++) {
CellValueRecordInterface cvr = cellRecs[i]; CellValueRecordInterface cvr = cellRecs[i];
if (cvr == null) { if (cvr == null) {
continue; continue;
} }
if (cvr instanceof RecordAggregate) { if (cvr instanceof RecordAggregate) {
RecordAggregate agg = (RecordAggregate) cvr; RecordAggregate agg = (RecordAggregate) cvr;
agg.visitContainedRecords(rv); agg.visitContainedRecords(rv);
} else { } else {
Record rec = (Record) cvr; Record rec = (Record) cvr;
rv.visitRecord(rec); rv.visitRecord(rec);
} }
} }
} }
} }
public void updateFormulasAfterRowShift(FormulaShifter shifter, int currentExternSheetIndex) { public void updateFormulasAfterRowShift(FormulaShifter shifter, int currentExternSheetIndex) {
for (int i = 0; i < records.length; i++) { for (int i = 0; i < records.length; i++) {
CellValueRecordInterface[] rowCells = records[i]; CellValueRecordInterface[] rowCells = records[i];
if (rowCells == null) { if (rowCells == null) {
continue; continue;
} }
for (int j = 0; j < rowCells.length; j++) { for (int j = 0; j < rowCells.length; j++) {
CellValueRecordInterface cell = rowCells[j]; CellValueRecordInterface cell = rowCells[j];
if (cell instanceof FormulaRecordAggregate) { if (cell instanceof FormulaRecordAggregate) {
FormulaRecord fr = ((FormulaRecordAggregate)cell).getFormulaRecord(); FormulaRecord fr = ((FormulaRecordAggregate)cell).getFormulaRecord();
Ptg[] ptgs = fr.getParsedExpression(); // needs clone() inside this getter? Ptg[] ptgs = fr.getParsedExpression(); // needs clone() inside this getter?
if (shifter.adjustFormula(ptgs, currentExternSheetIndex)) { if (shifter.adjustFormula(ptgs, currentExternSheetIndex)) {
fr.setParsedExpression(ptgs); fr.setParsedExpression(ptgs);
} }
} }
} }
} }
} }
public CellValueRecordInterface[] getValueRecords() { public CellValueRecordInterface[] getValueRecords() {
List temp = new ArrayList(); List<CellValueRecordInterface> temp = new ArrayList<CellValueRecordInterface>();
for (int i = 0; i < records.length; i++) { for (int i = 0; i < records.length; i++) {
CellValueRecordInterface[] rowCells = records[i]; CellValueRecordInterface[] rowCells = records[i];
if (rowCells == null) { if (rowCells == null) {
continue; continue;
} }
for (int j = 0; j < rowCells.length; j++) { for (int j = 0; j < rowCells.length; j++) {
CellValueRecordInterface cell = rowCells[j]; CellValueRecordInterface cell = rowCells[j];
if (cell != null) { if (cell != null) {
temp.add(cell); temp.add(cell);
} }
} }
} }
CellValueRecordInterface[] result = new CellValueRecordInterface[temp.size()]; CellValueRecordInterface[] result = new CellValueRecordInterface[temp.size()];
temp.toArray(result); temp.toArray(result);
return result; return result;
} }
public Iterator getIterator() public Iterator getIterator() {
{ return new MyIterator(records);
return new MyIterator(); }
}
private final class MyIterator implements Iterator { private static final class MyIterator implements Iterator {
short nextColumn=-1; private final CellValueRecordInterface[][] records;
int nextRow,lastRow; private short nextColumn = -1;
private int nextRow, lastRow;
public MyIterator() public MyIterator(CellValueRecordInterface[][] pRecords) {
{ this(pRecords, 0, pRecords.length - 1);
this.nextRow=0; }
this.lastRow=records.length-1;
findNext();
}
public MyIterator(int firstRow,int lastRow) public MyIterator(CellValueRecordInterface[][] pRecords, int firstRow, int lastRow) {
{ records = pRecords;
this.nextRow=firstRow; this.nextRow = firstRow;
this.lastRow=lastRow; this.lastRow = lastRow;
findNext(); findNext();
} }
public boolean hasNext() { public boolean hasNext() {
return nextRow<=lastRow; return nextRow <= lastRow;
} }
public Object next() {
Object o=records[nextRow][nextColumn];
findNext();
return o;
}
public void remove() {
throw new UnsupportedOperationException("gibt's noch nicht");
}
private void findNext() { public Object next() {
nextColumn++; Object o = records[nextRow][nextColumn];
for(;nextRow<=lastRow;nextRow++) { findNext();
//previously this threw array out of bounds... return o;
CellValueRecordInterface[] rowCells=(nextRow < records.length) ? records[nextRow] : null; }
if(rowCells==null) { // This row is empty
nextColumn=0;
continue;
}
for(;nextColumn<rowCells.length;nextColumn++) {
if(rowCells[nextColumn]!=null) return;
}
nextColumn=0;
}
}
} public void remove() {
throw new UnsupportedOperationException("gibt's noch nicht");
}
private void findNext() {
nextColumn++;
for (; nextRow <= lastRow; nextRow++) {
// previously this threw array out of bounds...
CellValueRecordInterface[] rowCells = (nextRow < records.length) ? records[nextRow]
: null;
if (rowCells == null) { // This row is empty
nextColumn = 0;
continue;
}
for (; nextColumn < rowCells.length; nextColumn++) {
if (rowCells[nextColumn] != null)
return;
}
nextColumn = 0;
}
}
}
} }

View File

@ -37,249 +37,282 @@ import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordBase; import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.SharedFormulaRecord;
import org.apache.poi.hssf.record.WindowTwoRecord; import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Tests for {@link ValueRecordsAggregate}
*/
public final class TestValueRecordsAggregate extends TestCase { public final class TestValueRecordsAggregate extends TestCase {
private static final String ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE = "AbnormalSharedFormulaFlag.xls"; private static final String ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE = "AbnormalSharedFormulaFlag.xls";
private final ValueRecordsAggregate valueRecord = new ValueRecordsAggregate(); private final ValueRecordsAggregate valueRecord = new ValueRecordsAggregate();
/** /**
* Make sure the shared formula DOESNT makes it to the FormulaRecordAggregate when being parsed * Make sure the shared formula DOESNT makes it to the FormulaRecordAggregate when being parsed
* as part of the value records * as part of the value records
*/ */
public void testSharedFormula() { public void testSharedFormula() {
List records = new ArrayList(); List<Record> records = new ArrayList<Record>();
records.add( new FormulaRecord() ); records.add(new FormulaRecord());
records.add( new SharedFormulaRecord() ); records.add(new SharedFormulaRecord());
records.add(new WindowTwoRecord()); records.add(new WindowTwoRecord());
constructValueRecord(records); constructValueRecord(records);
Iterator iterator = valueRecord.getIterator(); Iterator iterator = valueRecord.getIterator();
RecordBase record = (RecordBase) iterator.next(); RecordBase record = (RecordBase) iterator.next();
assertNotNull( "Row contains a value", record ); assertNotNull( "Row contains a value", record );
assertTrue( "First record is a FormulaRecordsAggregate", ( record instanceof FormulaRecordAggregate ) ); assertTrue( "First record is a FormulaRecordsAggregate", ( record instanceof FormulaRecordAggregate ) );
//Ensure that the SharedFormulaRecord has been converted //Ensure that the SharedFormulaRecord has been converted
assertFalse( "SharedFormulaRecord is null", iterator.hasNext() ); assertFalse( "SharedFormulaRecord is null", iterator.hasNext() );
} }
private void constructValueRecord(List records) { private void constructValueRecord(List records) {
RowBlocksReader rbr = new RowBlocksReader(new RecordStream(records, 0)); RowBlocksReader rbr = new RowBlocksReader(new RecordStream(records, 0));
SharedValueManager sfrh = rbr.getSharedFormulaManager(); SharedValueManager sfrh = rbr.getSharedFormulaManager();
RecordStream rs = rbr.getPlainRecordStream(); RecordStream rs = rbr.getPlainRecordStream();
while(rs.hasNext()) { while(rs.hasNext()) {
Record rec = rs.getNext(); Record rec = rs.getNext();
valueRecord.construct((CellValueRecordInterface)rec, rs, sfrh); valueRecord.construct((CellValueRecordInterface)rec, rs, sfrh);
} }
} }
private static List testData() { private static List testData() {
List records = new ArrayList(); List<Record> records = new ArrayList<Record>();
FormulaRecord formulaRecord = new FormulaRecord(); FormulaRecord formulaRecord = new FormulaRecord();
BlankRecord blankRecord = new BlankRecord(); BlankRecord blankRecord = new BlankRecord();
formulaRecord.setRow( 1 ); formulaRecord.setRow(1);
formulaRecord.setColumn( (short) 1 ); formulaRecord.setColumn((short) 1);
blankRecord.setRow( 2 ); blankRecord.setRow(2);
blankRecord.setColumn( (short) 2 ); blankRecord.setColumn((short) 2);
records.add( formulaRecord ); records.add(formulaRecord);
records.add( blankRecord ); records.add(blankRecord);
records.add(new WindowTwoRecord()); records.add(new WindowTwoRecord());
return records; return records;
} }
public void testInsertCell() { public void testInsertCell() {
Iterator iterator = valueRecord.getIterator(); Iterator iterator = valueRecord.getIterator();
assertFalse( iterator.hasNext() ); assertFalse( iterator.hasNext() );
BlankRecord blankRecord = newBlankRecord(); BlankRecord blankRecord = newBlankRecord();
valueRecord.insertCell( blankRecord ); valueRecord.insertCell( blankRecord );
iterator = valueRecord.getIterator(); iterator = valueRecord.getIterator();
assertTrue( iterator.hasNext() ); assertTrue( iterator.hasNext() );
} }
public void testRemoveCell() { public void testRemoveCell() {
BlankRecord blankRecord1 = newBlankRecord(); BlankRecord blankRecord1 = newBlankRecord();
valueRecord.insertCell( blankRecord1 ); valueRecord.insertCell( blankRecord1 );
BlankRecord blankRecord2 = newBlankRecord(); BlankRecord blankRecord2 = newBlankRecord();
valueRecord.removeCell( blankRecord2 ); valueRecord.removeCell( blankRecord2 );
Iterator iterator = valueRecord.getIterator(); Iterator iterator = valueRecord.getIterator();
assertFalse( iterator.hasNext() ); assertFalse( iterator.hasNext() );
// removing an already empty cell just falls through // removing an already empty cell just falls through
valueRecord.removeCell( blankRecord2 ); valueRecord.removeCell( blankRecord2 );
} }
public void testGetPhysicalNumberOfCells() { public void testGetPhysicalNumberOfCells() {
assertEquals(0, valueRecord.getPhysicalNumberOfCells()); assertEquals(0, valueRecord.getPhysicalNumberOfCells());
BlankRecord blankRecord1 = newBlankRecord(); BlankRecord blankRecord1 = newBlankRecord();
valueRecord.insertCell( blankRecord1 ); valueRecord.insertCell( blankRecord1 );
assertEquals(1, valueRecord.getPhysicalNumberOfCells()); assertEquals(1, valueRecord.getPhysicalNumberOfCells());
valueRecord.removeCell( blankRecord1 ); valueRecord.removeCell( blankRecord1 );
assertEquals(0, valueRecord.getPhysicalNumberOfCells()); assertEquals(0, valueRecord.getPhysicalNumberOfCells());
} }
public void testGetFirstCellNum() { public void testGetFirstCellNum() {
assertEquals( -1, valueRecord.getFirstCellNum() ); assertEquals( -1, valueRecord.getFirstCellNum() );
valueRecord.insertCell( newBlankRecord( 2, 2 ) ); valueRecord.insertCell( newBlankRecord( 2, 2 ) );
assertEquals( 2, valueRecord.getFirstCellNum() ); assertEquals( 2, valueRecord.getFirstCellNum() );
valueRecord.insertCell( newBlankRecord( 3, 3 ) ); valueRecord.insertCell( newBlankRecord( 3, 3 ) );
assertEquals( 2, valueRecord.getFirstCellNum() ); assertEquals( 2, valueRecord.getFirstCellNum() );
// Note: Removal doesn't currently reset the first column. It probably should but it doesn't. // Note: Removal doesn't currently reset the first column. It probably should but it doesn't.
valueRecord.removeCell( newBlankRecord( 2, 2 ) ); valueRecord.removeCell( newBlankRecord( 2, 2 ) );
assertEquals( 2, valueRecord.getFirstCellNum() ); assertEquals( 2, valueRecord.getFirstCellNum() );
} }
public void testGetLastCellNum() { public void testGetLastCellNum() {
assertEquals( -1, valueRecord.getLastCellNum() ); assertEquals( -1, valueRecord.getLastCellNum() );
valueRecord.insertCell( newBlankRecord( 2, 2 ) ); valueRecord.insertCell( newBlankRecord( 2, 2 ) );
assertEquals( 2, valueRecord.getLastCellNum() ); assertEquals( 2, valueRecord.getLastCellNum() );
valueRecord.insertCell( newBlankRecord( 3, 3 ) ); valueRecord.insertCell( newBlankRecord( 3, 3 ) );
assertEquals( 3, valueRecord.getLastCellNum() ); assertEquals( 3, valueRecord.getLastCellNum() );
// Note: Removal doesn't currently reset the last column. It probably should but it doesn't. // Note: Removal doesn't currently reset the last column. It probably should but it doesn't.
valueRecord.removeCell( newBlankRecord( 3, 3 ) ); valueRecord.removeCell( newBlankRecord( 3, 3 ) );
assertEquals( 3, valueRecord.getLastCellNum() ); assertEquals( 3, valueRecord.getLastCellNum() );
} }
public void testSerialize() { public void testSerialize() {
byte[] actualArray = new byte[36]; byte[] actualArray = new byte[36];
byte[] expectedArray = new byte[] byte[] expectedArray = new byte[]
{ {
(byte)0x06, (byte)0x00, (byte)0x16, (byte)0x00, (byte)0x06, (byte)0x00, (byte)0x16, (byte)0x00,
(byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x02,
(byte)0x06, (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x06, (byte)0x00, (byte)0x02, (byte)0x00,
(byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00,
}; };
List records = testData(); List records = testData();
constructValueRecord(records); constructValueRecord(records);
int bytesWritten = valueRecord.serializeCellRow(1, 0, actualArray ); int bytesWritten = valueRecord.serializeCellRow(1, 0, actualArray );
bytesWritten += valueRecord.serializeCellRow(2, bytesWritten, actualArray ); bytesWritten += valueRecord.serializeCellRow(2, bytesWritten, actualArray );
assertEquals( 36, bytesWritten ); assertEquals( 36, bytesWritten );
for (int i = 0; i < 36; i++) for (int i = 0; i < 36; i++)
assertEquals( expectedArray[i], actualArray[i] ); assertEquals( expectedArray[i], actualArray[i] );
} }
private static BlankRecord newBlankRecord() private static BlankRecord newBlankRecord()
{ {
return newBlankRecord( 2, 2 ); return newBlankRecord( 2, 2 );
} }
private static BlankRecord newBlankRecord( int col, int row) private static BlankRecord newBlankRecord( int col, int row)
{ {
BlankRecord blankRecord = new BlankRecord(); BlankRecord blankRecord = new BlankRecord();
blankRecord.setRow( row ); blankRecord.setRow( row );
blankRecord.setColumn( (short) col ); blankRecord.setColumn( (short) col );
return blankRecord; return blankRecord;
} }
/** /**
* Sometimes the 'shared formula' flag (<tt>FormulaRecord.isSharedFormula()</tt>) is set when * Sometimes the 'shared formula' flag (<tt>FormulaRecord.isSharedFormula()</tt>) is set when
* there is no corresponding SharedFormulaRecord available. SharedFormulaRecord definitions do * there is no corresponding SharedFormulaRecord available. SharedFormulaRecord definitions do
* not span multiple sheets. They are are only defined within a sheet, and thus they do not * not span multiple sheets. They are are only defined within a sheet, and thus they do not
* have a sheet index field (only row and column range fields).<br/> * have a sheet index field (only row and column range fields).<br/>
* So it is important that the code which locates the SharedFormulaRecord for each * So it is important that the code which locates the SharedFormulaRecord for each
* FormulaRecord does not allow matches across sheets.</br> * FormulaRecord does not allow matches across sheets.</br>
* *
* Prior to bugzilla 44449 (Feb 2008), POI <tt>ValueRecordsAggregate.construct(int, List)</tt> * Prior to bugzilla 44449 (Feb 2008), POI <tt>ValueRecordsAggregate.construct(int, List)</tt>
* allowed <tt>SharedFormulaRecord</tt>s to be erroneously used across sheets. That incorrect * allowed <tt>SharedFormulaRecord</tt>s to be erroneously used across sheets. That incorrect
* behaviour is shown by this test.<p/> * behaviour is shown by this test.<p/>
* *
* <b>Notes on how to produce the test spreadsheet</b>:</p> * <b>Notes on how to produce the test spreadsheet</b>:</p>
* The setup for this test (AbnormalSharedFormulaFlag.xls) is rather fragile, insomuchas * The setup for this test (AbnormalSharedFormulaFlag.xls) is rather fragile, insomuchas
* re-saving the file (either with Excel or POI) clears the flag.<br/> * re-saving the file (either with Excel or POI) clears the flag.<br/>
* <ol> * <ol>
* <li>A new spreadsheet was created in Excel (File | New | Blank Workbook).</li> * <li>A new spreadsheet was created in Excel (File | New | Blank Workbook).</li>
* <li>Sheet3 was deleted.</li> * <li>Sheet3 was deleted.</li>
* <li>Sheet2!A1 formula was set to '="second formula"', and fill-dragged through A1:A8.</li> * <li>Sheet2!A1 formula was set to '="second formula"', and fill-dragged through A1:A8.</li>
* <li>Sheet1!A1 formula was set to '="first formula"', and also fill-dragged through A1:A8.</li> * <li>Sheet1!A1 formula was set to '="first formula"', and also fill-dragged through A1:A8.</li>
* <li>Four rows on Sheet1 "5" through "8" were deleted ('delete rows' alt-E D, not 'clear' Del).</li> * <li>Four rows on Sheet1 "5" through "8" were deleted ('delete rows' alt-E D, not 'clear' Del).</li>
* <li>The spreadsheet was saved as AbnormalSharedFormulaFlag.xls.</li> * <li>The spreadsheet was saved as AbnormalSharedFormulaFlag.xls.</li>
* </ol> * </ol>
* Prior to the row delete action the spreadsheet has two <tt>SharedFormulaRecord</tt>s. One * Prior to the row delete action the spreadsheet has two <tt>SharedFormulaRecord</tt>s. One
* for each sheet. To expose the bug, the shared formulas have been made to overlap.<br/> * for each sheet. To expose the bug, the shared formulas have been made to overlap.<br/>
* The row delete action (as described here) seems to to delete the * The row delete action (as described here) seems to to delete the
* <tt>SharedFormulaRecord</tt> from Sheet1 (but not clear the 'shared formula' flags.<br/> * <tt>SharedFormulaRecord</tt> from Sheet1 (but not clear the 'shared formula' flags.<br/>
* There are other variations on this theme to create the same effect. * There are other variations on this theme to create the same effect.
* *
*/ */
public void testSpuriousSharedFormulaFlag() { public void testSpuriousSharedFormulaFlag() {
long actualCRC = getFileCRC(HSSFTestDataSamples.openSampleFileStream(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE)); long actualCRC = getFileCRC(HSSFTestDataSamples.openSampleFileStream(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE));
long expectedCRC = 2277445406L; long expectedCRC = 2277445406L;
if(actualCRC != expectedCRC) { if(actualCRC != expectedCRC) {
System.err.println("Expected crc " + expectedCRC + " but got " + actualCRC); System.err.println("Expected crc " + expectedCRC + " but got " + actualCRC);
throw failUnexpectedTestFileChange(); throw failUnexpectedTestFileChange();
} }
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE); HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE);
HSSFSheet s = wb.getSheetAt(0); // Sheet1 HSSFSheet s = wb.getSheetAt(0); // Sheet1
String cellFormula; String cellFormula;
cellFormula = getFormulaFromFirstCell(s, 0); // row "1" cellFormula = getFormulaFromFirstCell(s, 0); // row "1"
// the problem is not observable in the first row of the shared formula // the problem is not observable in the first row of the shared formula
if(!cellFormula.equals("\"first formula\"")) { if(!cellFormula.equals("\"first formula\"")) {
throw new RuntimeException("Something else wrong with this test case"); throw new RuntimeException("Something else wrong with this test case");
} }
// but the problem is observable in rows 2,3,4 // but the problem is observable in rows 2,3,4
cellFormula = getFormulaFromFirstCell(s, 1); // row "2" cellFormula = getFormulaFromFirstCell(s, 1); // row "2"
if(cellFormula.equals("\"second formula\"")) { if(cellFormula.equals("\"second formula\"")) {
throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used)."); throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used).");
} }
if(!cellFormula.equals("\"first formula\"")) { if(!cellFormula.equals("\"first formula\"")) {
throw new RuntimeException("Something else wrong with this test case"); throw new RuntimeException("Something else wrong with this test case");
} }
} }
private static String getFormulaFromFirstCell(HSSFSheet s, int rowIx) { private static String getFormulaFromFirstCell(HSSFSheet s, int rowIx) {
return s.getRow(rowIx).getCell(0).getCellFormula(); return s.getRow(rowIx).getCell(0).getCellFormula();
} }
/** /**
* If someone opened this particular test file in Excel and saved it, the peculiar condition * If someone opened this particular test file in Excel and saved it, the peculiar condition
* which causes the target bug would probably disappear. This test would then just succeed * which causes the target bug would probably disappear. This test would then just succeed
* regardless of whether the fix was present. So a CRC check is performed to make it less easy * regardless of whether the fix was present. So a CRC check is performed to make it less easy
* for that to occur. * for that to occur.
*/ */
private static RuntimeException failUnexpectedTestFileChange() { private static RuntimeException failUnexpectedTestFileChange() {
String msg = "Test file '" + ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE + "' has changed. " String msg = "Test file '" + ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE + "' has changed. "
+ "This junit may not be properly testing for the target bug. " + "This junit may not be properly testing for the target bug. "
+ "Either revert the test file or ensure that the new version " + "Either revert the test file or ensure that the new version "
+ "has the right characteristics to test the target bug."; + "has the right characteristics to test the target bug.";
// A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord) // A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord)
// should get hit during parsing of Sheet1. // should get hit during parsing of Sheet1.
// If the test spreadsheet is created as directed, this condition should occur. // If the test spreadsheet is created as directed, this condition should occur.
// It is easy to upset the test spreadsheet (for example re-saving will destroy the // It is easy to upset the test spreadsheet (for example re-saving will destroy the
// peculiar condition we are testing for). // peculiar condition we are testing for).
throw new RuntimeException(msg); throw new RuntimeException(msg);
} }
/** /**
* gets a CRC checksum for the content of a file * gets a CRC checksum for the content of a file
*/ */
private static long getFileCRC(InputStream is) { private static long getFileCRC(InputStream is) {
CRC32 crc = new CRC32(); CRC32 crc = new CRC32();
byte[] buf = new byte[2048]; byte[] buf = new byte[2048];
try { try {
while(true) { while(true) {
int bytesRead = is.read(buf); int bytesRead = is.read(buf);
if(bytesRead < 1) { if(bytesRead < 1) {
break; break;
} }
crc.update(buf, 0, bytesRead); crc.update(buf, 0, bytesRead);
} }
is.close(); is.close();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return crc.getValue(); return crc.getValue();
} }
public void testRemoveNewRow_bug46312() {
// To make bug occur, rowIndex needs to be >= ValueRecordsAggregate.records.length
int rowIndex = 30;
ValueRecordsAggregate vra = new ValueRecordsAggregate();
try {
vra.removeAllCellsValuesForRow(rowIndex);
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("Specified rowIndex 30 is outside the allowable range (0..30)")) {
throw new AssertionFailedError("Identified bug 46312");
}
throw e;
}
if (false) { // same bug as demonstrated through usermodel API
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
HSSFRow row = sheet.createRow(rowIndex);
if (false) { // must not add any cells to the new row if we want to see the bug
row.createCell(0); // this causes ValueRecordsAggregate.records to auto-extend
}
try {
sheet.createRow(rowIndex);
} catch (IllegalArgumentException e) {
throw new AssertionFailedError("Identified bug 46312");
}
}
}
} }