// jdbmBucket.java
// $Id: jdbmBucket.java,v 1.5 1998/09/14 15:57:02 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package org.w3c.tools.dbm ;
import java.io.* ;
class jdbmBucket {
/**
* debugging flag
*/
private static final boolean debug = false;
/**
* Number of available blocks to keep per bucket.
*/
private static final int AVAIL_SIZE = 6 ;
/**
* Below wich size should we ignore free blocks ?
*/
private final static int IGNORE_SIZE = 8 ;
/**
* File size of a Bucket, without its bucket elements.
* The number of bucket elements in a Bucket depends on the block size
* parameter of the database.
*/
static final int fsize =
(4 // bits used to this bucket
+ 4 // # of occupied elements
+ 4 // avail count
+ (AVAIL_SIZE*4) // avail size
+ (AVAIL_SIZE*4)); // avail ptrs
/**
* Number of bits used to get here.
*/
int bits = 0 ;
/**
* Number of elements bucket full.
*/
int count = 0 ;
/**
* List of buckets elements.
*/
jdbmBucketElement elements[] = null ;
/**
* Number of available blocks.
*/
int avail_count = 0 ;
/**
* Available blocks size.
*/
int avail_size[] = null ;
/**
* Available blocks file pointers.
*/
int avail_ptr[] = null ;
/**
* The database this bucket belongs to.
*/
jdbm db = null ;
/**
* Is this bucket modified ?
*/
boolean modified = false ;
/**
* This bucket adress in the DBF file.
*/
int fileptr = -1 ;
/**
* Does array a starts with the bytes of array s.
*/
private final boolean arrayStartsWith (byte a[], byte s[]) {
int len = Math.min(a.length, s.length) ;
for (int i = 0 ; i < len ; i++) {
if ( a[i] != s[i] ) {
if (debug)
db.trace("array doesn't start with.") ;
return false ;
}
}
if (debug)
db.trace("array matches.") ;
return true ;
}
/**
* Are two arrays equals ?
* @param a1 First array.
* @param a2 Second array.
* @return A boolean true if arrays are equals.
*/
private final boolean arrayEquals(byte a1[], byte a2[]) {
if (a1.length == a2.length) {
int len = a1.length ;
for (int i = 0 ; i < len ; i++)
if ( a1[i] != a2[i] )
return false ;
return true ;
}
return false ;
}
/**
* Save this bucket to the given data output stream.
* @param out The data output stream to write to.
* @exception IOException If some IO error occured.
*/
void save (DataOutputStream out)
throws IOException
{
out.writeInt(bits) ;
out.writeInt(count) ;
for (int i = 0 ; i < elements.length ; i++)
elements[i].save(out) ;
out.writeInt(avail_count) ;
for (int i = 0 ; i < avail_size.length ; i++) {
out.writeInt(avail_size[i]) ;
out.writeInt(avail_ptr[i]) ;
}
modified = false ;
}
/**
* Restore a bucket from the DBF file.
* @param in The data input stream to restore the bucket from.
* @param into Restore the bucket into this unused bucket.
* @exception IOException If some IO error occured.
*/
static jdbmBucket restore (DataInputStream in
, int fileptr
, jdbmBucket into)
throws IOException
{
into.fileptr = fileptr ;
into.bits = in.readInt() ;
into.count = in.readInt() ;
for (int i = 0 ; i < into.elements.length ; i++)
jdbmBucketElement.restore(in, into.elements[i]) ;
into.avail_count = in.readInt() ;
for (int i = 0 ; i < into.avail_size.length ; i++) {
into.avail_size[i] = in.readInt() ;
into.avail_ptr[i] = in.readInt() ;
}
return into ;
}
/**
* Lookup this bucket for a given key.
* @param key The key we are looking for.
* @param hashval The hash value for this key.
* @return A bucket elemennt, or null.
*/
protected jdbmBucketElement lookup (byte key[], int hashval)
throws IOException
{
int iloc = hashval % db.bucket_elems ;
int hloc = iloc ;
while (true) {
jdbmBucketElement el = elements[iloc] ;
if (debug)
db.trace("lookup: at "+iloc+" "+el) ;
if ( el.hashval == -1 )
return null ;
if ((el.hashval == hashval) && arrayStartsWith(key,el.keystart)) {
// It may be this one:
byte filekey[] = db.readKey(el) ;
if (arrayEquals(filekey, key))
return el ;
}
// It is not this one, continue, or halt:
iloc = (iloc+1) % elements.length ;
if ( iloc == hloc )
return null ;
}
}
/**
* Add a new association to this bucket.
* It is up to the caller to make sure that this won't require a bucket
* split.
* @param hashval The already computed hash value for the key.
* @param key The key as a bytes[].
* @param value The value as a byte[].
*/
protected void add (int hashval, byte key[], byte value[])
throws IOException
{
if ( count >= db.bucket_elems )
throw new RuntimeException("implementation error.") ;
// Find a suitable location:
int iloc = hashval % db.bucket_elems ;
while ( true ) {
jdbmBucketElement el = elements[iloc] ;
if (debug)
db.trace("elem["+iloc+"] of "+el);
if ( el.hashval == -1 ) {
// Got a free element:
el.hashval = hashval ;
System.arraycopy(key, 0
, el.keystart, 0
, ((key.length <= jdbmBucketElement.KEYSTART)
? key.length
: jdbmBucketElement.KEYSTART)) ;
el.key_size = key.length ;
el.data_size = value.length ;
el.fileptr = db.write(this, key, value) ;
modified = true ;
count++ ;
return ;
}
iloc = (iloc+1) % elements.length ;
}
}
/**
* Remove the avail block at given index from our avail list.
* @param idx The index of the avail block to remove.
*/
private final void removeAvailable(int idx) {
modified = true ;
avail_count-- ;
// The last one is easily thrown away:
if (idx == avail_count)
return ;
// Displace all avail, etc.
System.arraycopy(avail_size, idx+1, avail_size, idx, avail_count-idx);
System.arraycopy(avail_ptr, idx+1, avail_ptr, idx, avail_count-idx) ;
}
/**
* Mark the given item to be available for this bucket.
* It is up to the caller to make sure that the bucket avail list size
* will not be exceeded by this new item. Items are kept sorted by size
* in the avail_list.
* @param ptr The item file pointer.
* @param size The item size.
*/
private void markAvailable(int ptr, int size) {
modified = true ;
// Keep the list sorted:
for (int i = 0 ; i < avail_count ; i++) {
if ( avail_size[i] >= size ) {
// There we are:
System.arraycopy(avail_size,i,avail_size,i+1,avail_count-i);
System.arraycopy(avail_ptr,i,avail_ptr,i+1,avail_count-i);
avail_count++ ;
avail_size[i] = size ;
avail_ptr[i] = ptr ;
return ;
}
}
// Wow our biggest avail item
avail_size[avail_count] = size ;
avail_ptr[avail_count] = ptr ;
avail_count++;
}
/**
* Fix an available block to fit the requested size.
* @param idx The index of thenewly allocated avail.
* @param size The size we were requested to allocate.
* @return The fileptr of the allocated avail block.
*/
private int fixAvailable(int idx, int size) {
modified = true ;
int fileptr = avail_ptr[idx] ;
int nsize = avail_size[idx] -= size ;
int nptr = avail_ptr[idx] += size ;
removeAvailable(idx) ;
// Ignore remaining bytes if too small
if ( nsize <= IGNORE_SIZE )
return fileptr ;
// If remaining chunk is big, pass it to out db.
if ( nsize >= db.block_size ) {
// This is big, leave it for the db to manage:
db.markAvailable (nptr, nsize) ;
} else {
// Since we just got rid of an avail, we can plug a new one:
markAvailable(nptr, nsize) ;
}
return fileptr ;
}
/**
* Remove the given element of this bucket.
* @param el The element whose content is to be freed.
*/
protected void delete (jdbmBucketElement el) {
int size = el.key_size+el.data_size ;
if ((size >= db.block_size) || (avail_count+1 >= AVAIL_SIZE)) {
db.markAvailable(el.fileptr, size) ;
} else {
markAvailable(el.fileptr, el.key_size+el.data_size) ;
}
// Clear the bucket element:
int elem_loc = el.hashval % db.bucket_elems ;
// Following two lines suggested by Glen Diener
// Basically avoid hash collisions
while(elements[elem_loc] != el)
elem_loc = (elem_loc+1) % db.bucket_elems ;
el.hashval = -1 ;
count-- ;
// Check hash code of following elements, if any:
int last_loc = elem_loc ;
elem_loc = (elem_loc+1) % db.bucket_elems ;
while ((elem_loc != last_loc) && (elements[elem_loc].hashval != -1)) {
int h = elements[elem_loc].hashval % db.bucket_elems ;
if (((last_loc < elem_loc) && ((h <= last_loc) || (h > elem_loc)))
|| (last_loc > elem_loc && h <= last_loc && h > elem_loc)) {
if (debug)
db.trace("saving: "+elem_loc+" "+
elements[elem_loc]+" into "+last_loc);
elements[last_loc] = elements[elem_loc] ;
elements[elem_loc] = new jdbmBucketElement() ;
last_loc = elem_loc ;
}
elem_loc = (elem_loc+1) % db.bucket_elems ;
}
modified = true ;
return ;
}
/**
* Allocate some space in this bucket.
* @param size The size of the block to allocate.
* @return A valid file pointer to the block, or -1 if
* requested space wasn't available.
*/
protected int allocateSpace(int size) {
for (int i = 0 ; i < avail_count ; i++) {
if ( avail_size[i] >= size ) {
// Got it ! return this one.
return fixAvailable(i, size) ;
}
}
return -1 ;
}
jdbmBucket(jdbm db, int fileptr, int availblock) {
this.db = db ;
this.fileptr = fileptr ;
this.elements = new jdbmBucketElement[db.bucket_elems] ;
for (int i = 0 ; i < this.elements.length ; i++)
this.elements[i] = new jdbmBucketElement() ;
this.avail_count = 0 ;
this.avail_size = new int[AVAIL_SIZE] ;
this.avail_ptr = new int[AVAIL_SIZE] ;
if ( availblock >= 0 ) {
avail_count = 1 ;
avail_size[0] = db.block_size ;
avail_ptr[0] = availblock*db.block_size ;
}
}
}