/*
* Copyright (c) 2008, 2009, 2010, 2011
* 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 <string.h>
#import "OFMutableDictionary_hashtable.h"
#import "OFDictionary_hashtable.h"
#import "OFAutoreleasePool.h"
#import "OFEnumerationMutationException.h"
#import "OFInvalidArgumentException.h"
#import "OFOutOfRangeException.h"
#import "macros.h"
#define DELETED &of_dictionary_hashtable_deleted_bucket
static Class dictionary = Nil;
@implementation OFMutableDictionary_hashtable
+ (void)initialize
{
if (self == [OFMutableDictionary_hashtable class]) {
dictionary = [OFDictionary_hashtable class];
[self inheritMethodsFromClass: dictionary];
}
}
- (void)_resizeForCount: (size_t)newCount
{
size_t fullness = newCount * 4 / size;
struct of_dictionary_hashtable_bucket **newData;
uint32_t i, newSize;
if (newCount > UINT32_MAX)
@throw [OFOutOfRangeException newWithClass: isa];
if (fullness >= 3)
newSize = size << 1;
else if (fullness <= 1)
newSize = size >> 1;
else
return;
if (newSize == 0)
@throw [OFOutOfRangeException newWithClass: isa];
newData = [self allocMemoryForNItems: newSize
withSize: sizeof(*newData)];
for (i = 0; i < newSize; i++)
newData[i] = NULL;
for (i = 0; i < size; i++) {
if (data[i] != NULL && data[i] != DELETED) {
uint32_t j, last;
last = newSize;
j = data[i]->hash & (newSize - 1);
for (; j < last && newData[j] != NULL; j++);
/* In case the last bucket is already used */
if (j >= last) {
last = data[i]->hash & (newSize - 1);
for (j = 0; j < last &&
newData[j] != NULL; j++);
}
if (j >= last) {
[self freeMemory: newData];
@throw [OFOutOfRangeException
newWithClass: isa];
}
newData[j] = data[i];
}
}
[self freeMemory: data];
data = newData;
size = newSize;
}
- (void)_setObject: (id)object
forKey: (id)key
copyKey: (BOOL)copyKey
{
uint32_t i, hash, last;
id old;
if (key == nil || object == nil)
@throw [OFInvalidArgumentException newWithClass: isa
selector: _cmd];
hash = [key hash];
last = size;
for (i = hash & (size - 1); i < last && data[i] != NULL; i++) {
if (data[i] == DELETED)
continue;
if ([data[i]->key isEqual: key])
break;
}
/* In case the last bucket is already used */
if (i >= last) {
last = hash & (size - 1);
for (i = 0; i < last && data[i] != NULL; i++) {
if (data[i] == DELETED)
continue;
if ([data[i]->key isEqual: key])
break;
}
}
/* Key not in dictionary */
if (i >= last || data[i] == NULL || data[i] == DELETED ||
![data[i]->key isEqual: key]) {
struct of_dictionary_hashtable_bucket *bucket;
[self _resizeForCount: count + 1];
mutations++;
last = size;
for (i = hash & (size - 1); i < last && data[i] != NULL &&
data[i] != DELETED; i++);
/* In case the last bucket is already used */
if (i >= last) {
last = hash & (size - 1);
for (i = 0; i < last && data[i] != NULL &&
data[i] != DELETED; i++);
}
if (i >= last)
@throw [OFOutOfRangeException newWithClass: isa];
bucket = [self allocMemoryWithSize: sizeof(*bucket)];
@try {
key = (copyKey ? [key copy] : [key retain]);
} @catch (id e) {
[self freeMemory: bucket];
@throw e;
}
@try {
[object retain];
} @catch (id e) {
[self freeMemory: bucket];
[key release];
@throw e;
}
bucket->key = key;
bucket->object = object;
bucket->hash = hash;
data[i] = bucket;
count++;
return;
}
old = data[i]->object;
data[i]->object = [object retain];
[old release];
}
- (void)setObject: (id)object
forKey: (id)key
{
[self _setObject: object
forKey: key
copyKey: YES];
}
- (void)removeObjectForKey: (id)key
{
uint32_t i, hash, last;
if (key == nil)
@throw [OFInvalidArgumentException newWithClass: isa
selector: _cmd];
hash = [key hash];
last = size;
for (i = hash & (size - 1); i < last && data[i] != NULL; i++) {
if (data[i] == DELETED)
continue;
if ([data[i]->key isEqual: key]) {
[data[i]->key release];
[data[i]->object release];
[self freeMemory: data[i]];
data[i] = DELETED;
count--;
mutations++;
[self _resizeForCount: count];
return;
}
}
if (i < last)
return;
/* In case the last bucket is already used */
last = hash & (size - 1);
for (i = 0; i < last && data[i] != NULL; i++) {
if (data[i] == DELETED)
continue;
if ([data[i]->key isEqual: key]) {
[data[i]->key release];
[data[i]->object release];
[self freeMemory: data[i]];
data[i] = DELETED;
count--;
mutations++;
[self _resizeForCount: count];
return;
}
}
}
- (int)countByEnumeratingWithState: (of_fast_enumeration_state_t*)state
objects: (id*)objects
count: (int)count_
{
const SEL selector =
@selector(countByEnumeratingWithState:objects:count:);
int (*imp)(id, SEL, of_fast_enumeration_state_t*, id*, int) =
(int(*)(id, SEL, of_fast_enumeration_state_t*, id*, int))
[dictionary instanceMethodForSelector: selector];
int ret = imp(self, selector, state, objects, count_);
state->mutationsPtr = &mutations;
return ret;
}
- (OFEnumerator*)objectEnumerator
{
return [[[OFDictionaryObjectEnumerator_hashtable alloc]
initWithDictionary: (OFDictionary_hashtable*)self
data: data
size: size
mutationsPointer: &mutations] autorelease];
}
- (OFEnumerator*)keyEnumerator
{
return [[[OFDictionaryKeyEnumerator_hashtable alloc]
initWithDictionary: (OFDictionary_hashtable*)self
data: data
size: size
mutationsPointer: &mutations] autorelease];
}
#ifdef OF_HAVE_BLOCKS
- (void)enumerateKeysAndObjectsUsingBlock:
(of_dictionary_enumeration_block_t)block
{
size_t i;
BOOL stop = NO;
unsigned long mutations2 = mutations;
for (i = 0; i < size && !stop; i++) {
if (mutations != mutations2)
@throw [OFEnumerationMutationException
newWithClass: isa
object: self];
if (data[i] != NULL && data[i] != DELETED)
block(data[i]->key, data[i]->object, &stop);
}
}
- (void)replaceObjectsUsingBlock: (of_dictionary_replace_block_t)block
{
size_t i;
BOOL stop = NO;
unsigned long mutations2 = mutations;
for (i = 0; i < size && !stop; i++) {
if (mutations != mutations2)
@throw [OFEnumerationMutationException
newWithClass: isa
object: self];
if (data[i] != NULL && data[i] != DELETED) {
id new = block(data[i]->key, data[i]->object, &stop);
if (new == nil)
@throw [OFInvalidArgumentException
newWithClass: isa
selector: _cmd];
[new retain];
[data[i]->object release];
data[i]->object = new;
}
}
}
#endif
- (void)makeImmutable
{
isa = [OFDictionary_hashtable class];
}
@end