/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.test.store; import java.util.TreeSet; import org.h2.mvstore.DataUtils; import org.h2.util.MathUtils; /** * A list that maintains ranges of free space (in blocks) in a file. */ public class FreeSpaceTree { /** * The first usable block. */ private final int firstFreeBlock; /** * The block size in bytes. */ private final int blockSize; /** * The list of free space. */ private TreeSet<BlockRange> freeSpace = new TreeSet<BlockRange>(); public FreeSpaceTree(int firstFreeBlock, int blockSize) { this.firstFreeBlock = firstFreeBlock; if (Integer.bitCount(blockSize) != 1) { throw DataUtils.newIllegalArgumentException("Block size is not a power of 2"); } this.blockSize = blockSize; clear(); } /** * Reset the list. */ public synchronized void clear() { freeSpace.clear(); freeSpace.add(new BlockRange(firstFreeBlock, Integer.MAX_VALUE - firstFreeBlock)); } /** * Allocate a number of blocks and mark them as used. * * @param length the number of bytes to allocate * @return the start position in bytes */ public synchronized long allocate(int length) { int blocks = getBlockCount(length); BlockRange x = null; for (BlockRange b : freeSpace) { if (b.blocks >= blocks) { x = b; break; } } long pos = getPos(x.start); if (x.blocks == blocks) { freeSpace.remove(x); } else { x.start += blocks; x.blocks -= blocks; } return pos; } /** * Mark the space as in use. * * @param pos the position in bytes * @param length the number of bytes */ public synchronized void markUsed(long pos, int length) { int start = getBlock(pos); int blocks = getBlockCount(length); BlockRange x = new BlockRange(start, blocks); BlockRange prev = freeSpace.floor(x); if (prev == null) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Free space already marked"); } if (prev.start == start) { if (prev.blocks == blocks) { // match freeSpace.remove(prev); } else { // cut the front prev.start += blocks; prev.blocks -= blocks; } } else if (prev.start + prev.blocks == start + blocks) { // cut the end prev.blocks -= blocks; } else { // insert an entry x.start = start + blocks; x.blocks = prev.start + prev.blocks - x.start; freeSpace.add(x); prev.blocks = start - prev.start; } } /** * Mark the space as free. * * @param pos the position in bytes * @param length the number of bytes */ public synchronized void free(long pos, int length) { int start = getBlock(pos); int blocks = getBlockCount(length); BlockRange x = new BlockRange(start, blocks); BlockRange next = freeSpace.ceiling(x); if (next == null) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Free space sentinel is missing"); } BlockRange prev = freeSpace.lower(x); if (prev != null) { if (prev.start + prev.blocks == start) { // extend the previous entry prev.blocks += blocks; if (prev.start + prev.blocks == next.start) { // merge with the next entry prev.blocks += next.blocks; freeSpace.remove(next); } return; } } if (start + blocks == next.start) { // extend the next entry next.start -= blocks; next.blocks += blocks; return; } freeSpace.add(x); } private long getPos(int block) { return (long) block * (long) blockSize; } private int getBlock(long pos) { return (int) (pos / blockSize); } private int getBlockCount(int length) { if (length <= 0) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Free space invalid length"); } return MathUtils.roundUpInt(length, blockSize) / blockSize; } @Override public String toString() { return freeSpace.toString(); } /** * A range of free blocks. */ private static final class BlockRange implements Comparable<BlockRange> { /** * The starting point (the block number). */ public int start; /** * The length, in blocks. */ public int blocks; public BlockRange(int start, int blocks) { this.start = start; this.blocks = blocks; } @Override public int compareTo(BlockRange o) { return start < o.start ? -1 : start > o.start ? 1 : 0; } @Override public String toString() { if (blocks + start == Integer.MAX_VALUE) { return Integer.toHexString(start) + "-"; } return Integer.toHexString(start) + "-" + Integer.toHexString(start + blocks - 1); } } }