blob: 35293e6ba531f54fd14b696b8aa8e39a5660b8a1 [file] [log] [blame]
/*
* 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.
*/
#include <stdio.h>
#include <string.h>
#include <qpid/dispatch/alloc.h>
#include <qpid/dispatch/hash.h>
#include <qpid/dispatch/ctools.h>
typedef struct qd_hash_item_t {
DEQ_LINKS(struct qd_hash_item_t);
unsigned char *key;
union {
void *val;
const void *val_const;
} v;
} qd_hash_item_t;
ALLOC_DECLARE(qd_hash_item_t);
ALLOC_DEFINE(qd_hash_item_t);
DEQ_DECLARE(qd_hash_item_t, items_t);
typedef struct bucket_t {
items_t items;
} bucket_t;
struct qd_hash_t {
bucket_t *buckets;
unsigned int bucket_count;
unsigned int bucket_mask;
int batch_size;
size_t size;
int is_const;
};
struct qd_hash_handle_t {
bucket_t *bucket;
qd_hash_item_t *item;
};
ALLOC_DECLARE(qd_hash_handle_t);
ALLOC_DEFINE(qd_hash_handle_t);
static bucket_t *qd_hash_get_bucket_iter(qd_hash_t *h, qd_iterator_t *key)
{
uint32_t idx = qd_iterator_hash_view(key) & h->bucket_mask;
return &h->buckets[idx];
}
static bucket_t *qd_hash_get_bucket_str(qd_hash_t *h, const unsigned char *key)
{
uint32_t hash = HASH_INIT;
while (*key) {
hash = HASH_COMPUTE(hash, *key++);
}
return &h->buckets[(hash & h->bucket_mask)];
}
qd_hash_t *qd_hash(int bucket_exponent, int batch_size, int value_is_const)
{
int i;
qd_hash_t *h = NEW(qd_hash_t);
if (!h)
return 0;
h->bucket_count = 1 << bucket_exponent;
h->bucket_mask = h->bucket_count - 1;
h->batch_size = batch_size;
h->size = 0;
h->is_const = value_is_const;
h->buckets = NEW_ARRAY(bucket_t, h->bucket_count);
for (i = 0; i < h->bucket_count; i++) {
DEQ_INIT(h->buckets[i].items);
}
return h;
}
//remove the given item from the given bucket of the given hash
//return the key if non-null key pointer given, otherwise, free the memory
static void qd_hash_internal_remove_item(qd_hash_t *h, bucket_t *bucket, qd_hash_item_t *item, unsigned char **key) {
if (key)
*key = item->key;
else
free(item->key);
DEQ_REMOVE(bucket->items, item);
free_qd_hash_item_t(item);
h->size--;
}
void qd_hash_free(qd_hash_t *h)
{
if (!h) return;
qd_hash_item_t *item;
int idx;
for (idx = 0; idx < h->bucket_count; idx++) {
item = DEQ_HEAD(h->buckets[idx].items);
while (item) {
qd_hash_internal_remove_item(h, &h->buckets[idx], item, 0);
item = DEQ_HEAD(h->buckets[idx].items);
}
}
free(h->buckets);
free(h);
}
size_t qd_hash_size(qd_hash_t *h)
{
return h ? h->size : 0;
}
// ownership of key is transfered to the qd_hash_item_t
static qd_hash_item_t *qd_hash_internal_insert(qd_hash_t *h, bucket_t *bucket, unsigned char *key, int *exists, qd_hash_handle_t **handle)
{
qd_hash_item_t *item = DEQ_HEAD(bucket->items);
while (item) {
if (strcmp((const char *) key, (const char *) item->key) == 0)
break;
item = DEQ_NEXT(item);
}
if (item) {
*exists = 1;
if (handle)
*handle = 0;
return item;
}
item = new_qd_hash_item_t();
if (!item)
return 0;
DEQ_ITEM_INIT(item);
item->key = key;
DEQ_INSERT_TAIL(bucket->items, item);
h->size++;
*exists = 0;
//
// If a pointer to a handle-pointer was supplied, create a handle for this item.
//
if (handle) {
*handle = new_qd_hash_handle_t();
(*handle)->bucket = bucket;
(*handle)->item = item;
}
return item;
}
qd_error_t qd_hash_insert(qd_hash_t *h, qd_iterator_t *key, void *val, qd_hash_handle_t **handle)
{
int exists = 0;
bucket_t *bucket = qd_hash_get_bucket_iter(h, key);
unsigned char *k = qd_iterator_copy(key);
if (!k)
return QD_ERROR_ALLOC;
qd_hash_item_t *item = qd_hash_internal_insert(h, bucket, k, &exists, handle);
if (!item) {
free(k);
return QD_ERROR_ALLOC;
}
if (exists) {
free(k);
return QD_ERROR_ALREADY_EXISTS;
}
item->v.val = val;
return QD_ERROR_NONE;
}
qd_error_t qd_hash_insert_const(qd_hash_t *h, qd_iterator_t *key, const void *val, qd_hash_handle_t **handle)
{
assert(h->is_const);
int exists = 0;
bucket_t *bucket = qd_hash_get_bucket_iter(h, key);
unsigned char *k = qd_iterator_copy(key);
if (!k)
return QD_ERROR_ALLOC;
qd_hash_item_t *item = qd_hash_internal_insert(h, bucket, k, &exists, handle);
if (!item) {
free(k);
return QD_ERROR_ALLOC;
}
if (exists) {
free(k);
return QD_ERROR_ALREADY_EXISTS;
}
item->v.val_const = val;
return QD_ERROR_NONE;
}
qd_error_t qd_hash_insert_str(qd_hash_t *h, const unsigned char *key, void *val, qd_hash_handle_t **handle)
{
int exists = 0;
bucket_t *bucket = qd_hash_get_bucket_str(h, key);
unsigned char *k = (unsigned char *) strdup((const char *) key);
if (!k)
return QD_ERROR_ALLOC;
qd_hash_item_t *item = qd_hash_internal_insert(h, bucket, k, &exists, handle);
if (!item) {
free(k);
return QD_ERROR_ALLOC;
}
if (exists) {
free(k);
return QD_ERROR_ALREADY_EXISTS;
}
item->v.val = val;
return QD_ERROR_NONE;
}
static qd_hash_item_t *qd_hash_internal_retrieve_with_hash(qd_hash_t *h, uint32_t hash, qd_iterator_t *key)
{
uint32_t idx = hash & h->bucket_mask;
qd_hash_item_t *item = DEQ_HEAD(h->buckets[idx].items);
while (item) {
if (qd_iterator_equal(key, item->key))
break;
item = DEQ_NEXT(item);
}
return item;
}
static qd_hash_item_t *qd_hash_internal_retrieve(qd_hash_t *h, qd_iterator_t *key)
{
uint32_t hash = qd_iterator_hash_view(key);
return qd_hash_internal_retrieve_with_hash(h, hash, key);
}
void qd_hash_retrieve_prefix(qd_hash_t *h, qd_iterator_t *iter, void **val)
{
//Hash individual segments by iterating thru the octets in the iterator.
qd_iterator_hash_view_segments(iter);
uint32_t hash = 0;
qd_hash_item_t *item;
while (qd_iterator_next_segment(iter, &hash)) {
item = qd_hash_internal_retrieve_with_hash(h, hash, iter);
if (item)
break;
}
if (item)
*val = item->v.val;
else
*val = 0;
}
void qd_hash_retrieve_prefix_const(qd_hash_t *h, qd_iterator_t *iter, const void **val)
{
assert(h->is_const);
//Hash individual segments by iterating thru the octets in the iterator.
qd_iterator_hash_view_segments(iter);
uint32_t hash = 0;
qd_hash_item_t *item;
while (qd_iterator_next_segment(iter, &hash)) {
item = qd_hash_internal_retrieve_with_hash(h, hash, iter);
if (item)
break;
}
if (item)
*val = item->v.val_const;
else
*val = 0;
}
qd_error_t qd_hash_retrieve(qd_hash_t *h, qd_iterator_t *key, void **val)
{
if (!key) {
*val = 0;
return QD_ERROR_NONE;
}
qd_hash_item_t *item = qd_hash_internal_retrieve(h, key);
if (item)
*val = item->v.val;
else
*val = 0;
return QD_ERROR_NONE;
}
qd_error_t qd_hash_retrieve_const(qd_hash_t *h, qd_iterator_t *key, const void **val)
{
assert(h->is_const);
qd_hash_item_t *item = qd_hash_internal_retrieve(h, key);
if (item)
*val = item->v.val_const;
else
*val = 0;
return QD_ERROR_NONE;
}
static qd_hash_item_t *qd_hash_internal_get_item_str(qd_hash_t *h, bucket_t *bucket, const unsigned char *key)
{
qd_hash_item_t *item = DEQ_HEAD(bucket->items);
while (item) {
if (strcmp((const char *) key, (const char *) item->key) == 0) {
return item;
}
item = DEQ_NEXT(item);
}
return 0;
}
qd_error_t qd_hash_retrieve_str(qd_hash_t *h, const unsigned char *key, void **val)
{
qd_hash_item_t *item = qd_hash_internal_get_item_str(h,
qd_hash_get_bucket_str(h, key),
key);
if (item) {
*val = item->v.val;
} else {
*val = 0;
}
return QD_ERROR_NONE;
}
qd_error_t qd_hash_remove(qd_hash_t *h, qd_iterator_t *key)
{
//the retrieve function will re-apply the bucket_mask, but that is ok
//we apply it here because we need the bucket index to do the remove
uint32_t idx = qd_iterator_hash_view(key) & h->bucket_mask;
qd_hash_item_t *item = qd_hash_internal_retrieve_with_hash(h, idx, key);
if (!item)
return QD_ERROR_NOT_FOUND;
qd_hash_internal_remove_item(h, &h->buckets[idx], item, 0);
return QD_ERROR_NONE;
}
qd_error_t qd_hash_remove_str(qd_hash_t *h, const unsigned char *key)
{
bucket_t *bucket = qd_hash_get_bucket_str(h, key);
qd_hash_item_t *item = qd_hash_internal_get_item_str(h, bucket, key);
if (!item)
return QD_ERROR_NOT_FOUND;
qd_hash_internal_remove_item(h, bucket, item, 0);
return QD_ERROR_NONE;
}
void qd_hash_handle_free(qd_hash_handle_t *handle)
{
if (handle)
free_qd_hash_handle_t(handle);
}
const unsigned char *qd_hash_key_by_handle(const qd_hash_handle_t *handle)
{
if (handle)
return handle->item->key;
return 0;
}
qd_error_t qd_hash_remove_by_handle(qd_hash_t *h, qd_hash_handle_t *handle)
{
if (!handle)
return QD_ERROR_NONE;
unsigned char *key = 0;
qd_error_t error = qd_hash_remove_by_handle2(h, handle, &key);
if (key)
free(key);
return error;
}
qd_error_t qd_hash_remove_by_handle2(qd_hash_t *h, qd_hash_handle_t *handle, unsigned char **key)
{
if (!handle)
return QD_ERROR_NOT_FOUND;
qd_hash_internal_remove_item(h, handle->bucket, handle->item, key);
return QD_ERROR_NONE;
}