/*************************************************************************** hash.c hash table routines, adapted from glib 1.2.8 (c) 2000-2007 Benoit Minisini This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ***************************************************************************/ #define __GB_HASH_C #include #include "gb_common.h" #include "gb_common_case.h" #include "gb_alloc.h" #include "gb_hash.h" #define NODE_value(_node) ((void *)((char *)_node) + sizeof(HASH_NODE)) //#define NODE_length(_table, _node) (*((ushort *)(((char *)_node) + sizeof(HASH_NODE) + (_table)->s_value))) #define NODE_key(_table, _node) ((HASH_KEY *)(((char *)_node) + sizeof(HASH_NODE) + (_table)->s_value)) static void hash_table_resize(HASH_TABLE *hash_table); static HASH_NODE **hash_table_lookup_node(HASH_TABLE *hash_table, const char *key, int len); static HASH_NODE *hash_node_new(HASH_TABLE *hash_table, const char *key, int len); static void hash_node_destroy(HASH_NODE *hash_node); static void hash_nodes_destroy(HASH_NODE *hash_node); static const int primes[] = { 11, 19, 37, 73, 109, 163, 251, 367, 557, 823, 1237, 1861, 2777, 4177, 6247, 9371, 14057, 21089, 31627, 47431, 71143, 106721, 160073, 240101, 360163, 540217, 810343, 1215497, 1823231, 2734867, 4102283, 6153409, 9230113, 13845163 }; static const int nprimes = sizeof (primes) / sizeof (primes[0]); static int spaced_primes_closest(int num) { int i; for (i = 0; i < nprimes; i++) if (primes[i] > num) return primes[i]; return primes[nprimes - 1]; } static uint key_hash_binary(const char *key, int len) { uint hash = 0; //for (i = 0; i < len; i++) // hash = (hash << 4) + (hash ^ key[i]); while (len) { len--; hash = (hash << 4) + (hash ^ key[len]); } return hash; } static uint key_hash_text(const char *key, int len) { uint hash = 0; while (len) { len--; hash = (hash << 4) + (hash ^ (key[len] & ~0x20)); } #if 0 while (len) { len -= 4; p = &key[len]; hash = (hash << 4) + (hash ^ (p[0] & ~0x20)); hash = (hash << 4) + (hash ^ (p[1] & ~0x20)); hash = (hash << 4) + (hash ^ (p[2] & ~0x20)); hash = (hash << 4) + (hash ^ (p[3] & ~0x20)); } #endif return hash; } static HASH_FUNC get_hash_func(HASH_TABLE *hash) { if (hash->mode == HF_IGNORE_CASE) return key_hash_text; else return key_hash_binary; } PUBLIC void HASH_TABLE_create(HASH_TABLE **hash, size_t s_value, HASH_FLAG mode) { HASH_TABLE *hash_table; /*int i;*/ ALLOC_ZERO(&hash_table, sizeof(HASH_TABLE), "HASH_TABLE_create"); hash_table->size = HASH_TABLE_MIN_SIZE; hash_table->s_value = s_value; ALLOC_ZERO(&hash_table->nodes, sizeof(HASH_NODE) * hash_table->size, "HASH_TABLE_create"); if (mode == HF_IGNORE_CASE) hash_table->mode = mode; else hash_table->mode = HF_NORMAL; *hash = hash_table; } PUBLIC void HASH_TABLE_delete(HASH_TABLE **hash) { int i; HASH_TABLE *hash_table = *hash; if (hash_table == NULL) return; for (i = 0; i < hash_table->size; i++) hash_nodes_destroy(hash_table->nodes[i]); FREE(&hash_table->nodes, "HASH_TABLE_delete"); FREE(hash, "HASH_TABLE_delete"); } PUBLIC int HASH_TABLE_size(HASH_TABLE *hash_table) { return hash_table->nnodes; } static bool compare_key_ignore_case(const char *key_comp, int len_comp, const char *key, int len) { if (len != len_comp) return FALSE; while (len) { len--; if (toupper(key_comp[len]) != toupper(key[len])) return FALSE; } return TRUE; } static bool compare_key(const char *key_comp, int len_comp, const char *key, int len) { if (len != len_comp) return FALSE; while (len) { len--; if (key_comp[len] != key[len]) return FALSE; } return TRUE; } static HASH_NODE **hash_table_lookup_node(HASH_TABLE *hash_table, const char *key, int len) { HASH_NODE **node; HASH_KEY *node_key; if (hash_table->mode == HF_IGNORE_CASE) { node = &hash_table->nodes[key_hash_text(key, len) % hash_table->size]; while (*node) { node_key = NODE_key(hash_table, *node); if (compare_key_ignore_case(node_key->key, node_key->len, key, len)) break; node = &(*node)->next; } } else { node = &hash_table->nodes[key_hash_binary(key, len) % hash_table->size]; while (*node) { node_key = NODE_key(hash_table, *node); if (compare_key(node_key->key, node_key->len, key, len)) break; node = &(*node)->next; } } return node; } PUBLIC void *HASH_TABLE_lookup(HASH_TABLE *hash_table, const char *key, int len) { HASH_NODE *node; node = *hash_table_lookup_node(hash_table, key, len); hash_table->last = node; return node ? NODE_value(node) : NULL; } PUBLIC void *HASH_TABLE_insert(HASH_TABLE *hash_table, const char *key, int len) { HASH_NODE **node; node = hash_table_lookup_node(hash_table, key, len); if (*node) return NODE_value(*node); /*hash_table_resize(hash_table);*/ *node = hash_node_new(hash_table, key, len); hash_table->nnodes++; /*if (!hash_table->frozen)*/ return NODE_value(*node); } PUBLIC void HASH_TABLE_remove(HASH_TABLE *hash_table, const char *key, int len) { HASH_NODE **node, *dest; node = hash_table_lookup_node(hash_table, key, len); if (*node) { dest = *node; (*node) = dest->next; #ifdef KEEP_ORDER if (dest->sprev) dest->sprev->snext = dest->snext; else hash_table->sfirst = dest->snext; if (dest->snext) dest->snext->sprev = dest->sprev; else hash_table->slast = dest->sprev; #endif hash_node_destroy(dest); hash_table->nnodes--; hash_table->last = NULL; /*if (!hash_table->frozen)*/ hash_table_resize(hash_table); } } PUBLIC void *HASH_TABLE_next(HASH_TABLE *hash_table, HASH_ENUM *iter) { #ifdef KEEP_ORDER if (iter->node == NULL) iter->node = hash_table->sfirst; else iter->node = iter->next; hash_table->last = iter->node; if (iter->node) { iter->next = iter->node->snext; return NODE_value(iter->node); } else return NULL; #else HASH_NODE *enum_node = iter->node; int enum_i = iter->index; if (enum_node) enum_node = enum_node->next; while (enum_node == NULL) { enum_i++; if (enum_i >= hash_table->size) break; enum_node = hash_table->nodes[enum_i]; }; iter->node = enum_node; iter->index = enum_i; hash_table->last = enum_node; if (enum_node) return NODE_value(enum_node); else return NULL; #endif } static void hash_table_resize(HASH_TABLE *hash_table) { HASH_NODE **new_nodes; HASH_NODE *node; HASH_NODE *next; HASH_KEY *node_key; double nodes_per_list; int hash_val; int new_size; int i; HASH_FUNC hash_func = get_hash_func(hash_table); nodes_per_list = hash_table->nnodes / hash_table->size; if ((nodes_per_list > 0.3 || hash_table->size <= HASH_TABLE_MIN_SIZE) && (nodes_per_list < 3.0 || hash_table->size >= HASH_TABLE_MAX_SIZE)) return; new_size = MinMax(spaced_primes_closest(hash_table->nnodes), HASH_TABLE_MIN_SIZE, HASH_TABLE_MAX_SIZE); ALLOC_ZERO(&new_nodes, new_size * sizeof(HASH_NODE *), "hash_table_resize"); for (i = 0; i < hash_table->size; i++) for (node = hash_table->nodes[i]; node; node = next) { next = node->next; node_key = NODE_key(hash_table, node); hash_val = (*hash_func)(node_key->key, node_key->len) % new_size; node->next = new_nodes[hash_val]; new_nodes[hash_val] = node; } FREE(&hash_table->nodes, "hash_table_resize"); hash_table->nodes = new_nodes; hash_table->size = new_size; } static HASH_NODE *hash_node_new(HASH_TABLE *hash_table, const char *key, int len) { HASH_NODE *hash_node; int size; HASH_KEY *node_key; size = sizeof(HASH_NODE) + hash_table->s_value + len + sizeof(ushort); ALLOC_ZERO(&hash_node, size, "hash_node_new"); node_key = NODE_key(hash_table, hash_node); memcpy(node_key->key, key, len); node_key->len = len; #ifdef KEEP_ORDER if (!hash_table->sfirst) { hash_table->sfirst = hash_node; hash_table->slast = hash_node; } else { hash_node->sprev = hash_table->slast; hash_node->sprev->snext = hash_node; hash_table->slast = hash_node; } #endif return hash_node; } static void hash_node_destroy(HASH_NODE *hash_node) { FREE(&hash_node, "hash_node_destroy"); } static void hash_nodes_destroy(HASH_NODE *hash_node) { HASH_NODE *node = hash_node; HASH_NODE *next; for(;;) { if (node == NULL) return; next = node->next; FREE(&node, "hash_nodes_destroy"); node = next; } } PUBLIC void HASH_TABLE_get_key(HASH_TABLE *hash_table, HASH_NODE *node, char **key, int *len) { HASH_KEY *node_key; if (node) { node_key = NODE_key(hash_table, node); *key = node_key->key; *len = node_key->len; } else *len = 0; } PUBLIC bool HASH_TABLE_get_last_key(HASH_TABLE *hash_table, char **key, int *len) { if (hash_table->last == NULL) return TRUE; HASH_TABLE_get_key(hash_table, hash_table->last, key, len); return FALSE; }