/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013
* Jonathan Schleifer <js@webkeks.org>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#import "OFMapTable.h"
#import "OFEnumerator.h"
#import "OFEnumerationMutationException.h"
#import "OFInvalidArgumentException.h"
#import "OFOutOfRangeException.h"
#import "macros.h"
#define MIN_CAPACITY 16
struct of_map_table_bucket {
void *key, *value;
uint32_t hash;
};
static struct of_map_table_bucket deleted = {};
static void*
default_retain(void *value)
{
return value;
}
static void
default_release(void *value)
{
}
static uint32_t
default_hash(void *value)
{
return (uint32_t)(uintptr_t)value;
}
static BOOL
default_equal(void *value1, void *value2)
{
return (value1 == value2);
}
@interface OFMapTableKeyEnumerator: OFMapTableEnumerator
@end
@interface OFMapTableValueEnumerator: OFMapTableEnumerator
@end
@implementation OFMapTable
+ (instancetype)mapTableWithKeyFunctions: (of_map_table_functions_t)keyFunctions
valueFunctions: (of_map_table_functions_t)
valueFunctions
{
return [[[self alloc]
initWithKeyFunctions: keyFunctions
valueFunctions: valueFunctions] autorelease];
}
+ (instancetype)mapTableWithKeyFunctions: (of_map_table_functions_t)keyFunctions
valueFunctions: (of_map_table_functions_t)
valueFunctions
capacity: (size_t)capacity
{
return [[[self alloc]
initWithKeyFunctions: keyFunctions
valueFunctions: valueFunctions
capacity: capacity] autorelease];
}
- init
{
@try {
[self doesNotRecognizeSelector: _cmd];
abort();
} @catch (id e) {
[self release];
@throw e;
}
}
- initWithKeyFunctions: (of_map_table_functions_t)keyFunctions_
valueFunctions: (of_map_table_functions_t)valueFunctions_
{
return [self initWithKeyFunctions: keyFunctions_
valueFunctions: valueFunctions_
capacity: 0];
}
- initWithKeyFunctions: (of_map_table_functions_t)keyFunctions_
valueFunctions: (of_map_table_functions_t)valueFunctions_
capacity: (size_t)capacity_
{
self = [super init];
@try {
keyFunctions = keyFunctions_;
valueFunctions = valueFunctions_;
#define SET_DEFAULT(var, value) \
if (var == NULL) \
var = value;
SET_DEFAULT(keyFunctions.retain, default_retain);
SET_DEFAULT(keyFunctions.release, default_release);
SET_DEFAULT(keyFunctions.hash, default_hash);
SET_DEFAULT(keyFunctions.equal, default_equal);
SET_DEFAULT(valueFunctions.retain, default_retain);
SET_DEFAULT(valueFunctions.release, default_release);
SET_DEFAULT(valueFunctions.hash, default_hash);
SET_DEFAULT(valueFunctions.equal, default_equal);
#undef SET_DEFAULT
if (capacity_ > UINT32_MAX ||
capacity_ > UINT32_MAX / sizeof(*buckets) ||
capacity_ > UINT32_MAX / 8)
@throw [OFOutOfRangeException
exceptionWithClass: [self class]];
for (capacity = 1; capacity < capacity_; capacity <<= 1);
if (capacity_ * 8 / capacity >= 6)
capacity <<= 1;
if (capacity < MIN_CAPACITY)
capacity = MIN_CAPACITY;
minCapacity = capacity;
buckets = [self allocMemoryWithSize: sizeof(*buckets)
count: capacity];
memset(buckets, 0, capacity * sizeof(*buckets));
if (of_hash_seed != 0)
#if defined(OF_HAVE_ARC4RANDOM)
rotate = arc4random() & 31;
#elif defined(OF_HAVE_RANDOM)
rotate = random() & 31;
#else
rotate = rand() & 31;
#endif
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
uint32_t i;
for (i = 0; i < capacity; i++) {
if (buckets[i] != NULL && buckets[i] != &deleted) {
keyFunctions.release(buckets[i]->key);
valueFunctions.release(buckets[i]->value);
}
}
[super dealloc];
}
- (BOOL)isEqual: (id)mapTable_
{
OFMapTable *mapTable;
uint32_t i;
if (![mapTable_ isKindOfClass: [OFMapTable class]])
return NO;
mapTable = mapTable_;
if (mapTable->count != count ||
mapTable->keyFunctions.equal != keyFunctions.equal ||
mapTable->valueFunctions.equal != valueFunctions.equal)
return NO;
for (i = 0; i < capacity; i++) {
if (buckets[i] != NULL && buckets[i] != &deleted) {
void *value = [mapTable valueForKey: buckets[i]->key];
if (!valueFunctions.equal(value, buckets[i]->value))
return NO;
}
}
return YES;
}
- (uint32_t)hash
{
uint32_t i, hash = 0;
for (i = 0; i < capacity; i++) {
if (buckets[i] != NULL && buckets[i] != &deleted) {
hash += OF_ROR(buckets[i]->hash, rotate);
hash += valueFunctions.hash(buckets[i]->value);
}
}
return hash;
}
- (id)copy
{
OFMapTable *copy = [[OFMapTable alloc]
initWithKeyFunctions: keyFunctions
valueFunctions: valueFunctions
capacity: capacity];
@try {
uint32_t i;
for (i = 0; i < capacity; i++)
if (buckets[i] != NULL && buckets[i] != &deleted)
[copy OF_setValue: buckets[i]->value
forKey: buckets[i]->key
hash: OF_ROR(buckets[i]->hash,
rotate)];
} @catch (id e) {
[copy release];
@throw e;
}
copy->minCapacity = MIN_CAPACITY;
return copy;
}
- (size_t)count
{
return count;
}
- (void*)valueForKey: (void*)key
{
uint32_t i, hash, last;
if (key == NULL)
@throw [OFInvalidArgumentException
exceptionWithClass: [self class]
selector: _cmd];
hash = OF_ROL(keyFunctions.hash(key), rotate);
last = capacity;
for (i = hash & (capacity - 1); i < last && buckets[i] != NULL; i++) {
if (buckets[i] == &deleted)
continue;
if (keyFunctions.equal(buckets[i]->key, key))
return buckets[i]->value;
}
if (i < last)
return nil;
/* In case the last bucket is already used */
last = hash & (capacity - 1);
for (i = 0; i < last && buckets[i] != NULL; i++) {
if (buckets[i] == &deleted)
continue;
if (keyFunctions.equal(buckets[i]->key, key))
return buckets[i]->value;
}
return NULL;
}
- (void)OF_resizeForCount: (uint32_t)newCount
{
uint32_t i, fullness, newCapacity;
struct of_map_table_bucket **newBuckets;
if (newCount > UINT32_MAX || newCount > UINT32_MAX / sizeof(*buckets) ||
newCount > UINT32_MAX / 8)
@throw [OFOutOfRangeException exceptionWithClass: [self class]];
fullness = newCount * 8 / capacity;
if (fullness >= 6)
newCapacity = capacity << 1;
else if (fullness <= 1)
newCapacity = capacity >> 1;
else
return;
if (newCapacity < capacity && newCapacity < minCapacity)
return;
newBuckets = [self allocMemoryWithSize: sizeof(*newBuckets)
count: newCapacity];
for (i = 0; i < newCapacity; i++)
newBuckets[i] = NULL;
for (i = 0; i < capacity; i++) {
if (buckets[i] != NULL && buckets[i] != &deleted) {
uint32_t j, last;
last = newCapacity;
j = buckets[i]->hash & (newCapacity - 1);
for (; j < last && newBuckets[j] != NULL; j++);
/* In case the last bucket is already used */
if (j >= last) {
last = buckets[i]->hash & (newCapacity - 1);
for (j = 0; j < last &&
newBuckets[j] != NULL; j++);
}
assert(j < last);
newBuckets[j] = buckets[i];
}
}
[self freeMemory: buckets];
buckets = newBuckets;
capacity = newCapacity;
}
- (void)OF_setValue: (void*)value
forKey: (void*)key
hash: (uint32_t)hash
{
uint32_t i, last;
void *old;
if (key == NULL || value == NULL)
@throw [OFInvalidArgumentException
exceptionWithClass: [self class]
selector: _cmd];
hash = OF_ROL(hash, rotate);
last = capacity;
for (i = hash & (capacity - 1); i < last && buckets[i] != NULL; i++) {
if (buckets[i] == &deleted)
continue;
if (keyFunctions.equal(buckets[i]->key, key))
break;
}
/* In case the last bucket is already used */
if (i >= last) {
last = hash & (capacity - 1);
for (i = 0; i < last && buckets[i] != NULL; i++) {
if (buckets[i] == &deleted)
continue;
if (keyFunctions.equal(buckets[i]->key, key))
break;
}
}
/* Key not in dictionary */
if (i >= last || buckets[i] == NULL || buckets[i] == &deleted ||
!keyFunctions.equal(buckets[i]->key, key)) {
struct of_map_table_bucket *bucket;
[self OF_resizeForCount: count + 1];
mutations++;
last = capacity;
for (i = hash & (capacity - 1); i < last &&
buckets[i] != NULL && buckets[i] != &deleted; i++);
/* In case the last bucket is already used */
if (i >= last) {
last = hash & (capacity - 1);
for (i = 0; i < last && buckets[i] != NULL &&
buckets[i] != &deleted; i++);
}
if (i >= last)
@throw [OFOutOfRangeException
exceptionWithClass: [self class]];
bucket = [self allocMemoryWithSize: sizeof(*bucket)];
@try {
bucket->key = keyFunctions.retain(key);
} @catch (id e) {
[self freeMemory: bucket];
@throw e;
}
@try {
bucket->value = valueFunctions.retain(value);
} @catch (id e) {
keyFunctions.release(key);
[self freeMemory: bucket];
@throw e;
}
bucket->hash = hash;
buckets[i] = bucket;
count++;
return;
}
old = buckets[i]->value;
buckets[i]->value = valueFunctions.retain(value);
valueFunctions.release(old);
}
- (void)setValue: (void*)value
forKey: (void*)key
{
[self OF_setValue: value
forKey: key
hash: keyFunctions.hash(key)];
}
- (void)removeValueForKey: (void*)key
{
uint32_t i, hash, last;
if (key == NULL)
@throw [OFInvalidArgumentException
exceptionWithClass: [self class]
selector: _cmd];
hash = OF_ROL(keyFunctions.hash(key), rotate);
last = capacity;
for (i = hash & (capacity - 1); i < last && buckets[i] != NULL; i++) {
if (buckets[i] == &deleted)
continue;
if (keyFunctions.equal(buckets[i]->key, key)) {
keyFunctions.release(buckets[i]->key);
valueFunctions.release(buckets[i]->value);
[self freeMemory: buckets[i]];
buckets[i] = &deleted;
count--;
mutations++;
[self OF_resizeForCount: count];
return;
}
}
if (i < last)
return;
/* In case the last bucket is already used */
last = hash & (capacity - 1);
for (i = 0; i < last && buckets[i] != NULL; i++) {
if (buckets[i] == &deleted)
continue;
if (keyFunctions.equal(buckets[i]->key, key)) {
keyFunctions.release(buckets[i]->key);
valueFunctions.release(buckets[i]->value);
[self freeMemory: buckets[i]];
buckets[i] = &deleted;
count--;
mutations++;
[self OF_resizeForCount: count];
return;
}
}
}
- (BOOL)containsValue: (void*)value
{
uint32_t i;
if (value == NULL || count == 0)
return NO;
for (i = 0; i < capacity; i++)
if (buckets[i] != NULL && buckets[i] != &deleted)
if (valueFunctions.equal(buckets[i]->value, value))
return YES;
return NO;
}
- (BOOL)containsValueIdenticalTo: (void*)value
{
uint32_t i;
if (value == NULL || count == 0)
return NO;
for (i = 0; i < capacity; i++)
if (buckets[i] != NULL && buckets[i] != &deleted)
if (buckets[i]->value == value)
return YES;
return NO;
}
- (OFMapTableEnumerator*)keyEnumerator
{
return [[[OFMapTableKeyEnumerator alloc]
OF_initWithMapTable: self
buckets: buckets
capacity: capacity
mutationsPointer: &mutations] autorelease];
}
- (OFMapTableEnumerator*)valueEnumerator
{
return [[[OFMapTableValueEnumerator alloc]
OF_initWithMapTable: self
buckets: buckets
capacity: capacity
mutationsPointer: &mutations] autorelease];
}
- (int)countByEnumeratingWithState: (of_fast_enumeration_state_t*)state
objects: (id*)objects
count: (int)count_
{
uint32_t j = (uint32_t)state->state;
int i;
for (i = 0; i < count_; i++) {
for (; j < capacity && (buckets[j] == NULL ||
buckets[j] == &deleted); j++);
if (j < capacity) {
objects[i] = buckets[j]->key;
j++;
} else
break;
}
state->state = j;
state->itemsPtr = objects;
state->mutationsPtr = &mutations;
return i;
}
#ifdef OF_HAVE_BLOCKS
- (void)enumerateKeysAndValuesUsingBlock:
(of_map_table_enumeration_block_t)block
{
size_t i;
BOOL stop = NO;
unsigned long mutations_ = mutations;
for (i = 0; i < capacity && !stop; i++) {
if (mutations != mutations_)
@throw [OFEnumerationMutationException
exceptionWithClass: [self class]
object: self];
if (buckets[i] != NULL && buckets[i] != &deleted)
block(buckets[i]->key, buckets[i]->value, &stop);
}
}
- (void)replaceValuesUsingBlock: (of_map_table_replace_block_t)block
{
size_t i;
BOOL stop = NO;
unsigned long mutations_ = mutations;
for (i = 0; i < capacity && !stop; i++) {
if (mutations != mutations_)
@throw [OFEnumerationMutationException
exceptionWithClass: [self class]
object: self];
if (buckets[i] != NULL && buckets[i] != &deleted) {
void *old, *new;
new = block(buckets[i]->key, buckets[i]->value, &stop);
if (new == NULL)
@throw [OFInvalidArgumentException
exceptionWithClass: [self class]
selector: _cmd];
old = buckets[i]->value;
buckets[i]->value = valueFunctions.retain(new);
valueFunctions.release(old);
}
}
}
#endif
- (of_map_table_functions_t)keyFunctions
{
return keyFunctions;
}
- (of_map_table_functions_t)valueFunctions
{
return valueFunctions;
}
@end
@implementation OFMapTableEnumerator
- init
{
@try {
[self doesNotRecognizeSelector: _cmd];
abort();
} @catch (id e) {
[self release];
@throw e;
}
}
- OF_initWithMapTable: (OFMapTable*)mapTable_
buckets: (struct of_map_table_bucket**)buckets_
capacity: (uint32_t)capacity_
mutationsPointer: (unsigned long*)mutationsPtr_
{
self = [super init];
mapTable = [mapTable_ retain];
buckets = buckets_;
capacity = capacity_;
mutations = *mutationsPtr_;
mutationsPtr = mutationsPtr_;
return self;
}
- (void)dealloc
{
[mapTable release];
[super dealloc];
}
- (void*)nextValue
{
[self doesNotRecognizeSelector: _cmd];
abort();
}
- (void)reset
{
if (*mutationsPtr != mutations)
@throw [OFEnumerationMutationException
exceptionWithClass: [mapTable class]
object: mapTable];
position = 0;
}
@end
@implementation OFMapTableKeyEnumerator
- (void*)nextValue
{
if (*mutationsPtr != mutations)
@throw [OFEnumerationMutationException
exceptionWithClass: [mapTable class]
object: mapTable];
for (; position < capacity && (buckets[position] == NULL ||
buckets[position] == &deleted); position++);
if (position < capacity)
return buckets[position++]->key;
else
return NULL;
}
@end
@implementation OFMapTableValueEnumerator
- (void*)nextValue
{
if (*mutationsPtr != mutations)
@throw [OFEnumerationMutationException
exceptionWithClass: [mapTable class]
object: mapTable];
for (; position < capacity && (buckets[position] == NULL ||
buckets[position] == &deleted); position++);
if (position < capacity)
return buckets[position++]->value;
else
return NULL;
}
@end
@implementation OFMapTableEnumeratorWrapper
- initWithEnumerator: (OFMapTableEnumerator*)enumerator_
object: (id)object_
{
self = [super init];
enumerator = [enumerator_ retain];
object = [object_ retain];
return self;
}
- (void)dealloc
{
[enumerator release];
[object release];
[super dealloc];
}
- (id)nextObject
{
id ret;
@try {
ret = [enumerator nextValue];
} @catch (OFEnumerationMutationException *e) {
@throw [OFEnumerationMutationException
exceptionWithClass: [object class]
object: object];
}
return ret;
}
- (void)reset
{
@try {
[enumerator reset];
} @catch (OFEnumerationMutationException *e) {
@throw [OFEnumerationMutationException
exceptionWithClass: [object class]
object: object];
}
}
@end