Newer
Older
libkc / modules / src / kc_map.c
/**
 * @file  kc_map.c
 * @brief マップモジュール
 * @copyright  2003 - 2023  Nomura Kei
 */
#include <stdlib.h>
#include <string.h>

#include <kc_lock_guard.h>
#include <kc_memory.h>
#include <kc_map.h>

#define KC_MAP_CAPACITY_MAX (65536)

/**
 * KcMapEntry 情報
 */
typedef struct KcMapEntry_
{
	char *key;				  //!< キー
	size_t key_size;		  //!< キーのためのメモリ割り当てサイズ
	void *value;			  //!< 値
	size_t size;			  //!< 値のサイズ
	struct KcMapEntry_ *next; //!< 次のエントリへのポインタ
	struct KcMapEntry_ *prev; //!< 一つ前のエントリへのポインタ
} KcMapEntry;

/**
 * KcMap 管理情報
 */
typedef struct
{
	mtx_t mutex;		//!< ロック用
	KcMapEntry **table; //!< ハッシュ用テーブル
	size_t table_size;	//!< ハッシュ用テーブルサイズ
	size_t size;		//!< 要素数
} KcMapInfo;

// =============================================================================
//  プロトタイプ宣言
// =============================================================================

static size_t KcMap_size(struct KcMap_ *map);
static void *KcMap_put(struct KcMap_ *map, const char *key, const void *obj, size_t size);
static void *KcMap_get(struct KcMap_ *map, const char *key, size_t *size);
static void KcMap_remove(struct KcMap_ *map, const char *key);
static void KcMap_clear(struct KcMap_ *map);
static void KcMap_entries(struct KcMap_ *map, bool (*handler)(const char *key, const void *val, size_t size, void *args), void *args);
static size_t KcMap_capacity(size_t cap);
static KcMapEntry *KcMapEntry_new(const char *key, const void *obj, size_t size);
static size_t KcMapEntry_key_size(const char *key);
static KcMapEntry *KcMapEntry_search(KcMapInfo *info, const char *key);

/**
 * マップ を構築します。
 *
 * @return cap マップのキー管理容量
 */
KcMap *KcMap_new(size_t cap)
{
	// KcMap の管理構造
	// +--------------+
	// | KcMap        |
	// |  ...         |
	// |  _info  -----------+
	// +--------------+     |
	// | <_info>      | <---+
	// | **table -----------+
	// | table_size   |     |
	// +--------------+     |
	// | <table>      | <---+
	// |              |
	// +--------------+
	size_t new_cap = KcMap_capacity(cap);
	KcMap *map = (KcMap *)malloc(
		sizeof(KcMap) + sizeof(KcMapInfo) + (sizeof(KcMapEntry) * new_cap));
	if (map != NULL)
	{
		map->size = KcMap_size;
		map->put = KcMap_put;
		map->get = KcMap_get;
		map->remove = KcMap_remove;
		map->clear = KcMap_clear;
		map->entries = KcMap_entries;
		map->_info = (map + 1);

		KcMapInfo *info = (KcMapInfo *)map->_info;
		info->table = (KcMapEntry **)(info + 1);
		info->table_size = new_cap;
		for (int i = 0; i < (int)new_cap; i++)
		{ // Table 初期化
			info->table[i] = NULL;
		}

		mtx_init(&(info->mutex), mtx_plain | mtx_recursive);
		info->size = 0;
	}
	return map;
}

/**
 * マップを破棄します。
 * @param map 破棄するマップ
 */
void KcMap_delete(KcMap *map)
{
	map->clear(map);
	free(map);
}

/**
 * 指定されたキーに対応するハッシュコードを返します。
 */
int KcMap_hash_code(const char *key)
{
	int code = 0;
	const char *ch = key;
	while (*ch != '\0')
	{
		code = code * 31 + *ch;
		ch++;
	}
	return code;
}

/**
 * マップに格納されている要素の数を返します。
 *
 * @param map 対象マップ
 * @return 要素の数
 */
static size_t KcMap_size(struct KcMap_ *map)
{
	KcMapInfo *info = (KcMapInfo *)map->_info;
	int size = -1;
	kc_lock_guard(&(info->mutex))
	{
		size = info->size;
	}
	return size;
}

/**
 * マップに要素を追加します。
 * オブジェクトはコピーして追加されます。
 * 追加に失敗した場合、NULL が返されます。
 *
 * @param map  対象マップ
 * @param key  キー
 * @param obj  キーに対応するオブジェクト
 * @param size オブジェクトのサイズ
 * @return 追加したオブジェクトへのポインタ
 */
static void *KcMap_put(struct KcMap_ *map, const char *key, const void *obj, size_t size)
{
	KcMapInfo *info = (KcMapInfo *)map->_info;
	int hash = KcMap_hash_code(key);
	KcMapEntry *entry = KcMapEntry_new(key, obj, size);
	if (entry != NULL)
	{
		kc_lock_guard(&(info->mutex))
		{ // 既に同じキーが登録されている場合、一度削除する。
			map->remove(map, key);

			// table_size は 2のべき乗
			// => -1 した値は、その値を上限として全てのビットが立つ。
			// 例) 8 -> (8-1) = 0b00000111
			// AND を取ると hash は、0-7 のテーブルのインデックス範囲となる。
			hash = hash & (info->table_size - 1);

			KcMapEntry *tmp_entry = info->table[hash];
			if (tmp_entry == NULL)
			{ // テーブルに入っていないため、先頭に設定
				info->table[hash] = entry;
				info->size++;
			}
			else
			{
				while (tmp_entry->next != NULL)
				{ // テーブルの先のリンクがなくなるまでたどる
					tmp_entry = tmp_entry->next;
				}
				tmp_entry->next = entry;
				entry->prev = tmp_entry;
				info->size++;
			}
		}
		return entry->value;
	}
	else
	{
		return NULL;
	}
}

/**
 * 指定されたキーに対応するオブジェクトを取得します。
 * オブジェクトは参照が渡されます。
 * size が NULL でない場合、取得されたオブジェクトのサイズが格納されます。
 *
 * @param map  対象マップ
 * @param key  キー
 * @param size オブジェクトのサイズ
 * @return オブジェクトへのポインタ
 */
static void *KcMap_get(struct KcMap_ *map, const char *key, size_t *size)
{
	KcMapInfo *info = (KcMapInfo *)map->_info;
	void *res_obj = NULL;
	kc_lock_guard(&(info->mutex))
	{
		KcMapEntry *entry = KcMapEntry_search(info, key);
		if (entry != NULL)
		{
			if (size)
			{
				*size = entry->size;
			}
			res_obj = entry->value;
		}
	}
	return res_obj;
}

/**
 * 指定されたキーに対応するオブジェクトを削除します。
 *
 * @param map  対象マップ
 * @param key  キー
 */
static void KcMap_remove(struct KcMap_ *map, const char *key)
{
	KcMapInfo *info = (KcMapInfo *)map->_info;
	kc_lock_guard(&(info->mutex))
	{
		// 指定されたキーに対応するエントリを探す。
		KcMapEntry *remove_entry = KcMapEntry_search(info, key);
		if (remove_entry != NULL)
		{
			if (remove_entry->prev)
			{ // 削除対象がテーブルの先のリストにある
				remove_entry->prev->next = remove_entry->next;
				if (remove_entry->next)
				{
					remove_entry->next->prev = remove_entry->prev;
				}
			}
			else
			{ // 削除対象が table の配列要素
				int hash = KcMap_hash_code(key);
				hash = hash & (info->table_size - 1);
				info->table[hash] = remove_entry->next;
				if (remove_entry->next)
				{
					remove_entry->next->prev = NULL;
				}
			}
			free(remove_entry);
			info->size--;
		}
	}
}

/**
 * マップ内のすべての要素を対象リストから削除します。
 *
 * @param list 対象リスト
 */
static void KcMap_clear(struct KcMap_ *map)
{
	KcMapInfo *info = (KcMapInfo *)map->_info;
	kc_lock_guard(&(info->mutex))
	{
		KcMapEntry *tmp_entry;
		for (int i = 0; i < (int)info->table_size; i++)
		{
			for (KcMapEntry *entry = info->table[i]; entry != NULL; entry = tmp_entry)
			{
				tmp_entry = entry->next;
				free(entry);
			}
			info->table[i] = NULL;
		}
		info->size = 0;
	}
}

/**
 * マップに格納されている要素を引数に、指定されたハンドラを実行します。
 * ハンドラ関数が false を返す場合、呼出し処置は中断されます。
 *
 * @param map 対象マップ
 * @param handler ハンドラ関数
 * @param args ハンドラ関数に渡されるユーザーデータ
 */
static void KcMap_entries(struct KcMap_ *map, bool (*handler)(const char *key, const void *val, size_t size, void *args), void *args)
{
	KcMapInfo *info = (KcMapInfo *)map->_info;
	kc_lock_guard(&(info->mutex))
	{
		bool is_continue = true;
		for (int i = 0; (i < (int)info->table_size) && is_continue; i++)
		{
			for (KcMapEntry *entry = info->table[i]; (entry != NULL && is_continue); entry = entry->next)
			{
				is_continue = handler(entry->key, entry->value, entry->size, args);
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// [内部関数]
//

/**
 * 指定された容量を 2 のべき乗に揃えます。
 * cap に 0 または、 KC_MAP_CAPACITY_MAX より大きい値が指定された場合、
 * KC_MAP_CAPACITY_MAP の容量となります。
 *
 * @param cap 容量
 * @return 調整された容量
 */
static size_t KcMap_capacity(size_t cap)
{
	size_t new_cap = 1;
	if ((cap < 1) || (KC_MAP_CAPACITY_MAX < cap))
	{
		new_cap = KC_MAP_CAPACITY_MAX;
	}
	else
	{
		while (new_cap < cap)
		{
			new_cap <<= 1;
		}
	}
	return new_cap;
}

/**
 * KcMapEntry を生成します。
 * 生成に失敗した場合、NULL を返します。
 *
 * @param key キー
 * @param obj 値
 * @param size 値のサイズ
 * @return 生成された KeyMapEntry
 */
static KcMapEntry *KcMapEntry_new(const char *key, const void *obj, size_t size)
{
	// KcMapEntry の構成
	// +--------------+
	// | <KcMapEntry> |
	// | key -------------+
	// | value -----------|--+
	// | size         |   |  |
	// | next         |   |  |
	// +--------------+   |  |
	// | <key>        |<--+  |
	// | <value>      |<-----+
	// +--------------+
	size_t key_size = KcMapEntry_key_size(key);
	size_t new_size = sizeof(KcMapEntry) + key_size + size;
	KcMapEntry *entry = (KcMapEntry *)malloc(new_size);
	if (entry != NULL)
	{
		entry->key = (char *)(entry + 1);
		entry->key_size = key_size;
		entry->value = &entry->key[entry->key_size];
		entry->size = size;
		entry->next = NULL;
		entry->prev = NULL;
		strncpy(entry->key, key, entry->key_size);
		memcpy(entry->value, obj, size);
	}
	return entry;
}

/**
 * キーのメモリ割り当てサイズを取得します。
 * キーのメモリ割り当ては、ポインタの整数倍に揃えられます。
 *
 * @param key キー
 * @return キーのメモリ割り当てサイズ
 */
static size_t KcMapEntry_key_size(const char *key)
{
	size_t size = strlen(key) + 1;
	int nmemb = size / sizeof(void *);
	return (sizeof(void *) * (nmemb + 1));
}

/**
 * 指定されたキーに対応するエントリを取得します。
 *
 * @param info マップ情報
 * @param key キー
 * @return キーに対応するエントリ
 */
static KcMapEntry *KcMapEntry_search(
	KcMapInfo *info, const char *key)
{
	int hash = KcMap_hash_code(key);
	hash = hash & (info->table_size - 1);
	KcMapEntry *entry = (info->table)[hash];
	while (entry != NULL)
	{
		if (strcmp(entry->key, key) == 0)
		{ // キーが一致
			break;
		}
		entry = entry->next;
	}
	return entry;
}