/**
   * Remove the value which is associated with the given key. If the key does not exist, this method
   * simply ignores the operation.
   *
   * @param key key whose associated value is to be removed
   * @return object which was associated with the given key, or <code>null</code> if no association
   *     existed with given key.
   */
  Object remove(Object key) throws IOException {
    int hash = hashCode(key);
    long child_recid = _children[hash];
    if (child_recid == 0) {
      // not bucket/page --> not found
      return null;
    } else {
      HashNode node = (HashNode) _recman.fetch(child_recid, tree.SERIALIZER);
      // System.out.println("HashDirectory.remove() child is : "+node);

      if (node instanceof HashDirectory) {
        // recurse into next directory level
        HashDirectory dir = (HashDirectory) node;
        dir.setPersistenceContext(_recman, child_recid);
        Object existing = dir.remove(key);
        if (existing != null) {
          if (dir.isEmpty()) {
            // delete empty directory
            _recman.delete(child_recid);
            _children[hash] = 0;
            _recman.update(_recid, this, tree.SERIALIZER);
          }
        }
        return existing;
      } else {
        // node is a bucket
        HashBucket bucket = (HashBucket) node;
        Object existing = bucket.removeElement(key);
        if (existing != null) {
          if (bucket.getElementCount() >= 1) {
            _recman.update(child_recid, bucket, tree.SERIALIZER);
          } else {
            // delete bucket, it's empty
            _recman.delete(child_recid);
            _children[hash] = 0;
            _recman.update(_recid, this, tree.SERIALIZER);
          }
        }
        return existing;
      }
    }
  }
  /**
   * Returns the value which is associated with the given key. Returns <code>null</code> if there is
   * not association for this key.
   *
   * @param key key whose associated value is to be returned
   */
  V get(K key) throws IOException {
    int hash = hashCode(key);
    long child_recid = _children[hash];
    if (child_recid == 0) {
      // not bucket/page --> not found
      return null;
    } else {
      HashNode<K, V> node = (HashNode<K, V>) _recman.fetch(child_recid, tree.SERIALIZER);
      // System.out.println("HashDirectory.get() child is : "+node);

      if (node instanceof HashDirectory) {
        // recurse into next directory level
        HashDirectory<K, V> dir = (HashDirectory<K, V>) node;
        dir.setPersistenceContext(_recman, child_recid);
        return dir.get(key);
      } else {
        // node is a bucket
        HashBucket<K, V> bucket = (HashBucket) node;
        return bucket.getValue(key);
      }
    }
  }
  /**
   * Associates the specified value with the specified key.
   *
   * @param key key with which the specified value is to be assocated.
   * @param value value to be associated with the specified key.
   * @return object which was previously associated with the given key, or <code>null</code> if no
   *     association existed.
   */
  Object put(Object key, Object value) throws IOException {
    if (value == null) {
      return remove(key);
    }
    int hash = hashCode(key);
    long child_recid = _children[hash];
    if (child_recid == 0) {
      // no bucket/page here yet, let's create a bucket
      HashBucket bucket = new HashBucket(tree, _depth + 1);

      // insert (key,value) pair in bucket
      Object existing = bucket.addElement(key, value);

      long b_recid = _recman.insert(bucket, tree.SERIALIZER);
      _children[hash] = b_recid;

      _recman.update(_recid, this, tree.SERIALIZER);

      // System.out.println("Added: "+bucket);
      return existing;
    } else {
      HashNode node = (HashNode) _recman.fetch(child_recid, tree.SERIALIZER);

      if (node instanceof HashDirectory) {
        // recursive insert in next directory level
        HashDirectory dir = (HashDirectory) node;
        dir.setPersistenceContext(_recman, child_recid);
        return dir.put(key, value);
      } else {
        // node is a bucket
        HashBucket bucket = (HashBucket) node;
        if (bucket.hasRoom()) {
          Object existing = bucket.addElement(key, value);
          _recman.update(child_recid, bucket, tree.SERIALIZER);
          // System.out.println("Added: "+bucket);
          return existing;
        } else {
          // overflow, so create a new directory
          if (_depth == MAX_DEPTH) {
            throw new RuntimeException("Cannot create deeper directory. " + "Depth=" + _depth);
          }
          HashDirectory dir = new HashDirectory(tree, (byte) (_depth + 1));
          long dir_recid = _recman.insert(dir, tree.SERIALIZER);
          dir.setPersistenceContext(_recman, dir_recid);

          _children[hash] = dir_recid;
          _recman.update(_recid, this, tree.SERIALIZER);

          // discard overflown bucket
          _recman.delete(child_recid);

          // migrate existing bucket elements
          ArrayList keys = bucket.getKeys();
          ArrayList values = bucket.getValues();
          int entries = keys.size();
          for (int i = 0; i < entries; i++) {
            dir.put(keys.get(i), values.get(i));
          }

          // (finally!) insert new element
          return dir.put(key, value);
        }
      }
    }
  }