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");
      }
    }
  }
 /**
  * @param rs record stream with all {@link SharedFormulaRecord} {@link ArrayRecord}, {@link
  *     TableRecord} {@link MergeCellsRecord} Records removed
  * @param svm an initialised {@link SharedValueManager} (from the shared formula, array and table
  *     records of the current sheet). Never <code>null</code>.
  */
 public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) {
   this(svm);
   while (rs.hasNext()) {
     Record rec = rs.getNext();
     switch (rec.getSid()) {
       case RowRecord.sid:
         insertRow((RowRecord) rec);
         continue;
       case DBCellRecord.sid:
         // end of 'Row Block'.  Should only occur after cell records
         // ignore DBCELL records because POI generates them upon re-serialization
         continue;
     }
     if (rec instanceof UnknownRecord) {
       // might need to keep track of where exactly these belong
       addUnknownRecord(rec);
       while (rs.peekNextSid() == ContinueRecord.sid) {
         addUnknownRecord(rs.getNext());
       }
       continue;
     }
     if (rec instanceof MulBlankRecord) {
       _valuesAgg.addMultipleBlanks((MulBlankRecord) rec);
       continue;
     }
     if (!(rec instanceof CellValueRecordInterface)) {
       throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
     }
     _valuesAgg.construct((CellValueRecordInterface) rec, rs, svm);
   }
 }
 public void testGetPhysicalNumberOfCells() {
   assertEquals(0, valueRecord.getPhysicalNumberOfCells());
   BlankRecord blankRecord1 = newBlankRecord();
   valueRecord.insertCell(blankRecord1);
   assertEquals(1, valueRecord.getPhysicalNumberOfCells());
   valueRecord.removeCell(blankRecord1);
   assertEquals(0, valueRecord.getPhysicalNumberOfCells());
 }
 public DimensionsRecord createDimensions() {
   DimensionsRecord result = new DimensionsRecord();
   result.setFirstRow(_firstrow);
   result.setLastRow(_lastrow);
   result.setFirstCol((short) _valuesAgg.getFirstCellNum());
   result.setLastCol((short) _valuesAgg.getLastCellNum());
   return result;
 }
  @SuppressWarnings(
      "deprecation") // uses deprecated {@link ValueRecordsAggregate#getValueRecords()}
  public void testInsertCell() {
    CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
    assertEquals(0, cvrs.length);

    BlankRecord blankRecord = newBlankRecord();
    valueRecord.insertCell(blankRecord);
    cvrs = valueRecord.getValueRecords();
    assertEquals(1, cvrs.length);
  }
  @SuppressWarnings(
      "deprecation") // uses deprecated {@link ValueRecordsAggregate#getValueRecords()}
  public void testRemoveCell() {
    BlankRecord blankRecord1 = newBlankRecord();
    valueRecord.insertCell(blankRecord1);
    BlankRecord blankRecord2 = newBlankRecord();
    valueRecord.removeCell(blankRecord2);
    CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
    assertEquals(0, cvrs.length);

    // removing an already empty cell just falls through
    valueRecord.removeCell(blankRecord2);
  }
  /**
   * 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);
  }
  public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) {
    IndexRecord result = new IndexRecord();
    result.setFirstRow(_firstrow);
    result.setLastRowAdd1(_lastrow + 1);
    // Calculate the size of the records from the end of the BOF
    // and up to the RowRecordsAggregate...

    // Add the references to the DBCells in the IndexRecord (one for each block)
    // Note: The offsets are relative to the Workbook BOF. Assume that this is
    // 0 for now.....

    int blockCount = getRowBlockCount();
    // Calculate the size of this IndexRecord
    int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount);

    int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords;

    for (int block = 0; block < blockCount; block++) {
      // each row-block has a DBCELL record.
      // The offset of each DBCELL record needs to be updated in the INDEX record

      // account for row records in this row-block
      currentOffset += getRowBlockSize(block);
      // account for cell value records after those
      currentOffset +=
          _valuesAgg.getRowCellBlockSize(
              getStartRowNumberForBlock(block), getEndRowNumberForBlock(block));

      // currentOffset is now the location of the DBCELL record for this row-block
      result.addDbcell(currentOffset);
      // Add space required to write the DBCELL record (whose reference was just added).
      currentOffset += (8 + (getRowCountForBlock(block) * 2));
    }
    return result;
  }
 private void constructValueRecord(List<Record> records) {
   RowBlocksReader rbr = new RowBlocksReader(new RecordStream(records, 0));
   SharedValueManager sfrh = rbr.getSharedFormulaManager();
   RecordStream rs = rbr.getPlainRecordStream();
   while (rs.hasNext()) {
     Record rec = rs.getNext();
     valueRecord.construct((CellValueRecordInterface) rec, rs, sfrh);
   }
 }
  @Override
  public void visitContainedRecords(RecordVisitor rv) {

    PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0);
    // DBCells are serialized before row records.
    final int blockCount = getRowBlockCount();
    for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
      // Serialize a block of rows.
      // Hold onto the position of the first row in the block
      int pos = 0;
      // Hold onto the size of this block that was serialized
      final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv);
      pos += rowBlockSize;
      // Serialize a block of cells for those rows
      final int startRowNumber = getStartRowNumberForBlock(blockIndex);
      final int endRowNumber = getEndRowNumberForBlock(blockIndex);
      DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder();
      // Note: Cell references start from the second row...
      int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE);
      for (int row = startRowNumber; row <= endRowNumber; row++) {
        if (_valuesAgg.rowHasCells(row)) {
          stv.setPosition(0);
          _valuesAgg.visitCellsForRow(row, stv);
          int rowCellSize = stv.getPosition();
          pos += rowCellSize;
          // Add the offset to the first cell for the row into the
          // DBCellRecord.
          dbcrBuilder.addCellOffset(cellRefOffset);
          cellRefOffset = rowCellSize;
        }
      }
      // Calculate Offset from the start of a DBCellRecord to the first Row
      rv.visitRecord(dbcrBuilder.build(pos));
    }
    for (int i = 0; i < _unknownRecords.size(); i++) {
      // Potentially breaking the file here since we don't know exactly where to write these records
      rv.visitRecord(_unknownRecords.get(i));
    }
  }
  public void testGetLastCellNum() {
    assertEquals(-1, valueRecord.getLastCellNum());
    valueRecord.insertCell(newBlankRecord(2, 2));
    assertEquals(2, valueRecord.getLastCellNum());
    valueRecord.insertCell(newBlankRecord(3, 3));
    assertEquals(3, valueRecord.getLastCellNum());

    // Note: Removal doesn't currently reset the last column.  It probably should but it doesn't.
    valueRecord.removeCell(newBlankRecord(3, 3));
    assertEquals(3, valueRecord.getLastCellNum());
  }
 public void removeRow(RowRecord row) {
   int rowIndex = row.getRowNumber();
   _valuesAgg.removeAllCellsValuesForRow(rowIndex);
   Integer key = Integer.valueOf(rowIndex);
   RowRecord rr = _rowRecords.remove(key);
   if (rr == null) {
     throw new RuntimeException("Invalid row index (" + key.intValue() + ")");
   }
   if (row != rr) {
     _rowRecords.put(key, rr);
     throw new RuntimeException("Attempt to remove row that does not belong to this sheet");
   }
 }
  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);
  }
  public void testSerialize() {
    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);

    SerializerVisitor sv = new SerializerVisitor(actualArray);
    valueRecord.visitCellsForRow(1, sv);
    valueRecord.visitCellsForRow(2, sv);
    assertEquals(actualArray.length, sv.getWriteIndex());
    assertTrue(Arrays.equals(expectedArray, actualArray));
  }
  /**
   * Make sure the shared formula DOESNT makes it to the FormulaRecordAggregate when being parsed as
   * part of the value records
   */
  @SuppressWarnings(
      "deprecation") // uses deprecated {@link ValueRecordsAggregate#getValueRecords()}
  public void testSharedFormula() {
    List<Record> records = new ArrayList<Record>();
    records.add(new FormulaRecord());
    records.add(new SharedFormulaRecord());
    records.add(new WindowTwoRecord());

    constructValueRecord(records);
    CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
    // Ensure that the SharedFormulaRecord has been converted
    assertEquals(1, cvrs.length);

    CellValueRecordInterface record = cvrs[0];
    assertNotNull("Row contains a value", record);
    assertTrue(
        "First record is a FormulaRecordsAggregate", (record instanceof FormulaRecordAggregate));
  }
 public void removeCell(CellValueRecordInterface cvRec) {
   if (cvRec instanceof FormulaRecordAggregate) {
     ((FormulaRecordAggregate) cvRec).notifyFormulaChanging();
   }
   _valuesAgg.removeCell(cvRec);
 }
 public void insertCell(CellValueRecordInterface cvRec) {
   _valuesAgg.insertCell(cvRec);
 }
 public CellValueRecordInterface[] getValueRecords() {
   return _valuesAgg.getValueRecords();
 }
 public void updateFormulasAfterRowShift(
     FormulaShifter formulaShifter, int currentExternSheetIndex) {
   _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex);
 }
 /** Returns an iterator for the cell values */
 public Iterator<CellValueRecordInterface> getCellValueIterator() {
   return _valuesAgg.iterator();
 }