/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* 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"
#ifdef OF_OBJFW_RUNTIME
# import "ObjFWRT.h"
# import "private.h"
#else
# import "OFObject.h"
# import "OFMapTable.h"
#endif
struct Association {
id object;
objc_associationPolicy policy;
};
#ifdef OF_OBJFW_RUNTIME
typedef struct objc_hashtable objc_hashtable;
#else
typedef OFMapTable objc_hashtable;
static const OFMapTableFunctions defaultFunctions = { NULL };
static objc_hashtable *
objc_hashtable_new(uint32_t (*hash)(const void *key),
bool (*equal)(const void *key1, const void *key2), uint32_t size)
{
return [[OFMapTable alloc] initWithKeyFunctions: defaultFunctions
objectFunctions: defaultFunctions];
}
static void
objc_hashtable_set(objc_hashtable *hashtable, const void *key,
const void *object)
{
return [hashtable setObject: (void *)object forKey: (void *)key];
}
static void *
objc_hashtable_get(objc_hashtable *hashtable, const void *key)
{
return [hashtable objectForKey: (void *)key];
}
static void
objc_hashtable_delete(objc_hashtable *hashtable, const void *key)
{
[hashtable removeObjectForKey: (void *)key];
}
static void
objc_hashtable_free(objc_hashtable *hashtable)
{
[hashtable release];
}
# define OBJC_ERROR(...) abort()
#endif
#ifdef OF_HAVE_THREADS
# define numSlots 8 /* needs to be a power of 2 */
# import "OFPlainMutex.h"
static OFSpinlock spinlocks[numSlots];
#else
# define numSlots 1
#endif
static objc_hashtable *hashtables[numSlots];
static OF_INLINE size_t
slotForObject(id object)
{
return ((size_t)((uintptr_t)object >> 4) & (numSlots - 1));
}
static uint32_t
hash(const void *object)
{
return (uint32_t)(uintptr_t)object;
}
static bool
equal(const void *object1, const void *object2)
{
return (object1 == object2);
}
OF_CONSTRUCTOR()
{
for (size_t i = 0; i < numSlots; i++) {
hashtables[i] = objc_hashtable_new(hash, equal, 2);
#ifdef OF_HAVE_THREADS
if (OFSpinlockNew(&spinlocks[i]) != 0)
OBJC_ERROR("Failed to create spinlocks!");
#endif
}
}
void
objc_setAssociatedObject(id object, const void *key, id value,
objc_associationPolicy policy)
{
size_t slot;
switch (policy) {
case OBJC_ASSOCIATION_ASSIGN:
break;
case OBJC_ASSOCIATION_RETAIN:
case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
value = [value retain];
break;
case OBJC_ASSOCIATION_COPY:
case OBJC_ASSOCIATION_COPY_NONATOMIC:
value = [value copy];
break;
default:
/* Don't know what to do, so do nothing. */
return;
}
slot = slotForObject(object);
#ifdef OF_HAVE_THREADS
if (OFSpinlockLock(&spinlocks[slot]) != 0)
OBJC_ERROR("Failed to lock spinlock!");
@try {
#endif
objc_hashtable *objectHashtable;
struct Association *association;
objectHashtable = objc_hashtable_get(hashtables[slot], object);
if (objectHashtable == NULL) {
objectHashtable = objc_hashtable_new(hash, equal, 2);
objc_hashtable_set(hashtables[slot], object,
objectHashtable);
}
association = objc_hashtable_get(objectHashtable, key);
if (association != NULL) {
switch (association->policy) {
case OBJC_ASSOCIATION_RETAIN:
case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
case OBJC_ASSOCIATION_COPY:
case OBJC_ASSOCIATION_COPY_NONATOMIC:
[association->object release];
break;
default:
break;
}
} else {
association = malloc(sizeof(*association));
if (association == NULL)
OBJC_ERROR("Failed to allocate association!");
objc_hashtable_set(objectHashtable, key, association);
}
association->policy = policy;
association->object = value;
#ifdef OF_HAVE_THREADS
} @finally {
if (OFSpinlockUnlock(&spinlocks[slot]) != 0)
OBJC_ERROR("Failed to unlock spinlock!");
}
#endif
}
id
objc_getAssociatedObject(id object, const void *key)
{
size_t slot = slotForObject(object);
id ret;
#ifdef OF_HAVE_THREADS
if (OFSpinlockLock(&spinlocks[slot]) != 0)
OBJC_ERROR("Failed to lock spinlock!");
@try {
#endif
objc_hashtable *objectHashtable;
struct Association *association;
objectHashtable = objc_hashtable_get(hashtables[slot], object);
if (objectHashtable == NULL)
return nil;
association = objc_hashtable_get(objectHashtable, key);
if (association == NULL)
return nil;
switch (association->policy) {
case OBJC_ASSOCIATION_RETAIN:
case OBJC_ASSOCIATION_COPY:
ret = [[association->object retain] autorelease];
break;
default:
ret = association->object;
break;
}
#ifdef OF_HAVE_THREADS
} @finally {
if (OFSpinlockUnlock(&spinlocks[slot]) != 0)
OBJC_ERROR("Failed to unlock spinlock!");
}
#endif
return ret;
}
void
objc_removeAssociatedObjects(id object)
{
size_t slot = slotForObject(object);
#ifdef OF_HAVE_THREADS
if (OFSpinlockLock(&spinlocks[slot]) != 0)
OBJC_ERROR("Failed to lock spinlock!");
@try {
#endif
objc_hashtable *objectHashtable;
objectHashtable = objc_hashtable_get(hashtables[slot], object);
if (objectHashtable == NULL)
return;
#ifdef OF_OBJFW_RUNTIME
for (uint32_t i = 0; i < objectHashtable->size; i++) {
struct Association *association;
if (objectHashtable->data[i] == NULL ||
objectHashtable->data[i] == &objc_deletedBucket)
continue;
association = (struct Association *)
objectHashtable->data[i]->object;
#else
OFMapTableEnumerator *enumerator =
[objectHashtable objectEnumerator];
void **iter;
while ((iter = [enumerator nextObject]) != NULL) {
struct Association *association = *iter;
#endif
switch (association->policy) {
case OBJC_ASSOCIATION_RETAIN:
case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
case OBJC_ASSOCIATION_COPY:
case OBJC_ASSOCIATION_COPY_NONATOMIC:
[association->object release];
break;
default:
break;
}
free(association);
}
objc_hashtable_delete(hashtables[slot], object);
objc_hashtable_free(objectHashtable);
#ifdef OF_HAVE_THREADS
} @finally {
if (OFSpinlockUnlock(&spinlocks[slot]) != 0)
OBJC_ERROR("Failed to unlock spinlock!");
}
#endif
}