/** | |
* 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; | |
} | |
} | |
} | |
} | |