/* * 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.ArrayList; import java.util.List; import org.h2.mvstore.DataUtils; import org.h2.util.MathUtils; /** * A list that maintains ranges of free space (in blocks). */ public class FreeSpaceList { /** * The first usable block. */ private final int firstFreeBlock; /** * The block size in bytes. */ private final int blockSize; private List<BlockRange> freeSpaceList = new ArrayList<BlockRange>(); public FreeSpaceList(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() { freeSpaceList.clear(); freeSpaceList.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 required = getBlockCount(length); for (BlockRange pr : freeSpaceList) { if (pr.length >= required) { int result = pr.start; this.markUsed(pr.start * blockSize, length); return result * blockSize; } } throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Could not find a free page to allocate"); } /** * 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 = (int) (pos / blockSize); int required = getBlockCount(length); BlockRange found = null; int i = 0; for (BlockRange pr : freeSpaceList) { if (start >= pr.start && start < (pr.start + pr.length)) { found = pr; break; } i++; } if (found == null) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Cannot find spot to mark as used in free list"); } if (start + required > found.start + found.length) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Runs over edge of free space"); } if (found.start == start) { // if the used space is at the beginning of a free-space-range found.start += required; found.length -= required; if (found.length == 0) { // if the free-space-range is now empty, remove it freeSpaceList.remove(i); } } else if (found.start + found.length == start + required) { // if the used space is at the end of a free-space-range found.length -= required; } else { // it's in the middle, so split the existing entry int length1 = start - found.start; int start2 = start + required; int length2 = found.start + found.length - start - required; found.length = length1; BlockRange newRange = new BlockRange(start2, length2); freeSpaceList.add(i + 1, newRange); } } /** * 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 = (int) (pos / blockSize); int required = getBlockCount(length); BlockRange found = null; int i = 0; for (BlockRange pr : freeSpaceList) { if (pr.start > start) { found = pr; break; } i++; } if (found == null) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Cannot find spot to mark as unused in free list"); } if (start + required == found.start) { // if the used space is adjacent to the beginning of a // free-space-range found.start = start; found.length += required; // compact: merge the previous entry into this one if // they are now adjacent if (i > 0) { BlockRange previous = freeSpaceList.get(i - 1); if (previous.start + previous.length == found.start) { previous.length += found.length; freeSpaceList.remove(i); } } return; } if (i > 0) { // if the used space is adjacent to the end of a free-space-range BlockRange previous = freeSpaceList.get(i - 1); if (previous.start + previous.length == start) { previous.length += required; return; } } // it is between 2 entries, so add a new one BlockRange newRange = new BlockRange(start, required); freeSpaceList.add(i, newRange); } 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 freeSpaceList.toString(); } /** * A range of free blocks. */ private static final class BlockRange { /** * The starting point, in blocks. */ int start; /** * The length, in blocks. */ int length; public BlockRange(int start, int length) { this.start = start; this.length = length; } @Override public String toString() { if (start + length == Integer.MAX_VALUE) { return Integer.toHexString(start) + "-"; } return Integer.toHexString(start) + "-" + Integer.toHexString(start + length - 1); } } }