package org.basex.util;
import java.util.*;
import java.util.Map.Entry;
import org.basex.util.list.*;
/**
* Organizes free slots in heap files.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
public final class FreeSlots {
/** Free slots: byte sizes referencing file offsets. */
private final TreeMap<Integer, LongList> free = new TreeMap<>();
/** Number of slots. */
private int slots;
/**
* Adds a value for the specified slot size.
* @param size byte size
* @param offset file offset
*/
public void add(final int size, final long offset) {
add(size, offset, true);
}
/**
* Returns the offset of a slot that is greater than or equal to the specified size.
* @param size ideal (minimum) slot size
* @param offset offset used as fallback if no free slot is available
* @return insertion offset
*/
public long get(final int size, final long offset) {
long off = -1;
final Entry<Integer, LongList> entry = free.ceilingEntry(size);
if(entry != null) {
final int slotSize = entry.getKey();
if(slotSize < size) throw Util.notExpected("Free slot is too small: % < %", slotSize, size);
final LongList offsets = entry.getValue();
off = offsets.pop();
slots--;
if(offsets.isEmpty()) free.remove(slotSize);
if(slotSize > size) {
if(off + slotSize > offset)
throw Util.notExpected("Free slot exceeds file offset: % + % > %", off, slotSize, offset);
// chosen entry is smaller than supplied size: add entry for remaining free slot
add(slotSize - size, off + size);
}
}
return off == -1 ? offset : off;
}
/**
* Adds a value for the specified slot size.
* @param size byte size
* @param offset file offset
* @param opt optimize
*/
private void add(final int size, final long offset, final boolean opt) {
LongList ll = free.get(size);
if(ll == null) {
ll = new LongList();
free.put(size, ll);
}
ll.add(offset);
slots++;
if(opt) optimize();
}
/**
* Optimizes the free slot list structure by merging adjacent entries.
* Currently, this function is called after every addition of a new slot value.
*/
private void optimize() {
if(free.isEmpty()) return;
// sort all entries by their offset (use native arrays; faster than TreeMap)
final int size = slots;
final LongList offList = new LongList(size);
final IntList sizeList = new IntList(size);
for(final Entry<Integer, LongList> entry : free.entrySet()) {
final int slotSize = entry.getKey();
final LongList list = entry.getValue();
final int ll = list.size();
for(int l = 0; l < ll; l++) {
offList.add(list.get(l));
sizeList.add(slotSize);
}
}
if(size != offList.size())
throw Util.notExpected("Wrong slot count: % vs. %", size, offList.size());
final long[] offsets = offList.finish();
final int[] slotSizes = sizeList.finish();
final int[] index = Array.createOrder(offsets, true);
// rebuild map with merged slots
free.clear();
slots = 0;
long offset = offsets[0];
int slotSize = slotSizes[index[0]];
for(int c = 1; c < size; c++) {
final long o = offsets[c];
final int s = slotSizes[index[c]];
if(o == offset + slotSize) {
slotSize += s;
} else {
add(slotSize, offset, false);
offset = o;
slotSize = s;
}
}
add(slotSize, offset, false);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("FREE SLOTS: " + free.size() + '\n');
for(final Entry<Integer, LongList> entry : free.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append('\n');
}
return sb.toString();
}
}