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