/** * JDBM LICENSE v1.00 * * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "JDBM" must not be used to endorse or promote * products derived from this Software without prior written * permission of Cees de Groot. For written permission, * please contact cg@cdegroot.com. * * 4. Products derived from this Software may not be called "JDBM" * nor may "JDBM" appear in their names without prior written * permission of Cees de Groot. * * 5. Due credit should be given to the JDBM Project * (http://jdbm.sourceforge.net/). * * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 2000 (C) Cees de Groot. All Rights Reserved. * Contributions are Copyright (C) 2000 by their associated contributors. * * $Id: PhysicalRowIdManager.java,v 1.3 2003/03/21 03:00:09 boisvert Exp $ */ package de.mxro.thrd.jdbm2V22.recman; import java.io.IOException; import java.io.OutputStream; /** * This class manages physical row ids, and their data. */ final class PhysicalRowIdManager { // The file we're talking to and the associated page manager. final private RecordFile file; final private PageManager pageman; final private FreePhysicalRowIdPageManager freeman; final private int BLOCK_SIZE; final short DATA_PER_PAGE ; /** * Creates a new rowid manager using the indicated record file. and page manager. */ PhysicalRowIdManager(RecordFile file, PageManager pageManager, FreePhysicalRowIdPageManager freeman) throws IOException { this.file = file; this.pageman = pageManager; this.freeman = freeman; this.BLOCK_SIZE = file.BLOCK_SIZE; DATA_PER_PAGE = (short) (BLOCK_SIZE - DataPage.O_DATA); } /** * Inserts a new record. Returns the new physical rowid. */ long insert(byte[] data, int start, int length) throws IOException { if (length < 1) throw new IllegalArgumentException("Lenght is <1"); if (start < 0) throw new IllegalArgumentException("negative start"); long retval = alloc(length); write(retval, data, start, length); return retval; } /** * Updates an existing record. Returns the possibly changed physical rowid. */ long update(long rowid, byte[] data, int start, int length) throws IOException { // fetch the record header BlockIo block = file.get(Location.getBlock(rowid)); short head = Location.getOffset(rowid); int availSize = RecordHeader.getAvailableSize(block, head); if (length > availSize || //difference between free and available space can be only 64KB. //if bigger, need to realocate and free block availSize - length > RecordHeader.MAX_SIZE_SPACE ) { // not enough space - we need to copy to a new rowid. file.release(block); free(rowid); rowid = alloc(length); } else { file.release(block); } // 'nuff space, write it in and return the rowid. write(rowid, data, start, length); return rowid; } /** * Deletes a record. */ void delete(long rowid) throws IOException { free(rowid); } /** * Retrieves a record. */ // byte[] fetch( Location rowid ) // throws IOException // { // // fetch the record header // PageCursor curs = new PageCursor( pageman, rowid.getBlock() ); // BlockIo block = file.get( curs.getCurrent() ); // RecordHeader head = new RecordHeader( block, rowid.getOffset() ); // // // allocate a return buffer // byte[] retval = new byte[ head.getCurrentSize() ]; // if ( retval.length == 0 ) { // file.release( curs.getCurrent(), false ); // return retval; // } // // // copy bytes in // int offsetInBuffer = 0; // int leftToRead = retval.length; // short dataOffset = (short) (rowid.getOffset() + RecordHeader.SIZE); // while ( leftToRead > 0 ) { // // copy current page's data to return buffer // int toCopy = RecordFile.BLOCK_SIZE - dataOffset; // if ( leftToRead < toCopy ) { // toCopy = leftToRead; // } // System.arraycopy( block.getData(), dataOffset, // retval, offsetInBuffer, // toCopy ); // // // Go to the next block // leftToRead -= toCopy; // offsetInBuffer += toCopy; // // file.release( block ); // // if ( leftToRead > 0 ) { // block = file.get( curs.next() ); // dataOffset = DataPage.O_DATA; // } // // } // // return retval; // } void fetch(OutputStream out, long rowid) throws IOException { // fetch the record header PageCursor curs = new PageCursor(pageman, Location.getBlock(rowid)); BlockIo block = file.get(curs.getCurrent()); short head = Location.getOffset(rowid); // allocate a return buffer // byte[] retval = new byte[ head.getCurrentSize() ]; final int size = RecordHeader.getCurrentSize(block,head); if (size == 0) { file.release(curs.getCurrent(), false); return; } // copy bytes in int offsetInBuffer = 0; int leftToRead = size; short dataOffset = (short) (Location.getOffset(rowid) + RecordHeader.SIZE); while (leftToRead > 0) { // copy current page's data to return buffer int toCopy = BLOCK_SIZE - dataOffset; if (leftToRead < toCopy) { toCopy = leftToRead; } byte[] blockData = block.getData(); int finish = dataOffset + toCopy; out.write(blockData, dataOffset, finish - dataOffset); // Go to the next block leftToRead -= toCopy; offsetInBuffer += toCopy; // out.flush(); file.release(block); if (leftToRead > 0) { block = file.get(curs.next()); dataOffset = DataPage.O_DATA; } } // return retval; } /** * Allocate a new rowid with the indicated size. */ private long alloc(int size) throws IOException { size = RecordHeader.roundAvailableSize(size); long retval = freeman.get(size); if (retval == 0) { retval = allocNew(size, pageman.getLast(Magic.USED_PAGE)); } return retval; } /** * Allocates a new rowid. The second parameter is there to allow for a recursive call - it indicates where the * search should start. */ private long allocNew(int size, long start) throws IOException { BlockIo curBlock; DataPage curPage; if (start == 0) { // we need to create a new page. start = pageman.allocate(Magic.USED_PAGE); curBlock = file.get(start); curPage = DataPage.getDataPageView(curBlock,BLOCK_SIZE); curPage.setFirst(DataPage.O_DATA); RecordHeader.setAvailableSize(curBlock, DataPage.O_DATA, 0); RecordHeader.setCurrentSize(curBlock, DataPage.O_DATA, 0); } else { curBlock = file.get(start); curPage = DataPage.getDataPageView(curBlock,BLOCK_SIZE); } // follow the rowids on this page to get to the last one. We don't // fall off, because this is the last page, remember? short pos = curPage.getFirst(); if (pos == 0) { // page is exactly filled by the last block of a record file.release(curBlock); return allocNew(size, 0); } short hdr = pos; while (RecordHeader.getAvailableSize(curBlock, hdr) != 0 && pos < BLOCK_SIZE) { pos += RecordHeader.getAvailableSize(curBlock, hdr) + RecordHeader.SIZE; if (pos == BLOCK_SIZE) { // Again, a filled page. file.release(curBlock); return allocNew(size, 0); } hdr = pos; } if (pos == RecordHeader.SIZE) { // the last record exactly filled the page. Restart forcing // a new page. file.release(curBlock); } // we have the position, now tack on extra pages until we've got // enough space. long retval = Location.toLong(start, pos); int freeHere = BLOCK_SIZE - pos - RecordHeader.SIZE; if (freeHere < size) { // check whether the last page would have only a small bit left. // if yes, increase the allocation. A small bit is a record // header plus 16 bytes. int lastSize = (size - freeHere) % DATA_PER_PAGE; if ((DATA_PER_PAGE - lastSize) < (RecordHeader.SIZE + 16)) { size += (DATA_PER_PAGE - lastSize); size = RecordHeader.roundAvailableSize(size); } // write out the header now so we don't have to come back. RecordHeader.setAvailableSize(curBlock, hdr, size); file.release(start, true); int neededLeft = size - freeHere; // Refactor these two blocks! while (neededLeft >= DATA_PER_PAGE) { start = pageman.allocate(Magic.USED_PAGE); curBlock = file.get(start); curPage = DataPage.getDataPageView(curBlock, BLOCK_SIZE); curPage.setFirst((short) 0); // no rowids, just data file.release(start, true); neededLeft -= DATA_PER_PAGE; } if (neededLeft > 0) { // done with whole chunks, allocate last fragment. start = pageman.allocate(Magic.USED_PAGE); curBlock = file.get(start); curPage = DataPage.getDataPageView(curBlock, BLOCK_SIZE); curPage.setFirst((short) (DataPage.O_DATA + neededLeft)); file.release(start, true); } } else { // just update the current page. If there's less than 16 bytes // left, we increase the allocation (16 bytes is an arbitrary // number). if (freeHere - size <= (16 + RecordHeader.SIZE)) { size = freeHere; } RecordHeader.setAvailableSize(curBlock, hdr, size); file.release(start, true); } return retval; } protected void free(long id) throws IOException { // get the rowid, and write a zero current size into it. BlockIo curBlock = file.get(Location.getBlock(id)); DataPage curPage = DataPage.getDataPageView(curBlock,BLOCK_SIZE); RecordHeader.setCurrentSize(curBlock, Location.getOffset(id), 0); file.release(Location.getBlock(id), true); // write the rowid to the free list freeman.put(id, RecordHeader.getAvailableSize(curBlock, Location.getOffset(id))); } /** * Writes out data to a rowid. Assumes that any resizing has been done. */ protected void write(long rowid, byte[] data, int start, int length) throws IOException { PageCursor curs = new PageCursor(pageman, Location.getBlock(rowid)); BlockIo block = file.get(curs.getCurrent()); short hdr = Location.getOffset(rowid); RecordHeader.setCurrentSize(block, hdr, length); if (length == 0) { file.release(curs.getCurrent(), true); return; } // copy bytes in int offsetInBuffer = start; int leftToWrite = length; short dataOffset = (short) (Location.getOffset(rowid) + RecordHeader.SIZE); while (leftToWrite > 0) { // copy current page's data to return buffer int toCopy = BLOCK_SIZE - dataOffset; if (leftToWrite < toCopy) { toCopy = leftToWrite; } System.arraycopy(data, offsetInBuffer, block.getData(), dataOffset, toCopy); // Go to the next block leftToWrite -= toCopy; offsetInBuffer += toCopy; file.release(curs.getCurrent(), true); if (leftToWrite > 0) { block = file.get(curs.next()); dataOffset = DataPage.O_DATA; } } } void commit() throws IOException { freeman.commit(); } }