/* * eXist Open Source Native XML Database * Copyright (C) 2000-04, Wolfgang M. Meier (wolfgang@exist-db.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ package org.exist.storage.index; import java.io.IOException; import java.io.RandomAccessFile; import org.apache.log4j.Logger; import org.exist.util.ByteConversion; /** * Manages a list of pages containing unused sections. * * Class {@link org.exist.storage.index.BFile} stores all data in variable * length records. As records may grow or shrink, the database has to keep * track of the amount of free space currently available in pages. Class * {@link org.exist.storage.index.BFile} will always check if FreeList has a page * that can be filled before creating a new page. * * FreeList implements a linked list of {@link FreeSpace} objects. Each object * in the list describes a page and the unused space in this page. * * @see FreeList * @author wolf */ public class FreeList { private final static Logger LOG = Logger.getLogger(FreeList.class); public final static int MAX_FREE_LIST_LEN = 128; protected FreeSpace header = null; protected FreeSpace last = null; protected int size = 0; public FreeList() { } /** * Append a new {@link FreeSpace} object to the list, * describing the amount of free space available on a page. * * @param free */ public void add( FreeSpace free ) { if(header == null) { header = free; last = free; } else { last.next = free; free.previous = last; last = free; } ++size; } /** * Remove a record from the list. * * @param node */ public void remove(FreeSpace node) { --size; if (node.previous == null) { if (node.next != null) { node.next.previous = null; header = node.next; } else header = null; } else { node.previous.next = node.next; if (node.next != null) node.next.previous = node.previous; else last = node.previous; } } /** * Retrieve the record stored for the given page number. * * @param pageNum */ public FreeSpace retrieve(long pageNum) { FreeSpace next = header; while(next != null) { if(next.page == pageNum) return next; next = next.next; } return null; } /** * Try to find a page that has at least requiredSize bytes * available. This method selects the page with the smallest * possible space. This guarantees that all pages will be filled before * creating a new page. * * @param requiredSize */ public FreeSpace find(int requiredSize) { FreeSpace next = header; FreeSpace found = null; while(next != null) { if(next.free >= requiredSize) { if(found == null || next.free < found.free) found = next; } next = next.next; } return found; } public String toString() { StringBuilder buf = new StringBuilder(); FreeSpace next = header; while(next != null) { buf.append("[").append(next.page).append(", "); buf.append(next.free).append("] "); next = next.next; } return buf.toString(); } /** * Read the list from a {@link RandomAccessFile}. * * * @param buf * @param offset * @throws IOException */ public int read(byte[] buf, int offset) throws IOException { final int fsize = ByteConversion.byteToInt(buf, offset); offset += 4; long page; int space; for (int i = 0; i < fsize; i++) { page = ByteConversion.byteToLong(buf, offset); offset += 8; space = ByteConversion.byteToInt(buf, offset); offset += 4; add(new FreeSpace(page, space)); } return offset; } /** * Write the list to a {@link RandomAccessFile}. * * As the list is written to the file header, its maximum length * has to be restricted. The method will thus only store * {@link #MAX_FREE_LIST_LEN} entries and throw away the * rest. Usually, this should not happen very often, so it is ok to * waste some space. * * * @param buf * @param offset * @throws IOException */ public int write(byte[] buf, int offset) throws IOException { // does the free-space list fit into the file header? int skip = 0; if (size > MAX_FREE_LIST_LEN) { // LOG.warn("removing " + (size - MAX_FREE_LIST_LEN) // + " free pages."); // no: remove some smaller entries to make it fit skip = size - MAX_FREE_LIST_LEN; } ByteConversion.intToByte(size - skip, buf, offset); offset += 4; FreeSpace next = header; while(next != null) { if(skip == 0) { ByteConversion.longToByte(next.page, buf, offset); offset += 8; ByteConversion.intToByte(next.free, buf, offset); offset += 4; } else --skip; next = next.next; } return offset; } }