/**
  * Calculate the number of data bytes for the given id. As the length is encoded in the id, this
  * operation does not cause any reads in the map.
  *
  * @param id the id
  * @return the length
  */
 public long length(byte[] id) {
   ByteBuffer idBuffer = ByteBuffer.wrap(id);
   long length = 0;
   while (idBuffer.hasRemaining()) {
     switch (idBuffer.get()) {
       case 0:
         // in-place: 0, len (int), data
         int len = DataUtils.readVarInt(idBuffer);
         idBuffer.position(idBuffer.position() + len);
         length += len;
         break;
       case 1:
         // block: 1, len (int), blockId (long)
         length += DataUtils.readVarInt(idBuffer);
         DataUtils.readVarLong(idBuffer);
         break;
       case 2:
         // indirect: 2, total len (long), blockId (long)
         length += DataUtils.readVarLong(idBuffer);
         DataUtils.readVarLong(idBuffer);
         break;
       default:
         throw DataUtils.newIllegalArgumentException("Unsupported id {0}", Arrays.toString(id));
     }
   }
   return length;
 }
 /**
  * Remove all stored blocks for the given id.
  *
  * @param id the id
  */
 public void remove(byte[] id) {
   ByteBuffer idBuffer = ByteBuffer.wrap(id);
   while (idBuffer.hasRemaining()) {
     switch (idBuffer.get()) {
       case 0:
         // in-place: 0, len (int), data
         int len = DataUtils.readVarInt(idBuffer);
         idBuffer.position(idBuffer.position() + len);
         break;
       case 1:
         // block: 1, len (int), blockId (long)
         DataUtils.readVarInt(idBuffer);
         long k = DataUtils.readVarLong(idBuffer);
         map.remove(k);
         break;
       case 2:
         // indirect: 2, total len (long), blockId (long)
         DataUtils.readVarLong(idBuffer);
         long k2 = DataUtils.readVarLong(idBuffer);
         // recurse
         remove(map.get(k2));
         map.remove(k2);
         break;
       default:
         throw DataUtils.newIllegalArgumentException("Unsupported id {0}", Arrays.toString(id));
     }
   }
 }
 /**
  * Get the key of the biggest block, of -1 for inline data. This method is used to garbage collect
  * orphaned blocks.
  *
  * @param id the id
  * @return the key, or -1
  */
 public long getMaxBlockKey(byte[] id) {
   long maxKey = -1;
   ByteBuffer idBuffer = ByteBuffer.wrap(id);
   while (idBuffer.hasRemaining()) {
     switch (idBuffer.get()) {
       case 0:
         // in-place: 0, len (int), data
         int len = DataUtils.readVarInt(idBuffer);
         idBuffer.position(idBuffer.position() + len);
         break;
       case 1:
         // block: 1, len (int), blockId (long)
         DataUtils.readVarInt(idBuffer);
         long k = DataUtils.readVarLong(idBuffer);
         maxKey = Math.max(maxKey, k);
         break;
       case 2:
         // indirect: 2, total len (long), blockId (long)
         DataUtils.readVarLong(idBuffer);
         long k2 = DataUtils.readVarLong(idBuffer);
         // recurse
         byte[] r = map.get(k2);
         maxKey = Math.max(maxKey, getMaxBlockKey(r));
         break;
       default:
         throw DataUtils.newIllegalArgumentException("Unsupported id {0}", Arrays.toString(id));
     }
   }
   return maxKey;
 }
 private ByteArrayInputStream nextBuffer() {
   while (idBuffer.hasRemaining()) {
     switch (idBuffer.get()) {
       case 0:
         {
           int len = DataUtils.readVarInt(idBuffer);
           if (skip >= len) {
             skip -= len;
             idBuffer.position(idBuffer.position() + len);
             continue;
           }
           int p = (int) (idBuffer.position() + skip);
           int l = (int) (len - skip);
           idBuffer.position(p + l);
           return new ByteArrayInputStream(idBuffer.array(), p, l);
         }
       case 1:
         {
           int len = DataUtils.readVarInt(idBuffer);
           long key = DataUtils.readVarLong(idBuffer);
           if (skip >= len) {
             skip -= len;
             continue;
           }
           byte[] data = store.getBlock(key);
           int s = (int) skip;
           skip = 0;
           return new ByteArrayInputStream(data, s, data.length - s);
         }
       case 2:
         {
           long len = DataUtils.readVarLong(idBuffer);
           long key = DataUtils.readVarLong(idBuffer);
           if (skip >= len) {
             skip -= len;
             continue;
           }
           byte[] k = store.getBlock(key);
           ByteBuffer newBuffer =
               ByteBuffer.allocate(k.length + idBuffer.limit() - idBuffer.position());
           newBuffer.put(k);
           newBuffer.put(idBuffer);
           newBuffer.flip();
           idBuffer = newBuffer;
           return nextBuffer();
         }
       default:
         throw DataUtils.newIllegalArgumentException(
             "Unsupported id {0}", Arrays.toString(idBuffer.array()));
     }
   }
   return null;
 }
 /**
  * Convert the id to a human readable string.
  *
  * @param id the stream id
  * @return the string
  */
 public static String toString(byte[] id) {
   StringBuilder buff = new StringBuilder();
   ByteBuffer idBuffer = ByteBuffer.wrap(id);
   long length = 0;
   while (idBuffer.hasRemaining()) {
     long block;
     int len;
     switch (idBuffer.get()) {
       case 0:
         // in-place: 0, len (int), data
         len = DataUtils.readVarInt(idBuffer);
         idBuffer.position(idBuffer.position() + len);
         buff.append("data len=").append(len);
         length += len;
         break;
       case 1:
         // block: 1, len (int), blockId (long)
         len = DataUtils.readVarInt(idBuffer);
         length += len;
         block = DataUtils.readVarLong(idBuffer);
         buff.append("block ").append(block).append(" len=").append(len);
         break;
       case 2:
         // indirect: 2, total len (long), blockId (long)
         len = DataUtils.readVarInt(idBuffer);
         length += DataUtils.readVarLong(idBuffer);
         block = DataUtils.readVarLong(idBuffer);
         buff.append("indirect block ").append(block).append(" len=").append(len);
         break;
       default:
         buff.append("error");
     }
     buff.append(", ");
   }
   buff.append("length=").append(length);
   return buff.toString();
 }