mirror of
https://github.com/gnustep/libs-gsweb.git
synced 2025-02-21 02:41:04 +00:00
* GSWDatabase/WODisplayGroup.m remove local NSAutoreleasePool adjust makefiles git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gsweb/trunk@30698 72102866-910b-0410-8b05-ffd578937521
1724 lines
48 KiB
Objective-C
1724 lines
48 KiB
Objective-C
/** GSWMultiKeyDictionary.m - <title>GSWeb: Class GSWMultiKeyDictionary</title>
|
|
|
|
Copyright (C) 1999-2004 Free Software Foundation, Inc.
|
|
|
|
Written by: Manuel Guesdon <mguesdon@orange-concept.com>
|
|
Date: Mar 1999
|
|
|
|
Partially copied From GSIMap.h
|
|
written by Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
|
based on code written by Albin L. Jones <Albin.L.Jones@Dartmouth.EDU>
|
|
|
|
$Revision$
|
|
$Date$
|
|
|
|
This file is part of the GNUstep Web Library.
|
|
|
|
<license>
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
</license>
|
|
**/
|
|
|
|
#include "config.h"
|
|
|
|
RCS_ID("$Id$")
|
|
|
|
#include "GSWeb.h"
|
|
#include <limits.h>
|
|
|
|
#ifndef GNUSTEP
|
|
#include <GNUstepBase/GNUstep.h>
|
|
#include <GNUstepBase/GSObjCRuntime.h>
|
|
#endif
|
|
|
|
#define DEFAULT_DICTIONARY_CAPACITY 32
|
|
|
|
// Copied from NSDate.m. We should find a better solution....
|
|
static NSTimeInterval GSWTimeNow(void)
|
|
{
|
|
#if !defined(__MINGW__)
|
|
NSTimeInterval interval;
|
|
struct timeval tp;
|
|
|
|
gettimeofday (&tp, NULL);
|
|
interval = -NSTimeIntervalSince1970;
|
|
interval += tp.tv_sec;
|
|
interval += (double)tp.tv_usec / 1000000.0;
|
|
return interval;
|
|
#else
|
|
SYSTEMTIME sys_time;
|
|
NSTimeInterval t;
|
|
#if 0
|
|
NSCalendarDate *d;
|
|
|
|
// Get the system time
|
|
GetLocalTime(&sys_time);
|
|
|
|
// Use an NSCalendar object to make it easier
|
|
d = [NSCalendarDate alloc];
|
|
[d initWithYear: sys_time.wYear
|
|
month: sys_time.wMonth
|
|
day: sys_time.wDay
|
|
hour: sys_time.wHour
|
|
minute: sys_time.wMinute
|
|
second: sys_time.wSecond
|
|
timeZone: [NSTimeZone localTimeZone]];
|
|
t = otherTime(d);
|
|
RELEASE(d);
|
|
#else
|
|
/*
|
|
* Get current GMT time, convert to NSTimeInterval since reference date,
|
|
*/
|
|
GetSystemTime(&sys_time);
|
|
t = GSTime(sys_time.wDay, sys_time.wMonth, sys_time.wYear, sys_time.wHour,
|
|
sys_time.wMinute, sys_time.wSecond, sys_time.wMilliseconds);
|
|
#endif
|
|
return t;
|
|
#endif /* __MINGW__ */
|
|
}
|
|
|
|
typedef struct _GSWMapTable GSWMapTable_t;
|
|
typedef struct _GSWMapBase GSWMapBase_t;
|
|
typedef struct _GSWMapBucket GSWMapBucket_t;
|
|
typedef struct _GSWMapNode GSWMapNode_t;
|
|
typedef struct _GSWCacheMapNode GSWCacheMapNode_t;
|
|
|
|
typedef GSWMapTable_t *GSWMapTable;
|
|
typedef GSWMapBase_t *GSWMapBase;
|
|
typedef GSWMapBucket_t *GSWMapBucket;
|
|
typedef GSWMapNode_t *GSWMapNode;
|
|
typedef GSWCacheMapNode_t *GSWCacheMapNode;
|
|
|
|
struct _GSWMapNode {
|
|
GSWMapNode nextInBucket; /* Linked list of bucket. */
|
|
id key;
|
|
id value;
|
|
GSWMapTable subMap; /* SubMap */
|
|
};
|
|
|
|
struct _GSWCacheMapNode {
|
|
// Should have same first members as _GSWMapNode
|
|
GSWMapNode nextInBucket; /* Linked list of bucket. */
|
|
id key;
|
|
id value;
|
|
GSWMapTable subMap; /* SubMap */
|
|
|
|
NSTimeInterval firstAccessTS;
|
|
NSTimeInterval lastAccessTS;
|
|
NSTimeInterval cacheDuration;
|
|
unsigned int flags;
|
|
};
|
|
|
|
struct _GSWMapBucket {
|
|
size_t nodeCount; /* Number of nodes in bucket. */
|
|
GSWMapNode firstNode; /* The linked list of nodes. */
|
|
};
|
|
|
|
struct _GSWMapBase {
|
|
NSZone *zone;
|
|
NSUInteger nodeSize; /* Size of a node */
|
|
GSWMapTable firstTable;
|
|
|
|
GSWMapTable freeTables; /* List of unused tables. */
|
|
size_t tableChunkCount; /* Number of chunks in array. */
|
|
GSWMapTable *tableChunks; /* Chunks of allocated memory. */
|
|
|
|
GSWMapNode freeNodes; /* List of unused nodes. */
|
|
size_t nodeChunkCount; /* Number of chunks in array. */
|
|
GSWMapNode *nodeChunks; /* Chunks of allocated memory. */
|
|
};
|
|
|
|
struct _GSWMapTable {
|
|
GSWMapBase base;
|
|
GSWMapTable nextTable;
|
|
size_t nodeCount; /* Number of nodes in map. */
|
|
size_t bucketCount; /* Number of buckets in map. */
|
|
GSWMapBucket buckets; /* Array of buckets. */
|
|
};
|
|
|
|
typedef struct _GSWMapEnumerator {
|
|
GSWMapTable map; /* the map being enumerated. */
|
|
GSWMapNode node; /* The next node to use. */
|
|
size_t bucket; /* The next bucket to use. */
|
|
} *_GSIE;
|
|
|
|
#ifdef GSI_MAP_ENUMERATOR
|
|
typedef GSI_MAP_ENUMERATOR GSWMapEnumerator_t;
|
|
#else
|
|
typedef struct _GSWMapEnumerator GSWMapEnumerator_t;
|
|
#endif
|
|
typedef GSWMapEnumerator_t *GSWMapEnumerator;
|
|
|
|
static GSWMapBucket GSWMapPickBucket(unsigned hash,
|
|
GSWMapBucket buckets,
|
|
size_t bucketCount)
|
|
{
|
|
return buckets + hash % bucketCount;
|
|
}
|
|
|
|
static GSWMapBucket GSWMapBucketForKey(GSWMapTable map, id key)
|
|
{
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p key=%@",
|
|
map,key);
|
|
*/
|
|
return GSWMapPickBucket([key hash],
|
|
map->buckets,
|
|
map->bucketCount);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapCleanMap(GSWMapTable map);
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapEmptyMap(GSWMapTable map);
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapLinkNodeIntoBucket(GSWMapBucket bucket,
|
|
GSWMapNode node)
|
|
{
|
|
node->nextInBucket = bucket->firstNode;
|
|
bucket->firstNode = node;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapUnlinkNodeFromBucket(GSWMapBucket bucket,
|
|
GSWMapNode node)
|
|
{
|
|
if (node == bucket->firstNode)
|
|
{
|
|
bucket->firstNode = node->nextInBucket;
|
|
}
|
|
else
|
|
{
|
|
GSWMapNode tmp = bucket->firstNode;
|
|
|
|
while (tmp->nextInBucket != node)
|
|
{
|
|
tmp = tmp->nextInBucket;
|
|
}
|
|
tmp->nextInBucket = node->nextInBucket;
|
|
}
|
|
node->nextInBucket = 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapAddNodeToBucket(GSWMapBucket bucket,
|
|
GSWMapNode node)
|
|
{
|
|
GSWMapLinkNodeIntoBucket(bucket, node);
|
|
bucket->nodeCount += 1;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapAddNodeToMap(GSWMapTable map,
|
|
GSWMapNode node)
|
|
{
|
|
GSWMapBucket bucket;
|
|
|
|
bucket = GSWMapBucketForKey(map, node->key);
|
|
GSWMapAddNodeToBucket(bucket, node);
|
|
map->nodeCount++;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapRemoveNodeFromBucket(GSWMapBucket bucket,
|
|
GSWMapNode node)
|
|
{
|
|
bucket->nodeCount--;
|
|
GSWMapUnlinkNodeFromBucket(bucket, node);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapRemoveNodeFromMap(GSWMapTable map,
|
|
GSWMapBucket bkt,
|
|
GSWMapNode node)
|
|
{
|
|
map->nodeCount--;
|
|
GSWMapRemoveNodeFromBucket(bkt, node);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapRemangleBuckets(GSWMapTable map,
|
|
GSWMapBucket old_buckets,
|
|
size_t old_bucketCount,
|
|
GSWMapBucket new_buckets,
|
|
size_t new_bucketCount)
|
|
{
|
|
while (old_bucketCount-- > 0)
|
|
{
|
|
GSWMapNode node;
|
|
|
|
while ((node = old_buckets->firstNode) != 0)
|
|
{
|
|
GSWMapBucket bkt;
|
|
|
|
GSWMapRemoveNodeFromBucket(old_buckets, node);
|
|
bkt = GSWMapPickBucket([node->key hash],
|
|
new_buckets,
|
|
new_bucketCount);
|
|
GSWMapAddNodeToBucket(bkt, node);
|
|
}
|
|
old_buckets++;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapMoreNodes(GSWMapTable map, unsigned required)
|
|
{
|
|
GSWMapNode *newArray = NULL;
|
|
size_t arraySize = (map->base->nodeChunkCount+1)*sizeof(GSWMapNode);
|
|
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p required=%u",
|
|
map,required);
|
|
*/
|
|
#if GS_WITH_GC == 1
|
|
/*
|
|
* Our nodes may be allocated from the atomic zone - but we don't want
|
|
* them freed - so we must keep the array of pointers to memory chunks in
|
|
* the default zone
|
|
*/
|
|
if (map->base->zone == GSAtomicMallocZone())
|
|
{
|
|
newArray = (GSWMapNode*)NSZoneMalloc(NSDefaultMallocZone(), arraySize);
|
|
}
|
|
else
|
|
#endif
|
|
newArray = (GSWMapNode*)NSZoneMalloc(map->base->zone, arraySize);
|
|
if (newArray)
|
|
{
|
|
void* newNodes = NULL;
|
|
size_t chunkCount;
|
|
size_t chunkSize;
|
|
|
|
memcpy(newArray, map->base->nodeChunks, (map->base->nodeChunkCount)*sizeof(GSWMapNode));
|
|
if (map->base->nodeChunks)
|
|
{
|
|
NSZoneFree(map->base->zone, map->base->nodeChunks);
|
|
}
|
|
map->base->nodeChunks = newArray;
|
|
|
|
if (required == 0)
|
|
{
|
|
if (map->base->nodeChunkCount == 0)
|
|
{
|
|
chunkCount = map->bucketCount > 1 ? map->bucketCount : 2;
|
|
}
|
|
else
|
|
{
|
|
chunkCount = ((map->nodeCount>>2)+1)<<1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
chunkCount = required;
|
|
}
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p map->base=%p required=%u nodeSize=%u sizeof(GSWMapNode_t)=%u",
|
|
map,map->base,required,map->base->nodeSize,sizeof(GSWMapNode_t));
|
|
*/
|
|
NSCAssert2(map->base->nodeSize>=sizeof(GSWMapNode_t),
|
|
@"Bad node size: %u < %u",
|
|
map->base->nodeSize,
|
|
sizeof(GSWMapNode_t));
|
|
chunkSize = chunkCount * map->base->nodeSize;
|
|
newNodes = NSZoneMalloc(map->base->zone, chunkSize);
|
|
if (newNodes)
|
|
{
|
|
memset(newNodes,0,chunkSize);//I HATE unitialized memory !
|
|
map->base->nodeChunks[map->base->nodeChunkCount++] = newNodes;
|
|
chunkCount--;
|
|
((GSWMapNode)(newNodes+(chunkCount*map->base->nodeSize)))->nextInBucket = map->base->freeNodes;
|
|
while (chunkCount--)
|
|
{
|
|
((GSWMapNode)(newNodes+(chunkCount*map->base->nodeSize)))->nextInBucket =
|
|
(GSWMapNode)(newNodes+((chunkCount+1)*map->base->nodeSize));
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"newNodes[chunkCount].nextInBucket node=%p",
|
|
(GSWMapNode)(newNodes+(chunkCount*map->base->nodeSize)));
|
|
*/
|
|
}
|
|
map->base->freeNodes = newNodes;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map->base->freeNodes=%p",
|
|
map->base->freeNodes);
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapResize(GSWMapTable map, size_t new_capacity)
|
|
{
|
|
GSWMapBucket new_buckets = NULL;
|
|
size_t size = 1;
|
|
size_t old = 1;
|
|
|
|
/*
|
|
* Find next size up in the fibonacci series
|
|
*/
|
|
while (size < new_capacity)
|
|
{
|
|
size_t tmp = old;
|
|
|
|
old = size;
|
|
size += tmp;
|
|
}
|
|
/*
|
|
* Avoid 8 - since hash functions frequently generate uneven distributions
|
|
* around powers of two - we don't want lots of keys falling into a single
|
|
* bucket.
|
|
*/
|
|
if (size == 8)
|
|
{
|
|
size++;
|
|
}
|
|
|
|
/*
|
|
* Make a new set of buckets for this map
|
|
*/
|
|
new_buckets = (GSWMapBucket)NSZoneCalloc(map->base->zone, size,
|
|
sizeof(GSWMapBucket_t));
|
|
if (new_buckets != 0)
|
|
{
|
|
GSWMapRemangleBuckets(map,
|
|
map->buckets,
|
|
map->bucketCount, new_buckets,
|
|
size);
|
|
|
|
if (map->buckets != 0)
|
|
{
|
|
NSZoneFree(map->base->zone, map->buckets);
|
|
}
|
|
map->buckets = new_buckets;
|
|
map->bucketCount = size;
|
|
};
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapRightSizeMap(GSWMapTable map, size_t capacity)
|
|
{
|
|
/* FIXME: Now, this is a guess, based solely on my intuition. If anyone
|
|
* knows of a better ratio (or other test, for that matter) and can
|
|
* provide evidence of its goodness, please get in touch with me, Albin
|
|
* L. Jones <Albin.L.Jones@Dartmouth.EDU>. */
|
|
|
|
if (3 * capacity >= 4 * map->bucketCount)
|
|
{
|
|
GSWMapResize(map, (3 * capacity)/4 + 1);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapMoreTables(GSWMapBase base, unsigned required)
|
|
{
|
|
GSWMapTable *newArray = NULL;
|
|
size_t arraySize = (base->tableChunkCount+1)*sizeof(GSWMapTable);
|
|
|
|
#if GS_WITH_GC == 1
|
|
/*
|
|
* Our nodes may be allocated from the atomic zone - but we don't want
|
|
* them freed - so we must keep the array of pointers to memory chunks in
|
|
* the default zone
|
|
*/
|
|
if (base->zone == GSAtomicMallocZone())
|
|
{
|
|
newArray = (GSWMapTable*)NSZoneMalloc(NSDefaultMallocZone(), arraySize);
|
|
}
|
|
else
|
|
#endif
|
|
newArray = (GSWMapTable*)NSZoneMalloc(base->zone, arraySize);
|
|
if (newArray)
|
|
{
|
|
GSWMapTable newTables = NULL;
|
|
size_t chunkCount;
|
|
size_t chunkSize;
|
|
|
|
memcpy(newArray, base->tableChunks, (base->tableChunkCount)*sizeof(GSWMapTable));
|
|
|
|
if (base->tableChunks)
|
|
{
|
|
NSZoneFree(base->zone, base->tableChunks);
|
|
}
|
|
base->tableChunks = newArray;
|
|
|
|
if (required == 0)
|
|
{
|
|
chunkCount = 10;
|
|
}
|
|
else
|
|
{
|
|
chunkCount = required;
|
|
}
|
|
|
|
chunkSize = chunkCount * sizeof(GSWMapTable_t);
|
|
newTables = (GSWMapTable)NSZoneMalloc(base->zone, chunkSize);
|
|
|
|
if (newTables)
|
|
{
|
|
memset(newTables,0,chunkSize);//I HATE unitialized memory !
|
|
base->tableChunks[base->tableChunkCount++] = newTables;
|
|
newTables[--chunkCount].nextTable = base->freeTables;
|
|
while (chunkCount--)
|
|
{
|
|
newTables[chunkCount].nextTable = &newTables[chunkCount+1];
|
|
}
|
|
base->freeTables = newTables;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
static GSWMapNode GSWMapNewNode(GSWMapTable map, id key, id value)
|
|
{
|
|
GSWMapNode node = map->base->freeNodes;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p key='%@' value=%p node=%p",
|
|
map,map->base,key,value,node);
|
|
*/
|
|
if (!node)
|
|
{
|
|
GSWMapMoreNodes(map, 0);
|
|
node = map->base->freeNodes;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p key='%@' value=%p node=%p",
|
|
map,map->base,key,value,node);
|
|
*/
|
|
if (!node)
|
|
return NULL;
|
|
};
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p key='%@' value=%p node=%p",
|
|
map,map->base,key,value,node);
|
|
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p key='%@' value=%p node=%p node->nextInBucket=%p",
|
|
map,map->base,key,value,node,node->nextInBucket);
|
|
*/
|
|
map->base->freeNodes = node->nextInBucket;
|
|
|
|
ASSIGN(node->key,key);
|
|
ASSIGN(node->value,value);
|
|
node->nextInBucket = NULL;
|
|
node->subMap = NULL;
|
|
|
|
return node;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static GSWMapTable GSWMapNewTableWithCapacity(GSWMapBase base,size_t capacity)
|
|
{
|
|
GSWMapTable table = base->freeTables;
|
|
|
|
if (!table)
|
|
{
|
|
GSWMapMoreTables(base, 0);
|
|
table = base->freeTables;
|
|
if (!table)
|
|
return NULL;
|
|
}
|
|
|
|
base->freeTables = table->nextTable;
|
|
table->nextTable = NULL;
|
|
table->base=base;
|
|
|
|
table->nodeCount = 0;
|
|
table->bucketCount = 0;
|
|
table->buckets = NULL;
|
|
GSWMapRightSizeMap(table, capacity);
|
|
GSWMapMoreNodes(table, capacity);
|
|
|
|
return table;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapFreeNode(GSWMapTable map, GSWMapNode node)
|
|
{
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p node=%p",
|
|
map,map->base,node);
|
|
*/
|
|
DESTROY(node->key);
|
|
DESTROY(node->value);
|
|
if (node->subMap)
|
|
{
|
|
GSWMapEmptyMap(node->subMap);
|
|
node->subMap=NULL;
|
|
};
|
|
|
|
node->nextInBucket = map->base->freeNodes;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p node->nextInBucket node=%p",
|
|
map,map->base,node->nextInBucket);
|
|
*/
|
|
map->base->freeNodes = node;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p base=%p map->base->freeNodes node=%p",
|
|
map,map->base, map->base->freeNodes);
|
|
*/
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapFreeTable(GSWMapTable table)
|
|
{
|
|
table->nextTable = table->base->freeTables;
|
|
table->base->freeTables = table;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static GSWMapNode GSWMapNodeForKeyInBucket(GSWMapTable map,
|
|
GSWMapBucket bucket,
|
|
id key)
|
|
{
|
|
GSWMapNode node = bucket->firstNode;
|
|
|
|
while (node && ![node->key isEqual:key])
|
|
node = node->nextInBucket;
|
|
return node;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static GSWMapNode GSWMapNodeForKey(GSWMapTable map, id key)
|
|
{
|
|
GSWMapBucket bucket = NULL;
|
|
GSWMapNode node = NULL;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"map=%p key=%@",
|
|
map,key);
|
|
*/
|
|
if (map->nodeCount>0)
|
|
{
|
|
bucket = GSWMapBucketForKey(map, key);
|
|
node = GSWMapNodeForKeyInBucket(map, bucket, key);
|
|
};
|
|
return node;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
/** Enumerating **/
|
|
|
|
/* IMPORTANT WARNING: Enumerators have a wonderous property.
|
|
* Once a node has been returned by `GSWMapEnumeratorNextNode()', it may be
|
|
* removed from the map without effecting the rest of the current
|
|
* enumeration. */
|
|
|
|
/* EXTREMELY IMPORTANT WARNING: The purpose of this warning is point
|
|
* out that, various (i.e., many) functions currently depend on
|
|
* the behaviour outlined above. So be prepared for some serious
|
|
* breakage when you go fudging around with these things. */
|
|
|
|
/**
|
|
* Create an return an enumerator for the specified map.<br />
|
|
* You must call GSWMapEndEnumerator() when you have finished
|
|
* with the enumerator.<br />
|
|
* <strong>WARNING</strong> You should not alter a map while an enumeration
|
|
* is in progress. The results of doing so are reasonably unpredictable.
|
|
* <br />Remember, DON'T MESS WITH A MAP WHILE YOU'RE ENUMERATING IT.
|
|
*/
|
|
static GSWMapEnumerator_t GSWMapEnumeratorForMap(GSWMapTable map)
|
|
{
|
|
GSWMapEnumerator_t enumerator;
|
|
|
|
enumerator.map = map;
|
|
enumerator.node = 0;
|
|
enumerator.bucket = 0;
|
|
/*
|
|
* Locate next bucket and node to be returned.
|
|
*/
|
|
while (enumerator.bucket < map->bucketCount)
|
|
{
|
|
enumerator.node = map->buckets[enumerator.bucket].firstNode;
|
|
if (enumerator.node != 0)
|
|
{
|
|
break; // Got first node, and recorded its bucket.
|
|
}
|
|
enumerator.bucket++;
|
|
}
|
|
|
|
return enumerator;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
/**
|
|
* Tidies up after map enumeration ... effectively destroys the enumerator.
|
|
*/
|
|
static void GSWMapEndEnumerator(GSWMapEnumerator enumerator)
|
|
{
|
|
((_GSIE)enumerator)->map = 0;
|
|
((_GSIE)enumerator)->node = 0;
|
|
((_GSIE)enumerator)->bucket = 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
/**
|
|
* Returns the bucket from which the next node in the enumeration will
|
|
* come. Once the next node has been enumerated, you can use the
|
|
* bucket and node to remove the node from the map using the
|
|
* GSWMapRemoveNodeFromMap() function.
|
|
*/
|
|
static GSWMapBucket GSWMapEnumeratorBucket(GSWMapEnumerator enumerator)
|
|
{
|
|
if (((_GSIE)enumerator)->node != 0)
|
|
{
|
|
GSWMapTable map = ((_GSIE)enumerator)->map;
|
|
|
|
return &((map->buckets)[((_GSIE)enumerator)->bucket]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
/**
|
|
* Returns the next node in the map, or a nul pointer if at the end.
|
|
*/
|
|
static GSWMapNode GSWMapEnumeratorNextNode(GSWMapEnumerator enumerator)
|
|
{
|
|
GSWMapNode node = ((_GSIE)enumerator)->node;
|
|
|
|
if (node != 0)
|
|
{
|
|
GSWMapNode next = node->nextInBucket;
|
|
|
|
if (next == 0)
|
|
{
|
|
GSWMapTable map = ((_GSIE)enumerator)->map;
|
|
size_t bucketCount = map->bucketCount;
|
|
size_t bucket = ((_GSIE)enumerator)->bucket;
|
|
|
|
while (next == 0 && ++bucket < bucketCount)
|
|
{
|
|
next = (map->buckets[bucket]).firstNode;
|
|
}
|
|
((_GSIE)enumerator)->bucket = bucket;
|
|
}
|
|
((_GSIE)enumerator)->node = next;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static GSWMapNode GSWMapAddPair(GSWMapTable map, id key, id value)
|
|
{
|
|
GSWMapNode node = NULL;
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"key='%@' value=%p",
|
|
key,value);
|
|
*/
|
|
node = GSWMapNewNode(map, key, value);
|
|
/*
|
|
NSDebugFLLog(@"GSWMultiKeyDictionary",
|
|
@"key='%@' value=%p node=%p",
|
|
key,value,node);
|
|
*/
|
|
if (node)
|
|
{
|
|
GSWMapRightSizeMap(map, map->nodeCount);
|
|
GSWMapAddNodeToMap(map, node);
|
|
};
|
|
return node;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapRemoveKey(GSWMapTable map, id key)
|
|
{
|
|
GSWMapBucket bucket = GSWMapBucketForKey(map, key);
|
|
GSWMapNode node = NULL;
|
|
|
|
node = GSWMapNodeForKeyInBucket(map, bucket, key);
|
|
if (node)
|
|
{
|
|
if (node->subMap)
|
|
{
|
|
//Only release Value
|
|
DESTROY(node->value);
|
|
}
|
|
else
|
|
{
|
|
// Remove Node
|
|
GSWMapRemoveNodeFromMap(map, bucket, node);
|
|
GSWMapFreeNode(map, node);
|
|
};
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapMakeObjectsPerformSelector(GSWMapTable map,
|
|
SEL selector)
|
|
{
|
|
GSWMapEnumerator_t enumerator = GSWMapEnumeratorForMap(map);
|
|
GSWMapNode node = GSWMapEnumeratorNextNode(&enumerator);
|
|
|
|
while (node)
|
|
{
|
|
[node->value performSelector:selector];
|
|
if (node->subMap)
|
|
GSWMapMakeObjectsPerformSelector(node->subMap,selector);
|
|
node = GSWMapEnumeratorNextNode(&enumerator);
|
|
}
|
|
GSWMapEndEnumerator(&enumerator);
|
|
};
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapMakeObjectsPerformSelectorWithObject(GSWMapTable map,
|
|
SEL selector,
|
|
id object)
|
|
{
|
|
GSWMapEnumerator_t enumerator = GSWMapEnumeratorForMap(map);
|
|
GSWMapNode node = GSWMapEnumeratorNextNode(&enumerator);
|
|
|
|
while (node)
|
|
{
|
|
[node->value performSelector:selector
|
|
withObject:object];
|
|
if (node->subMap)
|
|
GSWMapMakeObjectsPerformSelectorWithObject(node->subMap,
|
|
selector,
|
|
object);
|
|
node = GSWMapEnumeratorNextNode(&enumerator);
|
|
}
|
|
GSWMapEndEnumerator(&enumerator);
|
|
};
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapMakeObjectsPerformSelectorWith2Objects(GSWMapTable map,
|
|
SEL selector,
|
|
id object1,
|
|
id object2)
|
|
{
|
|
GSWMapEnumerator_t enumerator = GSWMapEnumeratorForMap(map);
|
|
GSWMapNode node = GSWMapEnumeratorNextNode(&enumerator);
|
|
|
|
while (node)
|
|
{
|
|
[node->value performSelector:selector
|
|
withObject:object1
|
|
withObject:object2];
|
|
if (node->subMap)
|
|
GSWMapMakeObjectsPerformSelectorWith2Objects(node->subMap,
|
|
selector,
|
|
object1,
|
|
object2);
|
|
node = GSWMapEnumeratorNextNode(&enumerator);
|
|
}
|
|
GSWMapEndEnumerator(&enumerator);
|
|
};
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapAddAllValuesIntoArray(GSWMapTable map,
|
|
NSMutableArray* array)
|
|
{
|
|
GSWMapEnumerator_t enumerator = GSWMapEnumeratorForMap(map);
|
|
GSWMapNode node = GSWMapEnumeratorNextNode(&enumerator);
|
|
|
|
while (node)
|
|
{
|
|
if (node->value)
|
|
[array addObject:node->value];
|
|
if (node->subMap)
|
|
GSWMapAddAllValuesIntoArray(node->subMap,
|
|
array);
|
|
node = GSWMapEnumeratorNextNode(&enumerator);
|
|
}
|
|
GSWMapEndEnumerator(&enumerator);
|
|
};
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapCleanMap(GSWMapTable map)
|
|
{
|
|
if (map->nodeCount > 0)
|
|
{
|
|
GSWMapBucket bucket = map->buckets;
|
|
NSUInteger i;
|
|
GSWMapNode startNode = 0;
|
|
GSWMapNode prevNode = 0;
|
|
GSWMapNode node = NULL;
|
|
|
|
map->nodeCount = 0;
|
|
for (i = 0; i < map->bucketCount; i++)
|
|
{
|
|
node = bucket->firstNode;
|
|
if (prevNode)
|
|
{
|
|
prevNode->nextInBucket = node;
|
|
}
|
|
else
|
|
{
|
|
startNode = node;
|
|
}
|
|
while(node)
|
|
{
|
|
DESTROY(node->key);
|
|
DESTROY(node->value);
|
|
if (node->subMap)
|
|
{
|
|
GSWMapEmptyMap(node->subMap);
|
|
node->subMap=NULL;
|
|
};
|
|
|
|
prevNode = node;
|
|
node = node->nextInBucket;
|
|
}
|
|
bucket->nodeCount = 0;
|
|
bucket->firstNode = NULL;
|
|
bucket++;
|
|
}
|
|
|
|
prevNode->nextInBucket = map->base->freeNodes;
|
|
map->base->freeNodes = startNode;
|
|
}
|
|
}
|
|
|
|
//Really remove all (before deallocation)
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapEmptyMap(GSWMapTable map)
|
|
{
|
|
GSWMapCleanMap(map);
|
|
|
|
if (map->buckets)
|
|
{
|
|
NSZoneFree(map->base->zone, map->buckets);
|
|
map->buckets = NULL;
|
|
map->bucketCount = 0;
|
|
}
|
|
GSWMapFreeTable(map);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapCleanBase(GSWMapBase base)
|
|
{
|
|
GSWMapCleanMap(base->firstTable);
|
|
};
|
|
|
|
//Really remove all (before deallocation)
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapEmptyBase(GSWMapBase base)
|
|
{
|
|
NSUInteger i=0;
|
|
|
|
GSWMapEmptyMap(base->firstTable);
|
|
base->firstTable=NULL;
|
|
|
|
if (base->nodeChunks != 0)
|
|
{
|
|
for (i = 0; i < base->nodeChunkCount; i++)
|
|
{
|
|
NSZoneFree(base->zone, base->nodeChunks[i]);
|
|
}
|
|
base->nodeChunkCount = 0;
|
|
NSZoneFree(base->zone, base->nodeChunks);
|
|
base->nodeChunks = NULL;
|
|
}
|
|
base->freeNodes = NULL;
|
|
|
|
if (base->tableChunks != 0)
|
|
{
|
|
for (i = 0; i < base->tableChunkCount; i++)
|
|
{
|
|
NSZoneFree(base->zone, base->tableChunks[i]);
|
|
}
|
|
base->tableChunkCount = 0;
|
|
NSZoneFree(base->zone, base->tableChunks);
|
|
base->tableChunks = NULL;
|
|
}
|
|
base->freeTables = NULL;
|
|
base->zone = NULL;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
static void GSWMapBaseInitWithZoneAndCapacity(GSWMapBase base,
|
|
NSZone *zone,
|
|
size_t capacity)
|
|
{
|
|
base->zone = zone;
|
|
|
|
base->nodeChunks = NULL;
|
|
base->freeNodes = NULL;
|
|
base->nodeChunkCount = 0;
|
|
|
|
base->tableChunks = NULL;
|
|
base->freeTables = NULL;
|
|
base->tableChunkCount = 0;
|
|
|
|
base->firstTable=GSWMapNewTableWithCapacity(base,capacity);
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
@interface GSWMultiKeyDictionaryObjectEnumerator : NSEnumerator
|
|
{
|
|
GSWMultiKeyDictionary* _dictionary;
|
|
NSMutableArray* _objects;
|
|
}
|
|
@end
|
|
|
|
//==============================================================================
|
|
@interface GSWMultiKeyDictionary(Private)
|
|
+(id)dictionaryWithNodeSize:(NSUInteger)nodeSize;
|
|
-(id)initWithNodeSize:(NSUInteger)nodeSize;
|
|
-(id)initWithNodeSize:(NSUInteger)nodeSize
|
|
capacity:(NSUInteger)capacity;
|
|
|
|
-(void)setObject:(id)object
|
|
forKeys:(id*)keys
|
|
count:(unsigned)count
|
|
returnsNode:(GSWMapNode*)nodePtr;
|
|
|
|
-(GSWMapNode)nodeForKey:(id)key
|
|
andKeys:(va_list)nextKeys;
|
|
|
|
-(GSWMapNode)nodeForKeys:(id*)keys
|
|
count:(unsigned)count;
|
|
@end
|
|
|
|
//==============================================================================
|
|
@implementation GSWMultiKeyDictionary : NSObject
|
|
|
|
//------------------------------------------------------------------------------
|
|
+(id)dictionaryWithNodeSize:(NSUInteger)nodeSize
|
|
{
|
|
NSCAssert2(nodeSize>=sizeof(GSWMapNode_t),
|
|
@"Bad node size: %u < %u",
|
|
nodeSize,
|
|
sizeof(GSWMapNode_t));
|
|
return [[self alloc]initWithNodeSize:nodeSize];
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
+(id)dictionary
|
|
{
|
|
return [self dictionaryWithNodeSize:sizeof(GSWMapNode_t)];
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)initWithNodeSize:(NSUInteger)nodeSize
|
|
{
|
|
NSCAssert2(nodeSize>=sizeof(GSWMapNode_t),
|
|
@"Bad node size: %u < %u",
|
|
nodeSize,
|
|
sizeof(GSWMapNode_t));
|
|
self=[self initWithNodeSize:nodeSize
|
|
capacity:DEFAULT_DICTIONARY_CAPACITY];
|
|
return self;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)init
|
|
{
|
|
self=[self initWithNodeSize:sizeof(GSWMapNode_t)];
|
|
return self;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)initWithNodeSize:(NSUInteger)nodeSize
|
|
capacity:(NSUInteger)capacity
|
|
{
|
|
NSCAssert2(nodeSize>=sizeof(GSWMapNode_t),
|
|
@"Bad node size: %u < %u",
|
|
nodeSize,
|
|
sizeof(GSWMapNode_t));
|
|
if ((self=[super init]))
|
|
{
|
|
_mapBase = (GSWMapBase)NSZoneMalloc([self zone],sizeof(GSWMapBase_t));
|
|
((GSWMapBase)_mapBase)->nodeSize=nodeSize;
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"self=%p class=%@ base=%p nodeSize=%u sizeof(GSWMapNode_t)=%u",
|
|
self,[self class],_mapBase,nodeSize,sizeof(GSWMapNode_t));
|
|
*/
|
|
GSWMapBaseInitWithZoneAndCapacity((GSWMapBase)_mapBase, [self zone],
|
|
capacity);
|
|
};
|
|
return self;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)initWithCapacity:(NSUInteger)capacity
|
|
{
|
|
[self initWithNodeSize:sizeof(GSWMapNode_t)
|
|
capacity:capacity];
|
|
return self;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
- (void) dealloc
|
|
{
|
|
if (_mapBase)
|
|
{
|
|
GSWMapEmptyBase((GSWMapBase)_mapBase);
|
|
NSZoneFree([self zone],_mapBase);
|
|
};
|
|
[super dealloc];
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(NSString*)description
|
|
{
|
|
NSString* descr=nil;
|
|
//TODO
|
|
descr=[NSString stringWithFormat:@"<%@ %p allValues=%@>",
|
|
NSStringFromClass([self class]),
|
|
(void*)self, [self allValues]];
|
|
return descr;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
forKeys:(id)key,...
|
|
{
|
|
GS_USEIDLIST(key,[self setObject:object
|
|
forKeys:__objects
|
|
count: __count
|
|
returnsNode:NULL]);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
forKeysArray:(NSArray*)keysArray
|
|
{
|
|
int keysCount=[keysArray count];
|
|
if (keysCount==0)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Tried to add empty keys array to multi-keys dictionary. Object: %@",
|
|
object];
|
|
}
|
|
else if (object == nil)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Tried to add nil value to multi-keys dictionary"];
|
|
}
|
|
else
|
|
{
|
|
id keys[keysCount];
|
|
[keysArray getObjects:keys];
|
|
|
|
[self setObject:object
|
|
forKeys:keys
|
|
count:keysCount
|
|
returnsNode:NULL];
|
|
};
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
forKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
[self setObject:object
|
|
forKeys:keys
|
|
count:count
|
|
returnsNode:NULL];
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
forKeys:(id*)keys
|
|
count:(unsigned)count
|
|
returnsNode:(GSWMapNode*)nodePtr
|
|
{
|
|
if (count==0)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Tried to add object to multi-keys dictionary with no key. Object: %@",
|
|
object];
|
|
}
|
|
else if (*keys == nil)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Tried to add nil key to multi-keys dictionary. Object: %@",
|
|
object];
|
|
}
|
|
else if (object == nil)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"Tried to add nil value to multi-keys dictionary. First key: %@",
|
|
*keys];
|
|
}
|
|
else
|
|
{
|
|
int i=0;
|
|
GSWMapTable currentMap=((GSWMapBase)_mapBase)->firstTable;
|
|
|
|
for(i=0;i<count;i++)
|
|
{
|
|
id key=keys[i];
|
|
BOOL isNextKey=(i<(count-1) && keys[i+1]);
|
|
GSWMapNode node = GSWMapNodeForKey(currentMap,key);
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"key='%@' node=%p node->value=%p isNextKey=%d",
|
|
key,node,(node ? node->value : NULL),isNextKey);
|
|
*/
|
|
if (node)
|
|
{
|
|
if (isNextKey)
|
|
{
|
|
if (!node->subMap)
|
|
node->subMap=GSWMapNewTableWithCapacity(currentMap->base,DEFAULT_DICTIONARY_CAPACITY);
|
|
currentMap=node->subMap;
|
|
}
|
|
else
|
|
{
|
|
ASSIGN(node->value,object);
|
|
if (nodePtr)
|
|
*nodePtr=node;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
if (isNextKey)
|
|
{
|
|
node=GSWMapAddPair(currentMap,key,nil);
|
|
node->subMap=GSWMapNewTableWithCapacity(currentMap->base,DEFAULT_DICTIONARY_CAPACITY);
|
|
currentMap=node->subMap;
|
|
}
|
|
else
|
|
{
|
|
GSWMapNode aNode=GSWMapAddPair(currentMap,key,object);
|
|
if (nodePtr)
|
|
*nodePtr=aNode;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)objectForKeys:(id)key,...
|
|
{
|
|
id object=nil;
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"self=%p class=%@ key=%@",
|
|
self,[self class],key);
|
|
*/
|
|
GS_USEIDLIST(key,object = [self objectForKeys:__objects
|
|
count: __count]);
|
|
return object;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)objectForKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
id object=nil;
|
|
|
|
GSWMapNode node=[self nodeForKeys:keys
|
|
count:count];
|
|
|
|
if (node)
|
|
object=node->value;
|
|
|
|
return object;
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(id)objectForKeysArray:(NSArray*)keysArray
|
|
{
|
|
id object=nil;
|
|
int keysCount=[keysArray count];
|
|
if (keysCount>0)
|
|
{
|
|
id keys[keysCount];
|
|
[keysArray getObjects:keys];
|
|
object=[self objectForKeys:keys
|
|
count:keysCount];
|
|
};
|
|
return object;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(GSWMapNode)nodeForKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
int i=0;
|
|
GSWMapNode finalNode=NULL;
|
|
GSWMapTable currentMap=((GSWMapBase)_mapBase)->firstTable;
|
|
for(i=0;i<count;i++)
|
|
{
|
|
id key=keys[i];
|
|
BOOL isNextKey=(i<(count-1) && keys[i+1]);
|
|
GSWMapNode node = NULL;
|
|
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"key[%d]='%@' isNextKey=%d",
|
|
i,key,isNextKey);
|
|
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"key[%d]=%p isNextKey=%d",
|
|
i,key,isNextKey);
|
|
*/
|
|
node = GSWMapNodeForKey(currentMap,key);
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"key[%d]='%@' node=%p node->value=%p isNextKey=%d",
|
|
i,key,node,(node ? node->value : NULL),isNextKey);
|
|
*/
|
|
if (node)
|
|
{
|
|
if (isNextKey)
|
|
{
|
|
currentMap=node->subMap;
|
|
if (!currentMap)
|
|
break;
|
|
}
|
|
else
|
|
finalNode=node;
|
|
}
|
|
else
|
|
break;
|
|
};
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",@"Object node %sfound: %p",
|
|
(finalNode ? "" : "not "),finalNode);
|
|
*/
|
|
return finalNode;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)removeAllObjects
|
|
{
|
|
GSWMapCleanBase((GSWMapBase)_mapBase);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)removeObjectForKeys:(id)key,...
|
|
{
|
|
GS_USEIDLIST(key,[self removeObjectForKeys:__objects
|
|
count: __count]);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)removeObjectForKeysArray:(NSArray*)keysArray
|
|
{
|
|
int keysCount=[keysArray count];
|
|
if (keysCount>0)
|
|
{
|
|
id keys[keysCount];
|
|
[keysArray getObjects:keys];
|
|
[self removeObjectForKeys:keys
|
|
count:keysCount];
|
|
};
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)removeObjectForKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
int i=0;
|
|
GSWMapTable currentMap=((GSWMapBase)_mapBase)->firstTable;
|
|
for(i=0;i<count;i++)
|
|
{
|
|
id key=keys[i];
|
|
BOOL isNextKey=(i<(count-1) && keys[i+1]);
|
|
GSWMapNode node = GSWMapNodeForKey(currentMap,key);
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"key='%@' node=%p node->value=%p isNextKey=%d",
|
|
key,node,(node ? node->value : NULL),isNextKey);
|
|
*/
|
|
if (node)
|
|
{
|
|
if (isNextKey)
|
|
{
|
|
currentMap=node->subMap;
|
|
if (!currentMap)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
GSWMapRemoveKey(currentMap,key);
|
|
};
|
|
}
|
|
else
|
|
break;
|
|
};
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)removeAllSubObjectsForKeys:(id)key,...
|
|
{
|
|
GS_USEIDLIST(key,[self removeAllSubObjectsForKeys:__objects
|
|
count: __count]);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)removeAllSubObjectsForKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
GSWMapNode node=[self nodeForKeys:keys
|
|
count:count];
|
|
|
|
if (node)
|
|
{
|
|
if (node->subMap)
|
|
GSWMapCleanMap(node->subMap);
|
|
[self removeObjectForKeys:keys
|
|
count:count];
|
|
};
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)removeAllSubObjectsForKeysArray:(NSArray*)keysArray
|
|
{
|
|
int keysCount=[keysArray count];
|
|
if (keysCount>0)
|
|
{
|
|
id keys[keysCount];
|
|
[keysArray getObjects:keys];
|
|
[self removeAllSubObjectsForKeys:keys
|
|
count:keysCount];
|
|
};
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)makeObjectsPerformSelector:(SEL)selector
|
|
{
|
|
GSWMapMakeObjectsPerformSelector(((GSWMapBase)_mapBase)->firstTable,selector);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)makeObjectsPerformSelector:(SEL)selector
|
|
withObject:(id)object
|
|
{
|
|
GSWMapMakeObjectsPerformSelectorWithObject(((GSWMapBase)_mapBase)->firstTable,selector,object);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)makeObjectsPerformSelector:(SEL)selector
|
|
withObject:(id)object1
|
|
withObject:(id)object2
|
|
{
|
|
GSWMapMakeObjectsPerformSelectorWith2Objects(((GSWMapBase)_mapBase)->firstTable,
|
|
selector,
|
|
object1,
|
|
object2);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
- (NSEnumerator*) objectEnumerator
|
|
{
|
|
return AUTORELEASE([[GSWMultiKeyDictionaryObjectEnumerator
|
|
allocWithZone:NSDefaultMallocZone()]
|
|
initWithDictionary: (NSDictionary*)self]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(NSArray*)allValues
|
|
{
|
|
NSMutableArray* objects=(NSMutableArray*)[NSMutableArray array];
|
|
GSWMapAddAllValuesIntoArray(((GSWMapBase)_mapBase)->firstTable,
|
|
objects);
|
|
return objects;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(NSArray*)allSubValuesForKeys:(id)key,...
|
|
{
|
|
NSArray* values=nil;
|
|
/*
|
|
NSDebugMLLog(@"GSWMultiKeyDictionary",
|
|
@"self=%p class=%@ key=%@",
|
|
self,[self class],key);
|
|
*/
|
|
GS_USEIDLIST(key,values = [self allSubValuesForKeys:__objects
|
|
count: __count]);
|
|
return values;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(NSArray*)allSubValuesForKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
NSMutableArray* objects=nil;
|
|
GSWMapNode node=[self nodeForKeys:keys
|
|
count:count];
|
|
if (node)
|
|
{
|
|
NSMutableArray* objects=(NSMutableArray*)[NSMutableArray array];
|
|
if (node->value)
|
|
[objects addObject:node->value];
|
|
if (node->subMap)
|
|
GSWMapAddAllValuesIntoArray(node->subMap,
|
|
objects);
|
|
};
|
|
return objects;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(NSArray*)allSubValuesForKeysArray:(NSArray*)keysArray
|
|
{
|
|
NSArray* objects=nil;
|
|
int keysCount=[keysArray count];
|
|
if (keysCount>0)
|
|
{
|
|
id keys[keysCount];
|
|
[keysArray getObjects:keys];
|
|
objects=[self allSubValuesForKeys:keys
|
|
count:keysCount];
|
|
};
|
|
return objects;
|
|
}
|
|
@end
|
|
|
|
//==============================================================================
|
|
@implementation GSWMultiKeyDictionaryObjectEnumerator
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)initWithDictionary:(GSWMultiKeyDictionary*)d
|
|
{
|
|
if ((self=[super init]))
|
|
{
|
|
ASSIGN(_dictionary,((GSWMultiKeyDictionary*)d));
|
|
ASSIGN(_objects,[_dictionary allValues]);
|
|
};
|
|
return self;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)dealloc
|
|
{
|
|
DESTROY(_dictionary);
|
|
DESTROY(_objects);
|
|
[super dealloc];
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(id)nextObject
|
|
{
|
|
id object=nil;
|
|
if ([_objects count]>0)
|
|
{
|
|
object=[_objects lastObject];
|
|
AUTORELEASE(RETAIN(object));
|
|
[_objects removeLastObject];
|
|
};
|
|
return object;
|
|
}
|
|
|
|
@end
|
|
|
|
//==============================================================================
|
|
@implementation GSWCache
|
|
|
|
//------------------------------------------------------------------------------
|
|
static BOOL isNodeExpired(GSWCacheMapNode node)
|
|
{
|
|
if ((node->flags & GSWCacheFlags_expiresOnFirstAccess)==GSWCacheFlags_expiresOnFirstAccess)
|
|
{
|
|
return (node->cacheDuration>0 && (GSWTimeNow()-node->firstAccessTS)>node->cacheDuration);
|
|
}
|
|
else
|
|
{
|
|
return (node->cacheDuration>0 && (GSWTimeNow()-node->lastAccessTS)>node->cacheDuration);
|
|
};
|
|
};
|
|
|
|
//--------------------------------------------------------------------
|
|
static void removeExpiredNodes(GSWMapTable map)
|
|
{
|
|
GSWMapEnumerator_t enumerator = GSWMapEnumeratorForMap(map);
|
|
GSWMapBucket bucket=GSWMapEnumeratorBucket(&enumerator);
|
|
GSWCacheMapNode node = (GSWCacheMapNode)GSWMapEnumeratorNextNode(&enumerator);
|
|
while (node)
|
|
{
|
|
if (isNodeExpired(node))
|
|
{
|
|
if (node->subMap && node->subMap->nodeCount>0)
|
|
{
|
|
//Only release Value
|
|
DESTROY(node->value);
|
|
}
|
|
else
|
|
{
|
|
// Remove Node
|
|
GSWMapRemoveNodeFromMap(map,bucket, (GSWMapNode)node);
|
|
GSWMapFreeNode(map, (GSWMapNode)node);
|
|
};
|
|
};
|
|
if (node->subMap)
|
|
{
|
|
removeExpiredNodes(node->subMap);
|
|
if (node->subMap->nodeCount==0)
|
|
{
|
|
GSWMapEmptyMap(node->subMap);
|
|
node->subMap=NULL;
|
|
};
|
|
};
|
|
bucket=GSWMapEnumeratorBucket(&enumerator);
|
|
node = (GSWCacheMapNode)GSWMapEnumeratorNextNode(&enumerator);
|
|
}
|
|
GSWMapEndEnumerator(&enumerator);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
- (id)init
|
|
{
|
|
if ((self = [self initWithDefaultDuration:-1 //never expires
|
|
defaultFlags:0]))
|
|
{
|
|
};
|
|
return self;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
- (id)initWithDefaultDuration:(NSTimeInterval)defaultDuration
|
|
defaultFlags:(unsigned int)defaultFlags
|
|
{
|
|
if ((self = [self initWithNodeSize:sizeof(GSWCacheMapNode_t)]))
|
|
{
|
|
_defaultDuration=defaultDuration;
|
|
_defaultFlags=defaultFlags;
|
|
};
|
|
return self;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
- (void) dealloc
|
|
{
|
|
[super dealloc];
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(NSString*)description
|
|
{
|
|
return [super description];
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
+(GSWCache*)cache
|
|
{
|
|
return [[[GSWCache alloc] init] autorelease];
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
+(GSWCache*)cacheWithDefaultDuration:(NSTimeInterval)defaultDuration
|
|
defaultFlags:(unsigned int)defaultFlags
|
|
{
|
|
return [[[GSWCache alloc] initWithDefaultDuration:defaultDuration
|
|
defaultFlags:defaultFlags] autorelease];
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)deleteExpiredEntries
|
|
{
|
|
removeExpiredNodes(((GSWMapBase)_mapBase)->firstTable);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(GSWMapNode)nodeForKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
GSWCacheMapNode node=(GSWCacheMapNode)[super nodeForKeys:keys
|
|
count:count];
|
|
if (node)
|
|
{
|
|
if (isNodeExpired(node))
|
|
{
|
|
NSDebugMLog(@"Node for object %p EXPIRED",node->value);
|
|
[self removeObjectForKeys:keys
|
|
count:count];
|
|
}
|
|
else
|
|
{
|
|
node->lastAccessTS=GSWTimeNow();
|
|
};
|
|
};
|
|
return (GSWMapNode)node;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
forKeys:(id*)keys
|
|
count:(unsigned)count
|
|
returnsNode:(GSWMapNode*)nodePtr
|
|
{
|
|
GSWCacheMapNode aNode=NULL;
|
|
[super setObject:object
|
|
forKeys:keys
|
|
count:count
|
|
returnsNode:(GSWMapNode*)&aNode];
|
|
if (aNode)
|
|
{
|
|
aNode->firstAccessTS=GSWTimeNow();
|
|
aNode->lastAccessTS=aNode->firstAccessTS;
|
|
aNode->cacheDuration=_defaultDuration;
|
|
aNode->flags=_defaultFlags;
|
|
};
|
|
if (nodePtr)
|
|
*nodePtr=(GSWMapNode)aNode;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
withDuration:(NSTimeInterval)duration
|
|
forKey:(id)key
|
|
{
|
|
[self setObject:object
|
|
withDuration:duration
|
|
forKeys:&key
|
|
count:1];
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
withDuration:(NSTimeInterval)duration
|
|
forKeys:(id*)keys
|
|
count:(unsigned)count
|
|
{
|
|
GSWCacheMapNode aNode=NULL;
|
|
[self setObject:object
|
|
forKeys:keys
|
|
count:count
|
|
returnsNode:(GSWMapNode*)&aNode];
|
|
if (aNode)
|
|
{
|
|
aNode->cacheDuration=duration;
|
|
};
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
-(void)setObject:(id)object
|
|
withDuration:(NSTimeInterval)duration
|
|
forKeys:(id)key,...
|
|
{
|
|
GS_USEIDLIST(key,[self setObject:object
|
|
withDuration:duration
|
|
forKeys:__objects
|
|
count: __count]);
|
|
};
|
|
|
|
|
|
@end
|
|
|