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