public final class RecordFactory {
private static final int NUM_RECORDS = 512;
- private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
+ private static final Class<?>[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
/**
* contains the classes for all the records we want to parse.<br/>
/**
* cache of the recordsToMap();
*/
- private static Map recordsMap = recordsToMap(recordClasses);
+ private static Map<Short, Constructor<? extends Record>> recordsMap = recordsToMap(recordClasses);
private static short[] _allKnownRecordSIDs;
if (record instanceof MulRKRecord) {
return convertRKRecords((MulRKRecord)record);
}
- if (record instanceof MulBlankRecord) {
- return convertMulBlankRecords((MulBlankRecord)record);
- }
return new Record[] { record, };
}
private static Record createSingleRecord(RecordInputStream in) {
- Constructor constructor = (Constructor) recordsMap.get(new Short(in.getSid()));
+ Constructor<? extends Record> constructor = recordsMap.get(new Short(in.getSid()));
if (constructor == null) {
return new UnknownRecord(in);
}
try {
- return (Record) constructor.newInstance(new Object[] { in });
+ return constructor.newInstance(new Object[] { in });
} catch (InvocationTargetException e) {
throw new RecordFormatException("Unable to construct record instance" , e.getTargetException());
} catch (IllegalArgumentException e) {
return mulRecs;
}
- /**
- * Converts a {@link MulBlankRecord} into an equivalent array of {@link BlankRecord}s
- */
- private static BlankRecord[] convertMulBlankRecords(MulBlankRecord mb) {
-
- BlankRecord[] mulRecs = new BlankRecord[mb.getNumColumns()];
- for (int k = 0; k < mb.getNumColumns(); k++) {
- BlankRecord br = new BlankRecord();
-
- br.setColumn((short) (k + mb.getFirstColumn()));
- br.setRow(mb.getRow());
- br.setXFIndex(mb.getXFAt(k));
- mulRecs[k] = br;
- }
- return mulRecs;
- }
-
/**
* @return an array of all the SIDS for all known records
*/
short[] results = new short[ recordsMap.size() ];
int i = 0;
- for (Iterator iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
- Short sid = (Short) iterator.next();
+ for (Iterator<Short> iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
+ Short sid = iterator.next();
results[i++] = sid.shortValue();
}
short sid;
Constructor<? extends Record> constructor;
try {
- sid = recClass.getField("sid").getShort(null);
+ sid = recClass.getField("sid").getShort(null);
constructor = recClass.getConstructor(CONSTRUCTOR_ARGS);
} catch (Exception illegalArgumentException) {
throw new RecordFormatException(
}
Short key = new Short(sid);
if (result.containsKey(key)) {
- Class prev = result.get(key).getDeclaringClass();
+ Class<? extends Record> prev = result.get(key).getDeclaringClass();
throw new RuntimeException("duplicate record sid 0x" + Integer.toHexString(sid).toUpperCase()
+ " for classes (" + recClass.getName() + ") and (" + prev.getName() + ")");
}
*
* @exception RecordFormatException on error processing the InputStream
*/
- public static List createRecords(InputStream in) throws RecordFormatException {
+ public static List<Record> createRecords(InputStream in) throws RecordFormatException {
List<Record> records = new ArrayList<Record>(NUM_RECORDS);
addAll(records, convertRKRecords((MulRKRecord)record));
continue;
}
- if (record instanceof MulBlankRecord) {
- addAll(records, convertMulBlankRecords((MulBlankRecord)record));
- continue;
- }
if (record.getSid() == DrawingGroupRecord.sid
&& lastRecord instanceof DrawingGroupRecord) {
package org.apache.poi.hssf.record.aggregates;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import org.apache.poi.hssf.model.RecordStream;
+import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.StringRecord;
*/
public final class ValueRecordsAggregate {
private static final int MAX_ROW_INDEX = 0XFFFF;
- private int firstcell = -1;
- private int lastcell = -1;
+ private static final int INDEX_NOT_SET = -1;
+ private int firstcell = INDEX_NOT_SET;
+ private int lastcell = INDEX_NOT_SET;
private CellValueRecordInterface[][] records;
/** Creates a new instance of ValueRecordsAggregate */
public ValueRecordsAggregate() {
- records = new CellValueRecordInterface[30][]; // We start with 30 Rows.
+ this(INDEX_NOT_SET, INDEX_NOT_SET, new CellValueRecordInterface[30][]); // We start with 30 Rows.
+ }
+ private ValueRecordsAggregate(int firstCellIx, int lastCellIx, CellValueRecordInterface[][] pRecords) {
+ firstcell = firstCellIx;
+ lastcell = lastCellIx;
+ records = pRecords;
}
public void insertCell(CellValueRecordInterface cell) {
}
rowCells[column] = cell;
- if ((column < firstcell) || (firstcell == -1)) {
+ if (column < firstcell || firstcell == INDEX_NOT_SET) {
firstcell = column;
}
- if ((column > lastcell) || (lastcell == -1)) {
+ if (column > lastcell || lastcell == INDEX_NOT_SET) {
lastcell = column;
}
}
+ " is outside the allowable range (0.." +MAX_ROW_INDEX + ")");
}
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)
+ // 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;
}
return lastcell;
}
+ public void addMultipleBlanks(MulBlankRecord mbr) {
+ for (int j = 0; j < mbr.getNumColumns(); j++) {
+ BlankRecord br = new BlankRecord();
+
+ br.setColumn(( short ) (j + mbr.getFirstColumn()));
+ br.setRow(mbr.getRow());
+ br.setXFIndex(mbr.getXFAt(j));
+ insertCell(br);
+ }
+ }
+
/**
* Processes a single cell value record
* @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet
FormulaRecord formulaRec = (FormulaRecord)rec;
// read optional cached text value
StringRecord cachedText;
- Class nextClass = rs.peekNextClass();
+ Class<? extends Record> nextClass = rs.peekNextClass();
if (nextClass == StringRecord.class) {
cachedText = (StringRecord) rs.getNext();
} else {
* that are attached to the rows in the range specified.
*/
public int getRowCellBlockSize(int startRow, int endRow) {
- MyIterator itr = new MyIterator(records, 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 += ((RecordBase) cell).getRecordSize();
- }
+ int result = 0;
+ for(int rowIx=startRow; rowIx<=endRow && rowIx<records.length; rowIx++) {
+ result += getRowSerializedSize(records[rowIx]);
}
- return size;
+ return result;
}
/** Returns true if the row has cells attached to it */
if (row >= records.length) {
return false;
}
- CellValueRecordInterface[] rowCells=records[row];
+ CellValueRecordInterface[] rowCells=records[row];
if(rowCells==null) return false;
for(int col=0;col<rowCells.length;col++) {
if(rowCells[col]!=null) return true;
return false;
}
- /** Serializes the cells that are allocated to a certain row range*/
- public int serializeCellRow(final int row, int offset, byte [] data)
- {
- MyIterator itr = new MyIterator(records, row, row);
- int pos = offset;
-
- while (itr.hasNext())
- {
- CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
- if (cell.getRow() != row)
- break;
- pos += (( RecordBase ) cell).serialize(pos, data);
+ private static int getRowSerializedSize(CellValueRecordInterface[] rowCells) {
+ if(rowCells == null) {
+ return 0;
+ }
+ int result = 0;
+ for (int i = 0; i < rowCells.length; i++) {
+ RecordBase cvr = (RecordBase) rowCells[i];
+ if(cvr == null) {
+ continue;
+ }
+ int nBlank = countBlanks(rowCells, i);
+ if (nBlank > 1) {
+ result += (10 + 2*nBlank);
+ i+=nBlank-1;
+ } else {
+ result += cvr.getRecordSize();
+ }
}
- return pos - offset;
+ return result;
}
public void visitCellsForRow(int rowIndex, RecordVisitor rv) {
- CellValueRecordInterface[] cellRecs = records[rowIndex];
- if (cellRecs != null) {
- for (int i = 0; i < cellRecs.length; i++) {
- CellValueRecordInterface cvr = cellRecs[i];
- if (cvr == null) {
- continue;
- }
- if (cvr instanceof RecordAggregate) {
- RecordAggregate agg = (RecordAggregate) cvr;
- agg.visitContainedRecords(rv);
- } else {
- Record rec = (Record) cvr;
- rv.visitRecord(rec);
- }
+ CellValueRecordInterface[] rowCells = records[rowIndex];
+ if(rowCells == null) {
+ throw new IllegalArgumentException("Row [" + rowIndex + "] is empty");
+ }
+
+
+ for (int i = 0; i < rowCells.length; i++) {
+ RecordBase cvr = (RecordBase) rowCells[i];
+ if(cvr == null) {
+ continue;
+ }
+ int nBlank = countBlanks(rowCells, i);
+ if (nBlank > 1) {
+ rv.visitRecord(createMBR(rowCells, i, nBlank));
+ i+=nBlank-1;
+ } else if (cvr instanceof RecordAggregate) {
+ RecordAggregate agg = (RecordAggregate) cvr;
+ agg.visitContainedRecords(rv);
+ } else {
+ rv.visitRecord((Record) cvr);
}
}
}
+ /**
+ * @return the number of <em>consecutive</em> {@link BlankRecord}s in the specified row
+ * starting from startIx.
+ */
+ private static int countBlanks(CellValueRecordInterface[] rowCellValues, int startIx) {
+ int i = startIx;
+ while(i < rowCellValues.length) {
+ CellValueRecordInterface cvr = rowCellValues[i];
+ if (!(cvr instanceof BlankRecord)) {
+ break;
+ }
+ i++;
+ }
+ return i - startIx;
+ }
+
+ private MulBlankRecord createMBR(CellValueRecordInterface[] cellValues, int startIx, int nBlank) {
+
+ short[] xfs = new short[nBlank];
+ for (int i = 0; i < xfs.length; i++) {
+ xfs[i] = ((BlankRecord)cellValues[startIx + i]).getXFIndex();
+ }
+ int rowIx = cellValues[startIx].getRow();
+ return new MulBlankRecord(rowIx, startIx, xfs);
+ }
+
public void updateFormulasAfterRowShift(FormulaShifter shifter, int currentExternSheetIndex) {
for (int i = 0; i < records.length; i++) {
CellValueRecordInterface[] rowCells = records[i];
}
}
+ /**
+ * Gets all the cell records contained in this aggregate.
+ * Note {@link BlankRecord}s appear separate (not in {@link MulBlankRecord}s).
+ */
public CellValueRecordInterface[] getValueRecords() {
List<CellValueRecordInterface> temp = new ArrayList<CellValueRecordInterface>();
- for (int i = 0; i < records.length; i++) {
- CellValueRecordInterface[] rowCells = records[i];
+ for (int rowIx = 0; rowIx < records.length; rowIx++) {
+ CellValueRecordInterface[] rowCells = records[rowIx];
if (rowCells == null) {
continue;
}
- for (int j = 0; j < rowCells.length; j++) {
- CellValueRecordInterface cell = rowCells[j];
+ for (int colIx = 0; colIx < rowCells.length; colIx++) {
+ CellValueRecordInterface cell = rowCells[colIx];
if (cell != null) {
temp.add(cell);
}
temp.toArray(result);
return result;
}
- public Iterator getIterator() {
- return new MyIterator(records);
- }
-
- private static final class MyIterator implements Iterator {
- private final CellValueRecordInterface[][] records;
- private short nextColumn = -1;
- private int nextRow, lastRow;
-
- public MyIterator(CellValueRecordInterface[][] pRecords) {
- this(pRecords, 0, pRecords.length - 1);
- }
-
- public MyIterator(CellValueRecordInterface[][] pRecords, int firstRow, int lastRow) {
- records = pRecords;
- this.nextRow = firstRow;
- this.lastRow = lastRow;
- findNext();
- }
-
- public boolean hasNext() {
- 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() {
- 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;
- }
- }
+ public Object clone() {
+ throw new RuntimeException("clone() should not be called. ValueRecordsAggregate should be copied via Sheet.cloneSheet()");
}
}
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.SharedFormulaRecord;
import org.apache.poi.hssf.record.WindowTwoRecord;
+import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.util.HexRead;
/**
* Tests for {@link ValueRecordsAggregate}
records.add(new WindowTwoRecord());
constructValueRecord(records);
- Iterator iterator = valueRecord.getIterator();
- RecordBase record = (RecordBase) iterator.next();
- assertNotNull( "Row contains a value", record );
- assertTrue( "First record is a FormulaRecordsAggregate", ( record instanceof FormulaRecordAggregate ) );
+ CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
//Ensure that the SharedFormulaRecord has been converted
- assertFalse( "SharedFormulaRecord is null", iterator.hasNext() );
+ assertEquals(1, cvrs.length);
+ CellValueRecordInterface record = cvrs[0];
+ assertNotNull( "Row contains a value", record );
+ assertTrue( "First record is a FormulaRecordsAggregate", ( record instanceof FormulaRecordAggregate ) );
}
- private void constructValueRecord(List records) {
+ private void constructValueRecord(List<Record> records) {
RowBlocksReader rbr = new RowBlocksReader(new RecordStream(records, 0));
SharedValueManager sfrh = rbr.getSharedFormulaManager();
RecordStream rs = rbr.getPlainRecordStream();
}
}
- private static List testData() {
+ private static List<Record> testData() {
List<Record> records = new ArrayList<Record>();
FormulaRecord formulaRecord = new FormulaRecord();
BlankRecord blankRecord = new BlankRecord();
}
public void testInsertCell() {
- Iterator iterator = valueRecord.getIterator();
- assertFalse( iterator.hasNext() );
+ CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
+ assertEquals(0, cvrs.length);
BlankRecord blankRecord = newBlankRecord();
valueRecord.insertCell( blankRecord );
- iterator = valueRecord.getIterator();
- assertTrue( iterator.hasNext() );
+ cvrs = valueRecord.getValueRecords();
+ assertEquals(1, cvrs.length);
}
public void testRemoveCell() {
valueRecord.insertCell( blankRecord1 );
BlankRecord blankRecord2 = newBlankRecord();
valueRecord.removeCell( blankRecord2 );
- Iterator iterator = valueRecord.getIterator();
- assertFalse( iterator.hasNext() );
+ CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
+ assertEquals(0, cvrs.length);
// removing an already empty cell just falls through
valueRecord.removeCell( blankRecord2 );
}
+
+ private static final class SerializerVisitor implements RecordVisitor {
+ private final byte[] _buf;
+ private int _writeIndex;
+ public SerializerVisitor(byte[] buf) {
+ _buf = buf;
+ _writeIndex = 0;
+
+ }
+ public void visitRecord(Record r) {
+ r.serialize(_writeIndex, _buf);
+ _writeIndex += r.getRecordSize();
+ }
+ public int getWriteIndex() {
+ return _writeIndex;
+ }
+ }
+
public void testSerialize() {
- byte[] actualArray = new byte[36];
- byte[] expectedArray = new byte[]
- {
- (byte)0x06, (byte)0x00, (byte)0x16, (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)0x01, (byte)0x02,
- (byte)0x06, (byte)0x00, (byte)0x02, (byte)0x00,
- (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00,
- };
- List records = testData();
+ byte[] expectedArray = HexRead.readFromString(""
+ + "06 00 16 00 " // Formula
+ + "01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
+ + "01 02 06 00 " // Blank
+ + "02 00 02 00 00 00");
+ byte[] actualArray = new byte[expectedArray.length];
+ List<Record> records = testData();
constructValueRecord(records);
- int bytesWritten = valueRecord.serializeCellRow(1, 0, actualArray );
- bytesWritten += valueRecord.serializeCellRow(2, bytesWritten, actualArray );
- assertEquals( 36, bytesWritten );
- for (int i = 0; i < 36; i++)
- assertEquals( expectedArray[i], actualArray[i] );
+
+ SerializerVisitor sv = new SerializerVisitor(actualArray);
+ valueRecord.visitCellsForRow(1, sv);
+ valueRecord.visitCellsForRow(2, sv);
+ assertEquals(actualArray.length, sv.getWriteIndex());
+ assertTrue(Arrays.equals(expectedArray, actualArray));
}
- private static BlankRecord newBlankRecord()
- {
+ private static BlankRecord newBlankRecord() {
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.setRow( row );
blankRecord.setColumn( (short) col );
}
/**
- * 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
- * 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/>
- * So it is important that the code which locates the SharedFormulaRecord for each
- * FormulaRecord does not allow matches across sheets.</br>
- *
- * Prior to bugzilla 44449 (Feb 2008), POI <tt>ValueRecordsAggregate.construct(int, List)</tt>
+ * So it is important that the code which locates the SharedFormulaRecord for each
+ * FormulaRecord does not allow matches across sheets.</br>
+ *
+ * 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
* behaviour is shown by this test.<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/>
* <ol>
* <li>A new spreadsheet was created in Excel (File | New | Blank Workbook).</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>
* </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/>
- * 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/>
- * 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() {
-
+
long actualCRC = getFileCRC(HSSFTestDataSamples.openSampleFileStream(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE));
long expectedCRC = 2277445406L;
if(actualCRC != expectedCRC) {
throw failUnexpectedTestFileChange();
}
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE);
-
+
HSSFSheet s = wb.getSheetAt(0); // Sheet1
-
+
String cellFormula;
cellFormula = getFormulaFromFirstCell(s, 0); // row "1"
// the problem is not observable in the first row of the shared formula
if(!cellFormula.equals("\"first formula\"")) {
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"
if(cellFormula.equals("\"second formula\"")) {
throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used).");
// A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord)
// should get hit during parsing of Sheet1.
// 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
- // peculiar condition we are testing for).
+ // It is easy to upset the test spreadsheet (for example re-saving will destroy the
+ // peculiar condition we are testing for).
throw new RuntimeException(msg);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
-
+
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);
}
}
}
+
+ /**
+ * Tests various manipulations of blank cells, to make sure that {@link MulBlankRecord}s
+ * are use appropriately
+ */
+ public void testMultipleBlanks() {
+ BlankRecord brA2 = newBlankRecord(0, 1);
+ BlankRecord brB2 = newBlankRecord(1, 1);
+ BlankRecord brC2 = newBlankRecord(2, 1);
+ BlankRecord brD2 = newBlankRecord(3, 1);
+ BlankRecord brE2 = newBlankRecord(4, 1);
+ BlankRecord brB3 = newBlankRecord(1, 2);
+ BlankRecord brC3 = newBlankRecord(2, 2);
+
+ valueRecord.insertCell(brA2);
+ valueRecord.insertCell(brB2);
+ valueRecord.insertCell(brD2);
+ confirmMulBlank(3, 1, 1);
+
+ valueRecord.insertCell(brC3);
+ confirmMulBlank(4, 1, 2);
+
+ valueRecord.insertCell(brB3);
+ valueRecord.insertCell(brE2);
+ confirmMulBlank(6, 3, 0);
+
+ valueRecord.insertCell(brC2);
+ confirmMulBlank(7, 2, 0);
+
+ valueRecord.removeCell(brA2);
+ confirmMulBlank(6, 2, 0);
+
+ valueRecord.removeCell(brC2);
+ confirmMulBlank(5, 2, 1);
+
+ valueRecord.removeCell(brC3);
+ confirmMulBlank(4, 1, 2);
+ }
+
+ private void confirmMulBlank(int expectedTotalBlankCells,
+ int expectedNumberOfMulBlankRecords, int expectedNumberOfSingleBlankRecords) {
+ // assumed row ranges set-up by caller:
+ final int firstRow = 1;
+ final int lastRow = 2;
+
+
+ final class BlankStats {
+ public int countBlankCells;
+ public int countMulBlankRecords;
+ public int countSingleBlankRecords;
+ }
+
+ final BlankStats bs = new BlankStats();
+ RecordVisitor rv = new RecordVisitor() {
+
+ public void visitRecord(Record r) {
+ if (r instanceof MulBlankRecord) {
+ MulBlankRecord mbr = (MulBlankRecord) r;
+ bs.countMulBlankRecords++;
+ bs.countBlankCells += mbr.getNumColumns();
+ } else if (r instanceof BlankRecord) {
+ bs.countSingleBlankRecords++;
+ bs.countBlankCells++;
+ }
+ }
+ };
+
+ for (int rowIx = firstRow; rowIx <=lastRow; rowIx++) {
+ if (valueRecord.rowHasCells(rowIx)) {
+ valueRecord.visitCellsForRow(rowIx, rv);
+ }
+ }
+ assertEquals(expectedTotalBlankCells, bs.countBlankCells);
+ assertEquals(expectedNumberOfMulBlankRecords, bs.countMulBlankRecords);
+ assertEquals(expectedNumberOfSingleBlankRecords, bs.countSingleBlankRecords);
+ }
}