/* * Copyright 2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gradle.cache.internal.btree; import java.io.DataInputStream; import java.io.DataOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class FreeListBlockStore implements BlockStore { private final BlockStore store; private final BlockStore freeListStore; private final int maxBlockEntries; private FreeListBlock freeListBlock; public FreeListBlockStore(BlockStore store, int maxBlockEntries) { this.store = store; freeListStore = this; this.maxBlockEntries = maxBlockEntries; } public void open(final Runnable initAction, final Factory factory) { Runnable freeListInitAction = new Runnable() { public void run() { freeListBlock = new FreeListBlock(); store.write(freeListBlock); store.flush(); initAction.run(); } }; Factory freeListFactory = new Factory() { public Object create(Class<? extends BlockPayload> type) { if (type == FreeListBlock.class) { return new FreeListBlock(); } return factory.create(type); } }; store.open(freeListInitAction, freeListFactory); freeListBlock = store.readFirst(FreeListBlock.class); } public void close() { freeListBlock = null; store.close(); } public void clear() { store.clear(); } public void remove(BlockPayload block) { Block container = block.getBlock(); store.remove(block); freeListBlock.add(container.getPos(), container.getSize()); } public <T extends BlockPayload> T readFirst(Class<T> payloadType) { return store.read(freeListBlock.getNextPos(), payloadType); } public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) { return store.read(pos, payloadType); } public void write(BlockPayload block) { attach(block); store.write(block); } public void attach(BlockPayload block) { store.attach(block); freeListBlock.alloc(block.getBlock()); } public void flush() { store.flush(); } private void verify() { FreeListBlock block = store.readFirst(FreeListBlock.class); verify(block, Integer.MAX_VALUE); } private void verify(FreeListBlock block, int maxValue) { if (block.largestInNextBlock > maxValue) { throw new RuntimeException("corrupt free list"); } int current = 0; for (FreeListEntry entry : block.entries) { if (entry.size > maxValue) { throw new RuntimeException("corrupt free list"); } if (entry.size < block.largestInNextBlock) { throw new RuntimeException("corrupt free list"); } if (entry.size < current) { throw new RuntimeException("corrupt free list"); } current = entry.size; } if (!block.nextBlock.isNull()) { verify(store.read(block.nextBlock, FreeListBlock.class), block.largestInNextBlock); } } public class FreeListBlock extends BlockPayload { private List<FreeListEntry> entries = new ArrayList<FreeListEntry>(); private int largestInNextBlock; private BlockPointer nextBlock = BlockPointer.start(); // Transient fields private FreeListBlock prev; private FreeListBlock next; @Override protected int getSize() { return Block.LONG_SIZE + Block.INT_SIZE + Block.INT_SIZE + maxBlockEntries * (Block.LONG_SIZE + Block.INT_SIZE); } @Override protected byte getType() { return 0x44; } @Override protected void read(DataInputStream inputStream) throws Exception { nextBlock = BlockPointer.pos(inputStream.readLong()); largestInNextBlock = inputStream.readInt(); int count = inputStream.readInt(); for (int i = 0; i < count; i++) { BlockPointer pos = BlockPointer.pos(inputStream.readLong()); int size = inputStream.readInt(); entries.add(new FreeListEntry(pos, size)); } } @Override protected void write(DataOutputStream outputStream) throws Exception { outputStream.writeLong(nextBlock.getPos()); outputStream.writeInt(largestInNextBlock); outputStream.writeInt(entries.size()); for (FreeListEntry entry : entries) { outputStream.writeLong(entry.pos.getPos()); outputStream.writeInt(entry.size); } } public void add(BlockPointer pos, int size) { assert !pos.isNull() && size >= 0; if (size == 0) { return; } if (size < largestInNextBlock) { FreeListBlock next = getNextBlock(); next.add(pos, size); return; } FreeListEntry entry = new FreeListEntry(pos, size); int index = Collections.binarySearch(entries, entry); if (index < 0) { index = -index - 1; } entries.add(index, entry); if (entries.size() > maxBlockEntries) { FreeListBlock newBlock = new FreeListBlock(); newBlock.largestInNextBlock = largestInNextBlock; newBlock.nextBlock = nextBlock; newBlock.prev = this; newBlock.next = next; next = newBlock; List<FreeListEntry> newBlockEntries = entries.subList(0, entries.size() / 2); newBlock.entries.addAll(newBlockEntries); newBlockEntries.clear(); largestInNextBlock = newBlock.entries.get(newBlock.entries.size() - 1).size; freeListStore.write(newBlock); nextBlock = newBlock.getPos(); } freeListStore.write(this); } private FreeListBlock getNextBlock() { if (next == null) { next = freeListStore.read(nextBlock, FreeListBlock.class); next.prev = this; } return next; } public void alloc(Block block) { if (block.hasPos()) { return; } int requiredSize = block.getSize(); if (entries.isEmpty() || requiredSize <= largestInNextBlock) { if (nextBlock.isNull()) { return; } getNextBlock().alloc(block); return; } int index = Collections.binarySearch(entries, new FreeListEntry(null, requiredSize)); if (index < 0) { index = -index - 1; } if (index == entries.size()) { // Largest free block is too small return; } FreeListEntry entry = entries.remove(index); block.setPos(entry.pos); block.setSize(entry.size); freeListStore.write(this); if (entries.size() == 0 && prev != null) { prev.nextBlock = nextBlock; prev.largestInNextBlock = largestInNextBlock; prev.next = next; if (next != null) { next.prev = prev; } freeListStore.write(prev); freeListStore.remove(this); } } } private static class FreeListEntry implements Comparable<FreeListEntry> { final BlockPointer pos; final int size; private FreeListEntry(BlockPointer pos, int size) { this.pos = pos; this.size = size; } public int compareTo(FreeListEntry o) { if (size > o.size) { return 1; } if (size < o.size) { return -1; } return 0; } } }