blob: 4b95905eba121b29c2fcb21f2a370cf9b6db3913 [file] [log] [blame]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
etch_hash.c -- implementation of an underlying hashtable.
provided herein are implementations for the jenkins hashtable APIs.
*/
#include "etch_hash.h"
#include "etch_cache.h"
#include "etch_mem.h"
#include "etch_mutex.h"
#include "etch_objecttypes.h"
#include "etch_arraylist.h"
#include "jenkhtab.h"
#include "jenklook.h"
#define ETCH_HASH_MAX_KEYLENGTH 1024 /* arbitrary max byte length of a hashtable key */
// extern types
extern apr_pool_t* g_etch_main_pool;
/**
* implementation of etch_hashtable.insert() for the jenkins hashtable.
* key and data are pointers to memory owned by the caller. The hashtable does
* not make copies of this data. The caller is responsible for freeing said
* memory, however note that etch_hashtable.clear() frees this memory.
* key cannot be null but data can be null.
* datalen parameter is ignored for this implementation.
* if out is non_null, *out is assumed to point at a etch_hashitem struct,
* and this struct is populated with pointers to the inserted item.
* result is zero if OK, otherwise -1;
*/
int jenkins_insert(void* realtable, void* key, const int keylen,
void* data, const int datalen, void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int result = 0;
etch_hashtable* ht = (etch_hashtable*)in;
if (!realtable || !key || !keylen)
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (FALSE == hadd((htab*)realtable, key, keylen, data)) /* jenkhash.lib */
result = -1; /* key already exists most likely */
else
if (out)
{ etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = hkey ((htab*)realtable);
outentry->value = hstuff((htab*)realtable);
outentry->hash = ((htab*)realtable)->ipos->hval;
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/**
* implementation of etch_hashtable.inserth() for the jenkins hashtable.
* key and data are pointers to memory owned by the caller. the hashtable does
* not make copies of this data. the caller is responsible for freeing said
* memory, however note that etch_hashtable.clear() frees this memory.
* key cannot be null but data can be null.
* key object is expected to contain its hashkey value in its first 4 bytes.
* jenkins will use this value, rather than compute a hash value.
* if out is non_null, *out is assumed to point at a etch_hashitem struct,
* and this struct is populated with pointers to the inserted item.
* result is zero if OK, otherwise -1;
*/
int jenkins_inserth(void* realtable, void* key, void* data, void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int result = 0;
etch_hashtable* ht = (etch_hashtable*) in;
if (!realtable || !key)
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (FALSE == haddx((htab*)realtable, key, data)) /* jenkhash.lib */
result = -1; /* key already exists most likely */
else
if (out)
{
etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = hkey ((htab*)realtable);
outentry->value = hstuff((htab*)realtable);
outentry->hash = ((htab*)realtable)->ipos->hval;
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/**
* implementation of etch_hashtable.find() for the jenkins hashtable.
* moves the current position on the underlying table to that of supplied key.
* if out is non_null, *out is assumed to point at a etch_hashitem struct,
* and this struct is populated with pointers to the located item's key and value.
* result is zero if OK, otherwise -1;
*/
int jenkins_find(void* realtable, void* key, const int keylen,
void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int result = 0;
etch_hashtable* ht = (etch_hashtable*) in;
if (!realtable || !key || !keylen)
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (FALSE == hfind((htab*)realtable, (unsigned char*)key, keylen))
result = -1;
else
if (out)
{ etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = hkey ((htab*)realtable);
outentry->value = hstuff((htab*)realtable);
outentry->hash = ((htab*)realtable)->ipos->hval;
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/**
* implementation of etch_hashtable.findh() for the jenkins hashtable.
* Implements a find by hashed key, otherwise see comments for find().
* result is zero if OK, otherwise -1;
*/
int jenkins_findh(void* realtable, const unsigned int hashed,
void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int result = 0;
etch_hashtable* ht = (etch_hashtable*) in;
if (!realtable || !hashed)
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (FALSE == hfindx((htab*)realtable, hashed))
result = -1;
else
if (out)
{ char* tkey = hkey ((htab*)realtable);
void* data = hstuff((htab*)realtable);
etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = tkey;
outentry->value = data;
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/**
* implementation of etch_hashtable.first() for the jenkins hashtable.
* moves the current position on the underlying table to that of the first item.
* If out is non_null, *out is assumed to point at a etch_hashitem struct,
* and this struct is populated with pointers to the located item's key and value.
* in parameter is ignored for this implementation.
* result is zero if OK, otherwise -1, indicating bad params or an empty table.
* @note this method is NOT SYNCHRONIZED, since in most or all cases it is invoked
* only during iteration of the map, and in that case the map is locked explicitly
* by the caller (hashtable_setlock()), and the mutex may not support nesting.
* if there is a need to synch it otherwise, wrap the call as follows:
* map->synchook(ETCH_SYNC_SET_, ((etch_object*)map)->synclock);
* map->first(...);
* map->synchook(ETCH_SYNC_REL_, ((etch_object*)map)->synclock);
*/
int jenkins_first(void* realtable, void* in, void** out)
{
int result = 0;
if (FALSE == hfirst((htab*)realtable))
result = -1; /* table is empty most likely */
else
if (out)
{ char* tkey = hkey ((htab*)realtable);
void* data = hstuff((htab*)realtable);
etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = tkey;
outentry->value = data;
}
return result;
}
/**
* implementation of etch_hashtable.next() for the jenkins hashtable.
* Moves the current position on the underlying table to that of the next item
* in the table. If out is non_null, *out is assumed to point at a etch_hashitem,
* and this struct is populated with pointers to the located item's key and value.
* in parameter is ignored for this implementation.
* result is zero if OK, otherwise -1, indicating either bad params, or that
* there are no more entries, in which case the current position will have wrapped
* to the first item, if any entries in fact still remain.
* @note this method is NOT SYNCHRONIZED, since in most or all cases it is invoked
* only during iteration of the map, and in that case the map is locked explicitly
* by the caller (hashtable_setlock()), and the mutex may not support nesting.
* if there is a need to synch it otherwise, wrap the call as follows:
* map->synchook(ETCH_SYNC_SET_, ((etch_object*)map)->synclock);
* map->next(...);
* map->synchook(ETCH_SYNC_REL_, ((etch_object*)map)->synclock);
*/
int jenkins_next(void* realtable, void* in, void** out)
{
int is_next_found = 0, is_table_empty = 0;
char* tkey = NULL;
void* data = NULL;
if (!realtable) return -1;
/* hnext returns 1 if there is a next entry, or 0 if there is no next entry
* and the position has wrapped to the beginning of the table. However if
* the table is now empty, there is no current position, and so we test for
* that condition before attempting to reference the current position.
*/
is_next_found = hnext((htab*)realtable); /* jenkhash.h */
is_table_empty = NULL == ((htab*)realtable)->ipos;
if (out) /* return data at current position if requested */
{ etch_hashitem* outentry = (etch_hashitem*) *out;
if (!is_table_empty)
{ tkey = hkey ((htab*)realtable);
data = hstuff((htab*)realtable);
}
outentry->key = tkey;
outentry->value = data;
}
return is_next_found? 0: -1;
}
/**
* implementation of etch_hashtable.current() for the jenkins hashtable.
* retrieves data for the entry at the current hashtable position.
* *out is assumed to point at a etch_hashitem struct; this struct is populated
* with pointers to the current item's key and value.
* in parameter is ignored for this implementation.
* result is zero if OK, otherwise -1, indicating bad params or an empty table.
* @note this method is NOT SYNCHRONIZED, since it is not meaningful for a shared
* hashtable (current position will change with every operation).
* if there is a need to synch it, wrap the call as follows:
* map->synchook(ETCH_SYNC_SET_, ((etch_object*)map)->synclock);
* map->current(...);
* map->synchook(ETCH_SYNC_REL_, ((etch_object*)map)->synclock);
*/
int jenkins_current(void* realtable, void* in, void** out)
{
unsigned hash = 0;
char* tkey = NULL;
void* data = NULL;
etch_hashitem* outentry = NULL;
if (!realtable || !out || !((htab*)realtable)->count) return -1;
tkey = hkey ((htab*)realtable);
data = hstuff((htab*)realtable);
hash =((htab*)realtable)->ipos->hval;
outentry = (etch_hashitem*) *out;
outentry->key = tkey;
outentry->value = data;
outentry->hash = hash;
return tkey? 0: -1;
}
/**
* Implementation of etch_hashtable.remove() for the jenkins hashtable.
* Frees the entry for the key supplied , but neither the key nor the value,
* are freed, recall that neither of these is a copy but are instead pointers.
* To actually free memory for these items, pass etch_hashitem** &out),
* and you can then free(out->key), and free(out->value), at your leisure.
* Result is zero if OK, otherwise -1;
*/
int jenkins_remove(void* realtable, void* key, const int keylen,
void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int result = 0;
etch_hashtable* ht = (etch_hashtable*) in;
if (!realtable || !key || !keylen)
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (FALSE == hfind((htab*)realtable, key, keylen)) /* locate entry */
result = -1; /* key nonexistent most likely */
else
{ if (out) /* save off the entry contents if requested */
{ etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = hkey ((htab*)realtable);
outentry->value = hstuff((htab*)realtable);
outentry->hash = ((htab*)realtable)->ipos->hval;
}
hdel((htab*)realtable); /* delete entry at current position */
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/**
* jenkins_removeh()
*/
int jenkins_removeh(void* realtable, const unsigned key, void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int result = 0;
etch_hashtable* ht = (etch_hashtable*) in;
if (!realtable || !key)
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (FALSE == hfindx((htab*)realtable, key)) /* locate entry */
result = -1;
else
{ if (out)
{ etch_hashitem* outentry = (etch_hashitem*) *out;
outentry->key = hkey ((htab*)realtable);
outentry->value = hstuff((htab*)realtable);
}
hdel((htab*)realtable); /* delete entry at current position */
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return result;
}
/**
* implementation of etch_hashtable.clear() for the jenkins hashtable.
* empties the table and, if requested, frees memory for keys/and/or values.
* out parameter is ignored for this implementation.
* Result is count of table entries freed, or -1 if error.
* Use the freeuser parameter with care. recall that the hashtable stores
* pointers to keys and data, plus key length. If user did not allocate each
* key separately then setting freeuser would cause a crash. for example,
* if I used a vector of keys and a vector of key lengths; setting freeuser
* would ask to free (key length) bytes at some vector offset, obviously
* an error. also, currently freeuser does not check the memtable, so if
* allocations are being tracked, freeuser should not be used. we could
* change this code to query the memtable first, or alternatively, change
* etch_free to not complain about allegedly missing memtable entries.
*/
int jenkins_clear (void* realtable, const int freekey, const int freeval, void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
int freecount = 0, currcount = 0, freehandled = 0;
int is_static_keys = 0, is_static_values = 0, is_etch_free = 0;
mapcallback callback = NULL;
char* key, *value;
htab* jenktable;
etch_hashtable* ht = (etch_hashtable*) in;
if (!(jenktable = (htab*)realtable))
return -1;
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_lock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
if (ht)
{ is_static_keys = ht->is_readonly_keys;
is_static_values = ht->is_readonly_values;
is_etch_free = ht->is_tracked_memory;
callback = ht->freehook;
}
while (0 < (currcount = hcount(jenktable)))
{
key = hkey(jenktable); /* free entry's key if asked */
value = hstuff(jenktable);
/* optional callback to handle free */
freehandled = callback? callback(key, value): FALSE;
if (freekey && !is_static_keys && !freehandled)
if(is_etch_free)
etch_free(key);
else
free(key);
if (freeval && !is_static_values && !freehandled)
if (is_etch_free)
etch_free(value);
else free(value);
hdel(jenktable); /* free current table slot */
freecount++;
}
if (ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return freecount; /* return count of items freed */
}
/**
* implementation of etch_hashtable.count() for the jenkins hashtable.
* in and out parameters are ignored for this implementation.
* result is current number of table entries, or -1 if error.
*/
int jenkins_count(void* realtable, void* in, void** out)
{
const int count = realtable? ((htab*)realtable)->count: -1;
return count;
}
/**
* implementation of etch_hashtable.size() for the jenkins hashtable.
* in and out parameters are ignored for this implementation.
* result is current maximum number of table entries, or -1 if error.
*/
int jenkins_size(void* realtable, void* in, void** out)
{
const int count = realtable? 1 << ((htab*)realtable)->logsize: -1;
return count;
}
/**
* implementation of etch_hashtable.stats() for the jenkins hashtable.
* in and out parameters are ignored for this implementation.
* result is current maximum number of table entries, or -1 if error.
*/
int jenkins_stats(void* realtable, void* in, void** out)
{
if (realtable) hstat((htab*)realtable);
return realtable? 0: -1;
}
/**
* jenkins_hash
* implementation of etch_hashtable.hash() for the jenkins hashtable.
* priorhash is result of the previous operation, or any arbitrary value.
* in and out parameters are ignored for this implementation. result is a
* hash of the supplied key, as used by the jenkins hashtable, or zero
* if parameters were in error.
* author's comments: If you need less than 32 bits, use a bitmask.
* for example, if you need only 10 bits, do h = (h & hashmask(10)),
* in which case, the hash table should have hashsize(10) elements.
* if you are hashing n strings (unsigned char**)k, do it like this:
* for (i=0, h=0; i < n; ++i) h = lookup(k[i], len[i], h);
*/
int jenkins_hash(void* realtable, char* key, const int keylen, const int priorhash, void* in, void** out)
{
if (!realtable || !key || keylen < 1 || keylen > ETCH_HASH_MAX_KEYLENGTH) {
//TODO: log error
return 0;
}
return lookup(key, keylen, priorhash);
}
/**
* jenkins_hashx
* see comments at jenkins_hash
*/
int jenkins_hashx(char* key, const int keylen, const int priorhash)
{
if (!key || keylen < 1 || keylen > ETCH_HASH_MAX_KEYLENGTH) return 0;
return lookup(key, keylen, priorhash);
}
/* - - - - - - - - - - - - - - - - - - - - -
* constructors, destructors
* - - - - - - - - - - - - - - - - - - - - -
*/
/**
* constructor for a hashtable implementation. implements iterable.
* creates the underlying hashtable and returns a populated etch_hashtable
* interface to it. initialsize is the number of items the hashtable can hold
* before it reallocates itself. note that this value may be altered by the
* implementation, e.g. if it is out of range or if it is not a power of 2.
*/
etch_hashtable* new_hashtable(const unsigned int initialsize)
{
etch_hashtable* hashtable = ctor_jenkins_hashtable(initialsize);
new_iterable(&hashtable->iterable, NULL, hashtable_iterable_first, hashtable_iterable_next, hashtable_iterable_has_next);
hashtable->is_readonly_keys = HASHTABLE_DEFAULT_READONLY_KEYS;
hashtable->is_readonly_values = HASHTABLE_DEFAULT_READONLY_VALUES;
hashtable->is_tracked_memory = HASHTABLE_DEFAULT_TRACKED_MEMORY;
hashtable->content_type = HASHTABLE_DEFAULT_CONTENT_TYPE;
return hashtable;
}
etch_hashtable* new_hashtable_synchronized(const unsigned int initialsize)
{
etch_status_t status = ETCH_SUCCESS;
etch_hashtable* hashtable = NULL;
hashtable = new_hashtable(initialsize);
// TODO: pool
status = etch_mutex_create(&((etch_object*)hashtable)->synclock, ETCH_MUTEX_NESTED, NULL);
if(status != ETCH_SUCCESS) {
// error
}
return hashtable;
}
/**
* constructor for etch_set, which is a hashtable containing object keys
* and null values
*/
etch_set* new_set (const int initialsize)
{
etch_set* newset = new_hashtable_synchronized(initialsize);
newset->content_type = ETCHHASHTABLE_CONTENT_OBJECT_NONE;
newset->is_readonly_values = TRUE;
return newset;
}
/**
* destructor for a hashtable implementation.
* is_free_keys parameter asks that memory pointed to by hashbucket keys be freed.
* Use this with care, since this obviously requires that you have malloc'ed keys
* individually, and did not, for example, hash your keys out of a memory vector,
* static variables, or the like. is_free_values likewise for the table content.
* The readonly flags set on the hashtable take precedence over either.
* Use of either of course means your pointers to content will now be dangling.
*/
int destroy_hashtable(etch_hashtable* map, const int is_free_keys, const int is_free_values)
{
etch_status_t status = ETCH_SUCCESS;
int is_free_keymem = 0, is_free_valmem = 0;
if (NULL == map) return -1;
if (((etch_object*)map)->synclock) {
status = etch_mutex_trylock(((etch_object*)map)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
if (!is_etchobj_static_content(map))
{
struct i_hashtable* vtab = (struct i_hashtable*)((etch_object*)map)->vtab;
is_free_keymem = !map->is_readonly_keys && is_free_keys;
is_free_valmem = !map->is_readonly_values && is_free_values;
/* free all buckets, and also contents only if is_free_contents is set */
if (map->realtable && vtab && !is_etchobj_static_content(map))
{
vtab->clear(map->realtable, is_free_keymem, is_free_valmem, map, 0);
vtab->hdestroy(map->realtable, map, 0);
}
} else {
}
if (((etch_object*)map)->synclock) {
status = etch_mutex_unlock(((etch_object*)map)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
if (!is_etchobj_static_content(map)) {
status = etch_mutex_destroy(((etch_object*)map)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
}
//TODO:
// cleanup synclock
// maybe it is done by map->synchook(ETCH_SYNC_DEL
if (!is_etchobj_static_shell(map)) {
etch_free(map);
}
return 0;
}
/**
* implementation of etch_hashtable.destroy() for the jenkins hashtable.
* destroys the table, but not the memory allocated for the individual item
* keys and values. use clear(), not destroy(), for that purpose.
* out parameter is ignored for this implementation.
* result is zero if OK, otherwise -1;
*/
int jenkins_destroy(void* realtable, void* in, void** out)
{
etch_status_t status = ETCH_SUCCESS;
etch_hashtable* ht = (etch_hashtable*) in;
if (!realtable)
return -1;
if(ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_trylock(((etch_object*)ht)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
if (((htab*)realtable)->table)
hdestroy((htab*) realtable); /* jenkhash.lib */
if(ht && ((etch_object*)ht)->synclock) {
status = etch_mutex_unlock(((etch_object*)ht)->synclock);
ETCH_ASSERT(status == ETCH_SUCCESS);
}
return 0;
}
/**
* implementation of etch_hashtable.create() for the jenkins hashtable.
* this is the constructor for the underlying hashtable.
* we receive initial size in items and convert this to bit width.
* If initial size supplied is not a power of 2 we make it so.
* jenkins takes a log2 value as size, e.g. size 6 means size is 6 bits wide = 64.
* returns in *out, a pointer to a jenkins htab struct describing the underlying table.
* in parameter is ignored for this implementation.
* result is zero if OK, otherwise -1;
*/
int jenkins_create(const int initialsize_items, void* in, void** out)
{
htab* hashtable = NULL;
int initialsize_bits_plus1 = 0, initialsize, divby2;
if (out == NULL) return -1;
if (initialsize_items <= 0)
initialsize = ETCH_DEFAULT_HASHTABLE_SIZE;
else
if (initialsize_items < MIN_INITIAL_HASHTABLE_SIZE)
initialsize = MIN_INITIAL_HASHTABLE_SIZE;
else initialsize = initialsize_items;
if (initialsize > MAX_INITIAL_HASHTABLE_SIZE)
initialsize = MAX_INITIAL_HASHTABLE_SIZE;
for (divby2 = initialsize; divby2; divby2 >>= 1)
initialsize_bits_plus1++;
hashtable = hcreate(initialsize_bits_plus1 - 1); /* jenkhash.lib */
*out = hashtable;
return hashtable? 0: -1;
}
/**
* destroy_hashtable_default()
* default destructor for jenkins hashtable
*/
int destroy_hashtable_default(etch_hashtable* map)
{
destroy_hashtable(map, !map->is_readonly_keys, !map->is_readonly_values);
return 0;
}
/**
* clone_hashtable_default()
* default copy constructor for jenkins hashtable.
* we won't do an implementation at this level, since we would need to also clone
* content, and only the instantiator knows key/value sizes
*/
etch_hashtable* clone_hashtable_default(etch_hashtable* map)
{
return NULL;
}
/**
* constructor for the etch_hashtable interface. constructs and initializes
* the interface shell, but not the underlying hashtable.
*/
etch_hashtable* _new_etch_hashtable()
{
etch_hashtable* newht = 0;
newht = (etch_hashtable*) new_object(sizeof(etch_hashtable),ETCHTYPEB_HASHTABLE,CLASSID_HASHTABLE);
return newht;
}
/**
* ctor_jenkins_hashtable()
* constructor for jenkins hashtable interface.
* populates interface implementation methods and creates the underlying hashtable.
* returns pointer to etch_hashtable, or NULL if table could not be created.
*/
etch_hashtable* ctor_jenkins_hashtable(const int initialsize_items)
{
htab* jenkins_hashtable = NULL;
etch_hashtable* hashtable = NULL;
i_etch_hashtable* vtab = NULL;
const unsigned short CLASS_ID = CLASSID_HASHTABLE_VTAB;
int result = 0, is_just_cached = FALSE;
hashtable = _new_etch_hashtable();
vtab = etch_cache_find(get_vtable_cachehkey(CLASS_ID), 0);
if(!vtab)
{
vtab = new_vtable(((etch_object*)hashtable)->vtab, sizeof(i_etch_hashtable), CLASS_ID);
vtab->create = jenkins_create;
vtab->hdestroy = jenkins_destroy;
vtab->insert = jenkins_insert;
vtab->inserth = jenkins_inserth;
vtab->find = jenkins_find;
vtab->findh = jenkins_findh;
vtab->first = jenkins_first;
vtab->next = jenkins_next;
vtab->current = jenkins_current;
vtab->remove = jenkins_remove;
vtab->removeh = jenkins_removeh;
vtab->clear = jenkins_clear;
vtab->count = jenkins_count;
vtab->size = jenkins_size;
vtab->stats = jenkins_stats;
etch_cache_insert(((etch_object*)vtab)->get_hashkey(vtab), vtab, FALSE);
is_just_cached = TRUE;
}
((etch_object*)hashtable)->vtab = (vtabmask*)vtab;
/* create the underlying real hashtable */
result = vtab->create(initialsize_items, NULL, &jenkins_hashtable);
hashtable->realtable = jenkins_hashtable;
((etch_object*)hashtable)->destroy = destroy_hashtable_default;
((etch_object*)hashtable)->clone = clone_hashtable_default;
if (result == -1)
{
if (is_just_cached) {
etch_cache_del(CLASS_ID);
}
etch_object_destroy(hashtable);
}
return hashtable;
}
/*
* new_systemhashtable()
* for such a hashtable we will not use the vtab interface
* but rather will call the implementation methods directly.
*/
etch_hashtable* new_systemhashtable(const int initialsize_items)
{
int result = 0;
htab* jenkins_hashtable = NULL;
etch_hashtable* hashtable = 0;
hashtable = etch_malloc(sizeof(etch_hashtable), 0);
memset(hashtable, 0, sizeof(etch_hashtable));
result = jenkins_create(initialsize_items, NULL, &jenkins_hashtable);
hashtable->realtable = jenkins_hashtable;
if (result == 0)
new_iterable(&hashtable->iterable, NULL, hashtable_iterable_first,
hashtable_iterable_next, hashtable_iterable_has_next);
else /* some problem creating hashtable */
{
delete_systemhashtable(hashtable);
hashtable = NULL;
}
return hashtable;
}
/*
* delete_systemhashtable()
* delete an untracked hashtable
*/
void delete_systemhashtable(etch_hashtable* hashtable)
{
if (!hashtable) return;
jenkins_destroy(hashtable->realtable, NULL, NULL);
free(hashtable);
}
/* - - - - - - - - - - - - - - - - - - - - -
* hashtable synchronization
* - - - - - - - - - - - - - - - - - - - - -
*/
/*
* hashtable_defsynchook()
* hashtable synchronization hook usable for most purposes.
* to enable synchronization, set map.synchook to this function,
* and set map.synclock to an instantiated mutex.
*/
//int hashtable_defsynchook(void* action, void* mutex)
//{
// /* all the casting is to quash pointer cast warnings */
// return etch_mutex_default_hookproc((int)(((size_t) action) & 0xf), mutex);
//}
/*
* hashtable_getlock()
* explicitly set this map's synchronization lock, waiting if unavailable.
* this should be used only for locking a map prior to iterating the map.
* for synchronization of map operations, the presence of map.synchook
* and map.synclock is sufficient.
*/
int hashtable_getlock (etch_hashtable* map)
{
etch_status_t status = ETCH_SUCCESS;
ETCH_ASSERT(map && ((etch_object*)map)->synclock);
if(((etch_object*)map)->synclock) {
status = etch_mutex_lock(((etch_object*)map)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
return 0;
}
/*
* hashtable_trylock()
* explicitly set this map's synchronization lock, failing if unavailable.
* this should be used only for locking a map prior to iterating the map.
* for synchronization of map operations, the presence of map.synchook
* and map.synclock is sufficient.
*/
int hashtable_trylock (etch_hashtable* map)
{
etch_status_t status = ETCH_SUCCESS;
ETCH_ASSERT(map && ((etch_object*)map)->synclock);
if(((etch_object*)map)->synclock) {
status = etch_mutex_trylock(((etch_object*)map)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
return 0;
}
/*
* hashtable_rellock()
* release explicitly set this map's synchronization lock.
* this should be used only for unlocking a map after iterating the map.
* for synchronization of map operations, the presence of map.synchook
* and map.synclock is sufficient.
*/
int hashtable_rellock (etch_hashtable* map)
{
etch_status_t status = ETCH_SUCCESS;
ETCH_ASSERT(map && ((etch_object*)map)->synclock);
if(((etch_object*)map)->synclock) {
status = etch_mutex_unlock(((etch_object*)map)->synclock);
if(status != ETCH_SUCCESS) {
return -1;
}
}
return 0;
}
/* - - - - - - - - - - - - - - - - - - - - -
* i_iterable implementations
* - - - - - - - - - - - - - - - - - - - - -
*/
/*
* hashtable_iterable_first()
* i_iterable first() implementation
*/
int hashtable_iterable_first(etch_iterator* iter)
{
etch_hashtable* hash = NULL;
etch_hashitem hashbucket, *outentry = &hashbucket;
if (!iter || !iter->collection) return -1;
iter->current_key = iter->current_value = NULL;
hash = iter->collection;
if (-1 == ((struct i_hashtable*)((etch_object*)hash)->vtab)->first(hash->realtable, 0, &outentry))
return -1;
iter->current_key = outentry->key;
iter->current_value = outentry->value;
iter->ordinal++;
return 0;
}
/*
* hashtable_iterable_next()
* i_iterable next() implementation
* functions as first() if there is no current position.
*/
int hashtable_iterable_next(etch_iterator* iter)
{
etch_hashtable* hash = NULL;
etch_hashitem hashbucket, *outentry = &hashbucket;
if (!iter || !iter->collection || !iter->ordinal) return -1;
iter->current_key = iter->current_value = NULL;
hash = iter->collection;
if (-1 == ((struct i_hashtable*)((etch_object*)hash)->vtab)->next(hash->realtable, 0, &outentry))
return -1;
iter->current_key = outentry->key;
iter->current_value = outentry->value;
iter->ordinal++;
return 0;
}
/*
* hashtable_iterable_has_next()
* i_iterable has_next() implementation.
*/
int hashtable_iterable_has_next(etch_iterator* iter)
{
return iter && iter->collection && iter->current_key && iter->ordinal;
}
/* - - - - - - - - - - - - - - - - - - - - -
* clear() callbacks
* - - - - - - - - - - - - - - - - - - - - -
*/
/*
* string_to_object_clear_handler()
* callback set to handle freeing of key/value memory during destroy()
* and subsequent clear() of a string-to-etch_object hashtable.
* handlers return FALSE to indicate memory management NOT handled,
*/
int string_to_object_clear_handler (void* data1, void* data2)
{
wchar_t* key = (wchar_t*)data1;
etch_object* value = (etch_object*)data2;
etch_free(key); /* free string key */
etch_object_destroy(value); /* free etch object value */
return TRUE;
}
/*
* object_to_object_clear_handler()
* callback set to handle freeing of key/value memory during destroy()
* and subsequent clear() of a etch_object-to-etch_object hashtable.
* handlers return FALSE to indicate memory management NOT handled.
*/
int object_to_object_clear_handler (void* data1, void* data2)
{
etch_object* key = (etch_object*)data1;
etch_object* value = (etch_object*)data2;
etch_object_destroy(key); /* free etch object key */
etch_object_destroy(value); /* free etch object value */
return TRUE;
}
/*
* etch_noop_clear_handler()
* callback set to handle freeing of key/value memory during destroy()
* and subsequent clear() of a etch_object-to-etch_object hashtable.
*/
int etch_noop_clear_handler (void* key, void* value)
{
return TRUE;
}
/* - - - - - - - - - - - - - - - - - - - - -
* etch_map, etch_set
* - - - - - - - - - - - - - - - - - - - - -
*/
/*
* new_etch_map()
* an etch_map is a hashtable having object keys and object values.
* an object key should of course have its hashkey pre-computed appropriately.
* if instantiator wants to use non-disposable objects as keys and/or values,
* is_readonly_keys and/or is_readonly_values should be set, and the freehook
* callback overridden. furthermore if caller chooses to use the same object
* as both key and value, similar steps should be taken to ensure that code
* does not try to destroy both key and value.
*/
etch_hashtable* new_etch_map(const unsigned int initialsize)
{
etch_hashtable* newmap = new_hashtable(initialsize);
newmap->content_type = ETCHHASHTABLE_CONTENT_OBJECT_OBJECT;
newmap->freehook = object_to_object_clear_handler;
newmap->is_readonly_keys = newmap->is_readonly_values = FALSE;
return newmap;
}
/*
* new_etch_set()
* an etch_set is a hashtable having object keys and null values.
*/
etch_hashtable* new_etch_set(const unsigned int initialsize)
{
etch_hashtable* newset = new_hashtable(initialsize);
((etch_object*)newset)->class_id = CLASSID_ETCH_SET; /* serializer will expect this */
newset->content_type = ETCHHASHTABLE_CONTENT_OBJECT_NONE;
newset->freehook = object_to_object_clear_handler;
newset->is_readonly_keys = FALSE;
newset->is_readonly_values = TRUE;
return newset;
}
/**
* get_map_keys()
* return a collection of the specified map's keys.
* caller must invoke the destructor on the returned list. the returned list
* is marked as readonly content, in order that arraylist_destroy() will not
* attempt to free memory for the list content, which remains owned by the map.
*/
etch_arraylist* get_map_keys(etch_hashtable* map)
{
etch_iterator iterator;
etch_arraylist* list = NULL;
const int typecount = ((struct i_hashtable*)((etch_object*)map)->vtab)->count(map->realtable,0,0);
list = new_etch_arraylist(typecount, 0);
list->content_type = ETCHARRAYLIST_CONTENT_OBJECT;
list->is_readonly = TRUE; /* content is refs to objects owned by map */
set_iterator(&iterator, map, &map->iterable);
while(((struct i_iterable*)iterator.object.vtab)->has_next(&iterator))
{
etch_arraylist_add(list, iterator.current_key);
((struct i_iterable*)iterator.object.vtab)->next(&iterator);
}
return list;
}
/**
* get_map_values()
* return a collection of the specified map's values.
* caller must invoke the destructor on the returned list. the returned list
* is marked as readonly content, in order that arraylist_destroy() will not
* attempt to free memory for the list content, which remains owned by the map.
*/
etch_arraylist* get_map_values(etch_hashtable* map)
{
etch_iterator iterator;
etch_arraylist* list = NULL;
const int typecount = ((struct i_hashtable*)((etch_object*)map)->vtab)->count(map->realtable,0,0);
list = new_etch_arraylist(typecount, 0);
list->content_type = map->content_type;
list->is_readonly = TRUE; /* content is refs to objects owned by map */
set_iterator(&iterator, map, &map->iterable);
while(((struct i_iterable*)iterator.object.vtab)->has_next(&iterator))
{
etch_arraylist_add(list, iterator.current_value);
((struct i_iterable*)iterator.object.vtab)->next(&iterator);
}
return list;
}