netsurf/utils/hashmap.c
Daniel Silverstone a653e1e86e
utils: Add a generic hashmap and tests for it
In order to be able to use a generic hashmap in things such
as the fs_backing_store we want one to exist.  Here it is,
along with some moderately comprehensive tests.

Current limits:

1. All keys and values are owned by the hashmap
2. The hashmap, while capable of different bucket counts
   only has a single fixed count for now

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
2020-02-23 15:13:47 +00:00

216 lines
5.0 KiB
C

/*
* Copyright 2020 Daniel Silverstone <dsilvers@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf 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; version 2 of the License.
*
* NetSurf 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, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include "utils/hashmap.h"
/**
* The default number of buckets in the hashmaps we create.
*/
#define DEFAULT_HASHMAP_BUCKETS (4091)
/**
* Hashmaps have chains of entries in buckets.
*/
typedef struct hashmap_entry_s {
struct hashmap_entry_s **prevptr;
struct hashmap_entry_s *next;
void *key;
void *value;
uint32_t key_hash;
} hashmap_entry_t;
/**
* The content of a hashmap
*/
struct hashmap_s {
/**
* The parameters to be used for this hashmap
*/
hashmap_parameters_t *params;
/**
* The buckets for the hash chains
*/
hashmap_entry_t **buckets;
/**
* The number of buckets in this map
*/
uint32_t bucket_count;
};
/* Exported function, documented in hashmap.h */
hashmap_t *
hashmap_create(hashmap_parameters_t *params)
{
hashmap_t *ret = malloc(sizeof(hashmap_t));
ret->params = params;
ret->bucket_count = DEFAULT_HASHMAP_BUCKETS;
ret->buckets = malloc(ret->bucket_count * sizeof(hashmap_entry_t *));
memset(ret->buckets, 0, ret->bucket_count * sizeof(hashmap_entry_t *));
if (ret->buckets == NULL) {
free(ret);
return NULL;
}
return ret;
}
/* Exported function, documented in hashmap.h */
void
hashmap_destroy(hashmap_t *hashmap)
{
uint32_t bucket;
hashmap_entry_t *entry;
for (bucket = 0; bucket < hashmap->bucket_count; bucket++) {
for (entry = hashmap->buckets[bucket];
entry != NULL;
entry = entry->next) {
hashmap->params->value_destroy(entry->value);
hashmap->params->key_destroy(entry->key);
free(entry);
}
}
free(hashmap->buckets);
free(hashmap);
}
/* Exported function, documented in hashmap.h */
void *
hashmap_lookup(hashmap_t *hashmap, void *key)
{
uint32_t hash = hashmap->params->key_hash(key);
hashmap_entry_t *entry = hashmap->buckets[hash % hashmap->bucket_count];
for(;entry != NULL; entry = entry->next) {
if (entry->key_hash == hash) {
if (hashmap->params->key_eq(key, entry->key)) {
return entry->value;
}
}
}
return NULL;
}
/* Exported function, documented in hashmap.h */
void *
hashmap_insert(hashmap_t *hashmap, void *key)
{
uint32_t hash = hashmap->params->key_hash(key);
uint32_t bucket = hash % hashmap->bucket_count;
hashmap_entry_t *entry = hashmap->buckets[bucket];
void *new_key, *new_value;
for(;entry != NULL; entry = entry->next) {
if (entry->key_hash == hash) {
if (hashmap->params->key_eq(key, entry->key)) {
/* This key is already here */
new_key = hashmap->params->key_clone(key);
if (new_key == NULL) {
/* Allocation failed */
return NULL;
}
new_value = hashmap->params->value_alloc(entry->key);
if (new_value == NULL) {
/* Allocation failed */
hashmap->params->key_destroy(new_key);
return NULL;
}
hashmap->params->value_destroy(entry->value);
hashmap->params->key_destroy(entry->key);
entry->value = new_value;
entry->key = new_key;
return entry->value;
}
}
}
/* The key was not found in the map, so allocate a new entry */
entry = malloc(sizeof(*entry));
if (entry == NULL) {
return NULL;
}
memset(entry, 0, sizeof(*entry));
entry->key = hashmap->params->key_clone(key);
if (entry->key == NULL) {
goto err;
}
entry->key_hash = hash;
entry->value = hashmap->params->value_alloc(entry->key);
if (entry->value == NULL) {
goto err;
}
entry->prevptr = &(hashmap->buckets[bucket]);
entry->next = hashmap->buckets[bucket];
if (entry->next != NULL) {
entry->next->prevptr = &entry->next;
}
hashmap->buckets[bucket] = entry;
return entry->value;
err:
if (entry->value != NULL)
hashmap->params->value_destroy(entry->value);
if (entry->key != NULL)
hashmap->params->key_destroy(entry->key);
free(entry);
return NULL;
}
/* Exported function, documented in hashmap.h */
bool
hashmap_remove(hashmap_t *hashmap, void *key)
{
uint32_t hash = hashmap->params->key_hash(key);
hashmap_entry_t *entry = hashmap->buckets[hash % hashmap->bucket_count];
for(;entry != NULL; entry = entry->next) {
if (entry->key_hash == hash) {
if (hashmap->params->key_eq(key, entry->key)) {
hashmap->params->value_destroy(entry->value);
hashmap->params->key_destroy(entry->key);
if (entry->next != NULL) {
entry->next->prevptr = entry->prevptr;
}
*entry->prevptr = entry->next;
free(entry);
return true;
}
}
}
return false;
}