/*---------------------------------------------------------------------------*
* phashtable.c *
* *
* Copyright 2007, 2008 Nuance Communciations, Inc. *
* *
* Licensed 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. *
* *
*---------------------------------------------------------------------------*/
#include <string.h>
#include "phashtable.h"
#include "plog.h"
#include "pmemory.h"
#include "pstdio.h"
//extern int strcmp(const char * s1, const char * s2);
#define ALLOC_SIZE 16
struct PHashTableEntry_t
{
const void *key;
const void *value;
PHashTable *table;
unsigned int idx;
PHashTableEntry *next;
PHashTableEntry *prev;
unsigned int hashCode;
};
typedef struct PHashTableEntryBlock_t
{
PHashTableEntry entries[ALLOC_SIZE];
struct PHashTableEntryBlock_t *next;
}
PHashTableEntryBlock;
struct PHashTable_t
{
PHashTableArgs args;
const LCHAR *memoryTag;
unsigned int size;
float maxLoadFactor;
PHashTableEntry **entries;
unsigned int threshold;
PHashTableEntry *freeList;
PHashTableEntryBlock *entryBlock;
};
#include "pcrc.h"
static unsigned int hashString(const void *key)
{
return ~pcrcComputeString(key);
}
ESR_ReturnCode PHashTableCreate(PHashTableArgs *args,
const LCHAR *memTag,
PHashTable **table)
{
PHashTable *tmp;
unsigned int i;
if (table == NULL ||
(args != NULL && args->maxLoadFactor <= 0.0))
return ESR_INVALID_ARGUMENT;
if ((tmp = NEW(PHashTable, memTag)) == NULL)
return ESR_OUT_OF_MEMORY;
if (args == NULL)
{
tmp->args.capacity = PHASH_TABLE_DEFAULT_CAPACITY;
tmp->args.maxLoadFactor = PHASH_TABLE_DEFAULT_MAX_LOAD_FACTOR;
tmp->args.hashFunction = PHASH_TABLE_DEFAULT_HASH_FUNCTION;
tmp->args.compFunction = PHASH_TABLE_DEFAULT_COMP_FUNCTION;
}
else
{
memcpy(&tmp->args, args, sizeof(PHashTableArgs));
}
if (tmp->args.hashFunction == PHASH_TABLE_DEFAULT_HASH_FUNCTION)
tmp->args.hashFunction = hashString;
if (tmp->args.compFunction == PHASH_TABLE_DEFAULT_COMP_FUNCTION)
tmp->args.compFunction = LSTRCMP;
tmp->entries = NEW_ARRAY(PHashTableEntry *, tmp->args.capacity, memTag);
if (tmp->entries == NULL)
{
FREE(tmp);
return ESR_OUT_OF_MEMORY;
}
for (i = tmp->args.capacity; i > 0;)
{
tmp->entries[--i] = NULL;
}
tmp->memoryTag = memTag;
tmp->size = 0;
tmp->threshold = (unsigned int)(tmp->args.capacity * tmp->args.maxLoadFactor);
tmp->freeList = NULL;
tmp->entryBlock = NULL;
*table = tmp;
return ESR_SUCCESS;
}
ESR_ReturnCode PHashTableDestroy(PHashTable *table)
{
PHashTableEntryBlock *tmp, *block;
if (table == NULL)
return ESR_INVALID_ARGUMENT;
block = table->entryBlock;
while (block != NULL)
{
tmp = block->next;
FREE(block);
block = tmp;
}
FREE(table->entries);
FREE(table);
return ESR_SUCCESS;
}
ESR_ReturnCode PHashTableGetSize(PHashTable *table,
size_t *size)
{
if (table == NULL || size == NULL)
return ESR_INVALID_ARGUMENT;
*size = table->size;
return ESR_SUCCESS;
}
static PHashTableEntry *getEntry(PHashTable *table,
const void *key,
unsigned int hashCode,
unsigned int idx)
{
PHashTableEntry *entry = table->entries[idx];
if (key == NULL)
{
while (entry != NULL)
{
if (entry->key == NULL)
return entry;
entry = entry->next;
}
}
else
{
while (entry != NULL)
{
if (entry->hashCode == hashCode && table->args.compFunction(key, entry->key) == 0)
return entry;
entry = entry->next;
}
}
return NULL;
}
static void removeEntry(PHashTableEntry *entry)
{
if (entry->prev == NULL)
entry->table->entries[entry->idx] = entry->next;
else
entry->prev->next = entry->next;
if (entry->next != NULL)
entry->next->prev = entry->prev;
entry->table->size--;
entry->next = entry->table->freeList;
entry->table->freeList = entry;
/* clean up entry for re-use. */
entry->key = entry->value = NULL;
}
ESR_ReturnCode PHashTableGetValue(PHashTable *table, const void *key, void **value)
{
PHashTableEntry *entry;
unsigned int hashCode;
unsigned int idx;
if (table == NULL || value == NULL)
return ESR_INVALID_ARGUMENT;
hashCode = table->args.hashFunction(key);
idx = hashCode % table->args.capacity;
if ((entry = getEntry(table, key, hashCode, idx)) != NULL)
{
*value = (void *) entry->value;
return ESR_SUCCESS;
}
else
{
*value = NULL;
return ESR_NO_MATCH_ERROR;
}
}
ESR_ReturnCode PHashTableContainsKey(PHashTable *table, const void *key, ESR_BOOL* exists)
{
ESR_ReturnCode rc;
PHashTableEntry* entry;
if (table == NULL || exists == NULL)
{
rc = ESR_INVALID_ARGUMENT;
PLogError(ESR_rc2str(rc));
goto CLEANUP;
}
rc = PHashTableGetEntry(table, key, &entry);
if (rc == ESR_SUCCESS)
*exists = ESR_TRUE;
else if (rc == ESR_NO_MATCH_ERROR)
*exists = ESR_FALSE;
else
goto CLEANUP;
return ESR_SUCCESS;
CLEANUP:
return rc;
}
ESR_ReturnCode PHashTableGetEntry(PHashTable *table, const void *key, PHashTableEntry **entry)
{
unsigned int hashCode;
unsigned int idx;
PHashTableEntry* result;
if (table == NULL || entry == NULL)
return ESR_INVALID_ARGUMENT;
hashCode = table->args.hashFunction(key);
idx = hashCode % table->args.capacity;
result = getEntry(table, key, hashCode, idx);
if (result == NULL)
return ESR_NO_MATCH_ERROR;
*entry = result;
return ESR_SUCCESS;
}
static ESR_ReturnCode PHashTableRehash(PHashTable *table)
{
unsigned int i, idx;
unsigned int oldCapacity = table->args.capacity;
unsigned int newCapacity = ((oldCapacity << 1) | 0x01);
PHashTableEntry *entry, *tmp, *next;
PHashTableEntry **newEntries =
(PHashTableEntry **)
REALLOC(table->entries,
sizeof(PHashTableEntry *) * newCapacity);
if (newEntries == NULL)
return ESR_OUT_OF_MEMORY;
table->entries = newEntries;
table->args.capacity = newCapacity;
table->threshold = (unsigned int)(newCapacity * table->args.maxLoadFactor);
for (i = oldCapacity; i < newCapacity; ++i)
{
table->entries[i] = NULL;
}
for (i = 0; i < oldCapacity; i++)
{
for (entry = table->entries[i]; entry != NULL;)
{
idx = entry->hashCode % newCapacity;
if (idx != i)
{
/* Need to change location. */
entry->idx = idx;
next = entry->next;
if (entry->prev != NULL)
entry->prev->next = next;
else
table->entries[i] = next;
if (next != NULL)
next->prev = entry->prev;
tmp = table->entries[idx];
entry->next = tmp;
entry->prev = NULL;
if (tmp != NULL)
tmp->prev = entry;
table->entries[idx] = entry;
entry = next;
}
else
{
/* Already in the right slot. */
entry = entry->next;
}
}
}
return ESR_SUCCESS;
}
ESR_ReturnCode PHashTablePutValue(PHashTable *table,
const void *key,
const void *value,
void **oldValue)
{
ESR_ReturnCode rc = ESR_SUCCESS;
unsigned int hashCode, idx;
PHashTableEntry *entry;
if (table == NULL) return ESR_INVALID_ARGUMENT;
hashCode = table->args.hashFunction(key);
idx = hashCode % table->args.capacity;
entry = getEntry(table, key, hashCode, idx);
if (entry != NULL)
{
if (oldValue != NULL) *oldValue = (void *) entry->value;
entry->value = value;
return ESR_SUCCESS;
}
/* If we get here, we need to add a new entry. But first, verify if we need
to rehash. */
if (table->size >= table->threshold)
{
if ((rc = PHashTableRehash(table)) != ESR_SUCCESS)
return rc;
idx = hashCode % table->args.capacity;
}
if (table->freeList == NULL)
{
/* Allocate a new block and put all entries on the free list. */
PHashTableEntryBlock *block;
int i;
block = NEW(PHashTableEntryBlock, table->memoryTag);
if (block == NULL)
return ESR_OUT_OF_MEMORY;
block->next = table->entryBlock;
table->entryBlock = block;
for (i = 0; i < ALLOC_SIZE - 1; ++i)
{
block->entries[i].next = &block->entries[i+1];
}
block->entries[ALLOC_SIZE-1].next = NULL;
/* do not see any bug in following code. But on the VxWorks with optimization option -O3
it produces wrong result: block->entries[0].next is correct but block->entries[1].next = NULL
it causes lot of memory wastes.
for (i = 0, entry = block->entries; i < ALLOC_SIZE - 1; ++i, ++entry)
{
entry->next = entry+1;
}
entry->next = table->freeList;
*/
table->freeList = block->entries;
}
/* Get an entry from the freeList. */
entry = table->freeList;
table->freeList = entry->next;
/* Initialize entry data structure. */
entry->table = table;
entry->idx = idx;
entry->key = key;
entry->value = value;
entry->hashCode = hashCode;
entry->next = table->entries[idx];
entry->prev = NULL;
if (entry->next != NULL)
entry->next->prev = entry;
table->entries[idx] = entry;
table->size++;
if (oldValue != NULL) *oldValue = NULL;
return ESR_SUCCESS;
}
ESR_ReturnCode PHashTableRemoveValue(PHashTable *table,
const void *key,
void **oldValue)
{
unsigned int hashCode, idx;
PHashTableEntry *entry;
ESR_ReturnCode rc;
if (table == NULL)
{
rc = ESR_INVALID_ARGUMENT;
PLogError(ESR_rc2str(rc));
goto CLEANUP;
}
hashCode = table->args.hashFunction(key);
idx = hashCode % table->args.capacity;
entry = getEntry(table, key, hashCode, idx);
if (entry != NULL)
{
if (oldValue != NULL)
*oldValue = (void*) entry->value;
removeEntry(entry);
}
else
{
if (oldValue != NULL)
*oldValue = NULL;
}
return ESR_SUCCESS;
CLEANUP:
return rc;
}
ESR_ReturnCode PHashTableEntryGetKeyValue(PHashTableEntry *entry,
void **key,
void **value)
{
if (entry == NULL) return ESR_INVALID_ARGUMENT;
if (key != NULL) *key = (void *) entry->key;
if (value != NULL) *value = (void *) entry->value;
return ESR_SUCCESS;
}
/**
* Sets the value associated with this entry.
* @param entry The hashtable entry.
* @param value The value to associate with the entry.
* @param oldvalue If this pointer is non-NULL, it will be set to the previous value associated with this entry.
**/
ESR_ReturnCode PHashTableEntrySetValue(PHashTableEntry *entry,
const void *value,
void **oldValue)
{
if (entry == NULL) return ESR_INVALID_ARGUMENT;
if (oldValue != NULL) *oldValue = (void *) entry->value;
entry->value = value;
return ESR_SUCCESS;
}
/**
* Removes the entry from its hash table.
*
* @param entry The hashtable entry.
**/
ESR_ReturnCode PHashTableEntryRemove(PHashTableEntry *entry)
{
if (entry == NULL)
return ESR_INVALID_ARGUMENT;
removeEntry(entry);
return ESR_SUCCESS;
}
static PHashTableEntry* iteratorAdvance(PHashTable *table, PHashTableEntry *entry)
{
unsigned int idx;
if (entry != NULL)
{
idx = entry->idx;
entry = entry->next;
if (entry == NULL)
{
while (++idx < table->args.capacity)
{
if (table->entries[idx] != NULL)
{
entry = table->entries[idx];
break;
}
}
}
}
else
{
for (idx = 0; idx < table->args.capacity; ++idx)
{
if (table->entries[idx] != NULL)
{
entry = table->entries[idx];
break;
}
}
}
return entry;
}
ESR_ReturnCode PHashTableEntryGetFirst(PHashTable *table, PHashTableEntry **entry)
{
if (table == NULL || entry == NULL)
return ESR_INVALID_ARGUMENT;
*entry = iteratorAdvance(table, NULL);
return ESR_SUCCESS;
}
/**
* Iterates on the next key and value. Returns a NULL key when at the end of the hash table.
*
* @param iter The iterator on which the iteration is performed.
* @param key Returns the key associated with the entry, cannot be NULL.
* @param value If non-NULL, returns the value associated with the entry.
**/
ESR_ReturnCode PHashTableEntryAdvance(PHashTableEntry **entry)
{
if (entry == NULL || *entry == NULL)
return ESR_INVALID_ARGUMENT;
*entry = iteratorAdvance((*entry)->table, *entry);
return ESR_SUCCESS;
}