package com.codecademy.eventhub.list; import com.codecademy.eventhub.base.ByteBufferUtil; import java.io.Closeable; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; /** * Currently, it can contain up to MAX_NUM_RECORDS records. ((2^31 - 1) - 4) / 8, i.e. largest * indexable address in (MappedByteBuffer - size of metadata) / size of a long typed id. * Since it's used in IndividualEventIndex and UserEventIndex, this implied that no single date * nor single user can have number of events exceeding this limit. */ public class DmaIdList implements IdList, Closeable { static final int META_DATA_SIZE = 4; // offset for numRecords static final int SIZE_OF_DATA = 8; // each data is a long number private static final int MAX_NUM_RECORDS = (Integer.MAX_VALUE - META_DATA_SIZE) / SIZE_OF_DATA; private final String filename; private MappedByteBuffer buffer; private int numRecords; private long capacity; public DmaIdList(String filename, MappedByteBuffer buffer, int numRecords, int capacity) { this.filename = filename; this.buffer = buffer; this.numRecords = numRecords; this.capacity = capacity; } @Override public void add(long id) { if (numRecords == MAX_NUM_RECORDS) { throw new IllegalStateException( String.format("DmaIdList reaches its maximum number of records: %d", numRecords)); } if (numRecords == capacity) { buffer = ByteBufferUtil.expandBuffer(filename, buffer, META_DATA_SIZE + Math.min(MAX_NUM_RECORDS, 2 * capacity) * SIZE_OF_DATA); capacity *= 2; } buffer.putLong(id); buffer.putInt(0, ++numRecords); } @Override public int getStartOffset(long eventId) { ByteBuffer duplicate = buffer.duplicate(); duplicate.position(META_DATA_SIZE); duplicate = duplicate.slice(); return ByteBufferUtil.binarySearchOffset(duplicate, 0, numRecords, eventId, SIZE_OF_DATA); } @Override public Iterator subList(int startOffset, int maxRecords) { int endOffset = startOffset + maxRecords; endOffset = Math.min(endOffset < 0 ? Integer.MAX_VALUE : endOffset, numRecords); return new Iterator(buffer, startOffset, endOffset); } @Override public Iterator iterator() { return new Iterator(buffer, 0, numRecords); } @Override public void close() { buffer.force(); buffer = null; } public interface Factory { DmaIdList build(String filename); void setDefaultCapacity(int defaultCapacity); } public static class Iterator implements IdList.Iterator { private final MappedByteBuffer buffer; private final long start; private final long end; private long offset; public Iterator(MappedByteBuffer buffer, long start, long end) { this.buffer = buffer; this.start = start; this.end = end; this.offset = 0; } @Override public boolean hasNext() { return start + offset < end; } @Override public long next() { long kthRecord = start + (offset++); return buffer.getLong(META_DATA_SIZE + (int) kthRecord * SIZE_OF_DATA); } } }