blob: eb2539b00de6d9e7874c8447a7b4778af38056b6 [file] [log] [blame]
/**
* 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 jdbm.recman;
import java.io.IOException;
/**
* This class manages physical row ids, and their data.
*/
final class PhysicalRowIdManager
{
// The file we're talking to and the associated page manager.
private RecordFile file;
private PageManager pageman;
private FreePhysicalRowIdPageManager freeman;
/**
* Creates a new rowid manager using the indicated record file.
* and page manager.
*/
PhysicalRowIdManager( RecordFile file, PageManager pageManager )
throws IOException
{
this.file = file;
this.pageman = pageManager;
this.freeman = new FreePhysicalRowIdPageManager(file, pageman);
}
/**
* Inserts a new record. Returns the new physical rowid.
*/
Location insert( byte[] data, int start, int length )
throws IOException
{
Location retval = alloc( length );
write( retval, data, start, length );
return retval;
}
/**
* Updates an existing record. Returns the possibly changed
* physical rowid.
*/
Location update( Location rowid, byte[] data, int start, int length )
throws IOException
{
// fetch the record header
BlockIo block = file.get( rowid.getBlock() );
RecordHeader head = new RecordHeader( block, rowid.getOffset() );
if ( length > head.getAvailableSize() ) {
// 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( Location 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;
}
/**
* Allocate a new rowid with the indicated size.
*/
private Location alloc( int size )
throws IOException
{
Location retval = freeman.get( size );
if ( retval == null ) {
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 Location 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 );
curPage.setFirst( DataPage.O_DATA );
RecordHeader hdr = new RecordHeader( curBlock, DataPage.O_DATA );
hdr.setAvailableSize( 0 );
hdr.setCurrentSize( 0 );
} else {
curBlock = file.get( start );
curPage = DataPage.getDataPageView( curBlock );
}
// 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 );
}
RecordHeader hdr = new RecordHeader( curBlock, pos );
while ( hdr.getAvailableSize() != 0 && pos < RecordFile.BLOCK_SIZE ) {
pos += hdr.getAvailableSize() + RecordHeader.SIZE;
if ( pos == RecordFile.BLOCK_SIZE ) {
// Again, a filled page.
file.release( curBlock );
return allocNew( size, 0 );
}
hdr = new RecordHeader( curBlock, 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.
Location retval = new Location( start, pos );
int freeHere = RecordFile.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) % DataPage.DATA_PER_PAGE;
if (( DataPage.DATA_PER_PAGE - lastSize ) < (RecordHeader.SIZE + 16) ) {
size += (DataPage.DATA_PER_PAGE - lastSize);
}
// write out the header now so we don't have to come back.
hdr.setAvailableSize( size );
file.release( start, true );
int neededLeft = size - freeHere;
// Refactor these two blocks!
while ( neededLeft >= DataPage.DATA_PER_PAGE ) {
start = pageman.allocate( Magic.USED_PAGE );
curBlock = file.get( start );
curPage = DataPage.getDataPageView( curBlock );
curPage.setFirst( (short) 0 ); // no rowids, just data
file.release( start, true );
neededLeft -= DataPage.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 );
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;
}
hdr.setAvailableSize( size );
file.release( start, true );
}
return retval;
}
private void free( Location id )
throws IOException
{
// get the rowid, and write a zero current size into it.
BlockIo curBlock = file.get( id.getBlock() );
DataPage curPage = DataPage.getDataPageView( curBlock );
RecordHeader hdr = new RecordHeader( curBlock, id.getOffset() );
hdr.setCurrentSize( 0 );
file.release( id.getBlock(), true );
// write the rowid to the free list
freeman.put( id, hdr.getAvailableSize() );
}
/**
* Writes out data to a rowid. Assumes that any resizing has been
* done.
*/
private void write(Location rowid, byte[] data, int start, int length )
throws IOException
{
PageCursor curs = new PageCursor( pageman, rowid.getBlock() );
BlockIo block = file.get( curs.getCurrent() );
RecordHeader hdr = new RecordHeader( block, rowid.getOffset() );
hdr.setCurrentSize( length );
if ( length == 0 ) {
file.release( curs.getCurrent(), true );
return;
}
// copy bytes in
int offsetInBuffer = start;
int leftToWrite = length;
short dataOffset = (short) (rowid.getOffset() + RecordHeader.SIZE);
while ( leftToWrite > 0 ) {
// copy current page's data to return buffer
int toCopy = RecordFile.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;
}
}
}
}