static int assertFTADataIsSorted(
      IFrameTupleAccessor fta, Map<Integer, String> keyValuePair, int preKey)
      throws HyracksDataException {

    ByteBufferInputStream bbis = new ByteBufferInputStream();
    DataInputStream di = new DataInputStream(bbis);
    for (int i = 0; i < fta.getTupleCount(); i++) {
      bbis.setByteBuffer(
          fta.getBuffer(),
          fta.getTupleStartOffset(i) + fta.getFieldStartOffset(i, 0) + fta.getFieldSlotsLength());
      int key = (int) RecordDesc.getFields()[0].deserialize(di);
      bbis.setByteBuffer(
          fta.getBuffer(),
          fta.getTupleStartOffset(i) + fta.getFieldStartOffset(i, 1) + fta.getFieldSlotsLength());
      String value = (String) RecordDesc.getFields()[1].deserialize(di);

      if (!keyValuePair.get(key).equals(value)) {
        assertTrue(false);
      }
      keyValuePair.remove(key);
      assertTrue(key >= preKey);
      preKey = key;
    }
    return preKey;
  }
 @Override
 public void open() throws HyracksDataException {
   FileSplit split = splits[index];
   RecordDescriptor desc = recordDescriptors[0];
   IRecordReader reader;
   try {
     reader = createRecordReader(split.getLocalFile().getFile(), desc);
   } catch (Exception e) {
     throw new HyracksDataException(e);
   }
   if (desc == null) {
     desc = recordDescriptors[0];
   }
   writer.open();
   try {
     while (true) {
       Object[] record = new Object[desc.getFieldCount()];
       if (!reader.read(record)) {
         break;
       }
       writer.writeData(record);
     }
   } catch (Exception e) {
     writer.fail();
     throw new HyracksDataException(e);
   } finally {
     reader.close();
     writer.close();
   }
 }
 @Override
 public void open() throws HyracksDataException {
   accessor = new FrameTupleAccessor(inputRecDesc);
   builder = new ArrayTupleBuilder(outputRecDesc.getFieldCount());
   builderData = builder.getFieldData();
   appender = new FrameTupleAppender(new VSizeFrame(ctx), true);
   writer.open();
 }
  static void prepareData(
      IHyracksTaskContext ctx,
      List<IFrame> frameList,
      int minDataSize,
      int minRecordSize,
      int maxRecordSize,
      Map<Integer, String> specialData,
      Map<Integer, String> keyValuePair)
      throws HyracksDataException {

    ArrayTupleBuilder tb = new ArrayTupleBuilder(RecordDesc.getFieldCount());
    FrameTupleAppender appender = new FrameTupleAppender();

    int datasize = 0;
    if (specialData != null) {
      for (Map.Entry<Integer, String> entry : specialData.entrySet()) {
        tb.reset();
        tb.addField(IntegerSerializerDeserializer.INSTANCE, entry.getKey());
        tb.addField(UTF8StringSerializerDeserializer.INSTANCE, entry.getValue());

        VSizeFrame frame =
            new VSizeFrame(
                ctx,
                FrameHelper.calcAlignedFrameSizeToStore(
                    tb.getFieldEndOffsets().length, tb.getSize(), ctx.getInitialFrameSize()));
        appender.reset(frame, true);
        assertTrue(appender.append(tb.getFieldEndOffsets(), tb.getByteArray(), 0, tb.getSize()));
        frameList.add(frame);
        datasize += frame.getFrameSize();
      }
      keyValuePair.putAll(specialData);
    }

    VSizeFrame frame = new VSizeFrame(ctx, ctx.getInitialFrameSize());
    appender.reset(frame, true);
    while (datasize < minDataSize) {
      tb.reset();
      int key = GRandom.nextInt(minDataSize + 1);
      if (!keyValuePair.containsKey(key)) {
        String value = generateRandomRecord(minRecordSize, maxRecordSize);
        tb.addField(IntegerSerializerDeserializer.INSTANCE, key);
        tb.addField(UTF8StringSerializerDeserializer.INSTANCE, value);

        if (!appender.append(tb.getFieldEndOffsets(), tb.getByteArray(), 0, tb.getSize())) {
          frameList.add(frame);
          datasize += frame.getFrameSize();
          frame =
              new VSizeFrame(
                  ctx,
                  FrameHelper.calcAlignedFrameSizeToStore(
                      tb.getFieldEndOffsets().length, tb.getSize(), ctx.getInitialFrameSize()));
          appender.reset(frame, true);
          assertTrue(appender.append(tb.getFieldEndOffsets(), tb.getByteArray(), 0, tb.getSize()));
        }

        keyValuePair.put(key, value);
      }
    }
    if (appender.getTupleCount() > 0) {
      frameList.add(frame);
    }
  }
public class DeletableFrameTupleAppenderTest {
  DeletableFrameTupleAppender appender;
  ISerializerDeserializer[] fields =
      new ISerializerDeserializer[] {
        IntegerSerializerDeserializer.INSTANCE, new UTF8StringSerializerDeserializer(),
      };
  RecordDescriptor recordDescriptor = new RecordDescriptor(fields);
  ArrayTupleBuilder builder = new ArrayTupleBuilder(recordDescriptor.getFieldCount());
  static final char TEST_CH = 'x';

  int cap = 256;

  @Before
  public void initial() throws HyracksDataException {
    appender = new DeletableFrameTupleAppender(recordDescriptor);
  }

  @Test
  public void testClear() throws Exception {
    ByteBuffer buffer = ByteBuffer.allocate(cap);
    appender.clear(buffer);
    assertTrue(appender.getBuffer() == buffer);
    assertTrue(appender.getTupleCount() == 0);
    assertTrue(appender.getContiguousFreeSpace() == cap - 4 - 4);
  }

  ByteBuffer makeAFrame(int capacity, int count, int deletedBytes) throws HyracksDataException {
    ByteBuffer buffer = ByteBuffer.allocate(capacity);
    int metaOffset = capacity - 4;
    buffer.putInt(metaOffset, deletedBytes);
    metaOffset -= 4;
    buffer.putInt(metaOffset, count);
    metaOffset -= 4;
    for (int i = 0; i < count; i++, metaOffset -= 4) {
      makeARecord(builder, i);
      for (int x = 0; x < builder.getFieldEndOffsets().length; x++) {
        buffer.putInt(builder.getFieldEndOffsets()[x]);
      }
      buffer.put(builder.getByteArray(), 0, builder.getSize());
      assert (metaOffset > buffer.position());
      buffer.putInt(metaOffset, buffer.position());
    }
    return buffer;
  }

  void makeARecord(ArrayTupleBuilder builder, int i) throws HyracksDataException {
    builder.reset();
    builder.addField(fields[0], i + 1);
    builder.addField(fields[1], Utility.repeatString(TEST_CH, i + 1));
  }

  int assertTupleIsExpected(int i, int dataOffset) {
    int lenStrMeta = UTF8StringUtil.getNumBytesToStoreLength(i);
    int tupleLength = 2 * 4 + 4 + lenStrMeta + i + 1;
    assertEquals(dataOffset, appender.getTupleStartOffset(i));
    assertEquals(tupleLength, appender.getTupleLength(i));

    assertEquals(dataOffset + 2 * 4, appender.getAbsoluteFieldStartOffset(i, 0));
    assertEquals(4, appender.getFieldLength(i, 0));
    assertEquals(
        i + 1,
        IntSerDeUtils.getInt(
            appender.getBuffer().array(), appender.getAbsoluteFieldStartOffset(i, 0)));
    assertEquals(dataOffset + 2 * 4 + 4, appender.getAbsoluteFieldStartOffset(i, 1));
    assertEquals(lenStrMeta + i + 1, appender.getFieldLength(i, 1));
    return tupleLength;
  }

  @Test
  public void testReset() throws Exception {
    ByteBuffer buffer = ByteBuffer.allocate(cap);
    appender.reset(buffer);
    assertTrue(appender.getBuffer() == buffer);
    assertTrue(appender.getTupleCount() == 0);
    assertTrue(appender.getContiguousFreeSpace() == cap - 4 - 4);

    int count = 10;
    int deleted = 7;
    buffer = makeAFrame(cap, count, deleted);
    int pos = buffer.position();
    appender.reset(buffer);
    assertTrue(appender.getBuffer() == buffer);
    assertTrue(appender.getTupleCount() == count);
    assertTrue(appender.getContiguousFreeSpace() == cap - 4 - 4 - count * 4 - pos);
    assertTrue(appender.getTotalFreeSpace() == appender.getContiguousFreeSpace() + deleted);

    int dataOffset = 0;
    for (int i = 0; i < count; i++) {
      dataOffset += assertTupleIsExpected(i, dataOffset);
    }
  }

  @Test
  public void testAppend() throws Exception {
    int count = 10;
    ByteBuffer bufferRead = makeAFrame(cap, count, 0);
    DeletableFrameTupleAppender accessor = new DeletableFrameTupleAppender(recordDescriptor);
    accessor.reset(bufferRead);
    ByteBuffer bufferWrite = ByteBuffer.allocate(cap);
    appender.clear(bufferWrite);
    for (int i = 0; i < accessor.getTupleCount(); i++) {
      appender.append(accessor, i);
    }
    for (int i = 0; i < bufferRead.capacity(); i++) {
      assertEquals(bufferRead.get(i), bufferWrite.get(i));
    }
  }

  @Test
  public void testDelete() throws Exception {
    int count = 10;
    int deleteSpace = 0;
    ByteBuffer buffer = makeAFrame(cap, count, deleteSpace);
    appender.reset(buffer);

    int freeSpace = appender.getContiguousFreeSpace();
    for (int i = 0; i < appender.getTupleCount(); i++) {
      deleteSpace += assertDeleteSucceed(i, freeSpace, deleteSpace);
      int innerOffset = deleteSpace;
      for (int j = i + 1; j < appender.getTupleCount(); j++) {
        innerOffset += assertTupleIsExpected(j, innerOffset);
      }
    }
  }

  @Test
  public void testResetAfterDelete() throws Exception {
    testDelete();
    appender.reset(appender.getBuffer());
    assertEquals(cap - appender.getTupleCount() * 4 - 4 - 4, appender.getTotalFreeSpace());
  }

  int assertDeleteSucceed(int i, int freeSpaceBeforeDelete, int deleteSpace) {
    int startOffset = appender.getTupleStartOffset(i);
    int endOffset = appender.getTupleEndOffset(i);
    int tupleLength = appender.getTupleLength(i);

    appender.delete(i);

    assertEquals(startOffset, appender.getTupleStartOffset(i));
    assertEquals(-endOffset, appender.getTupleEndOffset(i));
    assertEquals(-tupleLength, appender.getTupleLength(i));
    assertEquals(freeSpaceBeforeDelete, appender.getContiguousFreeSpace());
    assertEquals(deleteSpace + tupleLength + freeSpaceBeforeDelete, appender.getTotalFreeSpace());
    return tupleLength;
  }

  @Test
  public void testAppendAndDelete() throws Exception {
    int cap = 1024;
    int count = 10;
    int deleteSpace = 0;
    ByteBuffer buffer = makeAFrame(cap, count, deleteSpace);
    int dataOffset = buffer.position();
    appender.reset(buffer);

    int freeSpace = appender.getContiguousFreeSpace();
    int[] deleteSet = new int[] {1, 3, 5};
    for (int i = 0; i < deleteSet.length; i++) {
      deleteSpace += assertDeleteSucceed(deleteSet[i], freeSpace, deleteSpace);
    }

    ByteBuffer bufferRead = makeAFrame(cap, count * 2, 0);
    DeletableFrameTupleAppender accessor = new DeletableFrameTupleAppender(recordDescriptor);
    accessor.reset(bufferRead);

    for (int i = count; i < accessor.getTupleCount(); i++) {
      int id = appender.append(accessor, i);
      dataOffset += assertTupleIsExpected(i, dataOffset);
      assertEquals(i, id);
    }

    appender.reOrganizeBuffer();
    dataOffset = 0;
    for (int i = 0; i < appender.getTupleCount(); i++) {
      if (ArrayUtils.contains(deleteSet, i)) {
        continue;
      }
      dataOffset += assertTupleIsExpected(i, dataOffset);
    }
  }

  @Test
  public void testReOrganizeBuffer() throws Exception {
    int count = 10;
    testDelete();
    appender.reOrganizeBuffer();
    ByteBuffer bufferRead = makeAFrame(cap, count, 0);
    DeletableFrameTupleAppender accessor = new DeletableFrameTupleAppender(recordDescriptor);
    accessor.reset(bufferRead);
    for (int i = 0; i < accessor.getTupleCount(); i++) {
      appender.append(accessor, i);
    }
    for (int i = 0; i < bufferRead.capacity(); i++) {
      assertEquals(bufferRead.get(i), appender.getBuffer().get(i));
    }
  }
}