114#ifndef OOCACHE_PERFORM_INTEGRITY_CHECKS
115#define OOCACHE_PERFORM_INTEGRITY_CHECKS 0
120@protocol OOCacheComparable <NSObject, NSCopying>
121- (NSComparisonResult) compare:(
id<OOCacheComparable>)other;
145 id<OOCacheComparable>
key;
177#if OOCACHE_PERFORM_INTEGRITY_CHECKS
178static NSString *
const kOOLogCacheIntegrityCheck =
@"dataCache.integrityCheck";
179static void CacheCheckIntegrity(
OOCacheImpl *cache, NSString *context);
181#define CHECK_INTEGRITY(context) CacheCheckIntegrity(cache, (context))
183#define CHECK_INTEGRITY(context) do {} while (0)
187@interface OOCache (Private)
189- (void)loadFromArray:(NSArray *)inArray;
208 return [
NSString stringWithFormat:@"<%@ %p>{\"%@\", %u elements, prune threshold=%u, auto-prune=%s dirty=%s}", [
self class],
self, [
self name], CacheGetCount(cache), pruneThreshold, autoPrune ? "yes" : "no", dirty ? "yes" : "no"];
218- (id)initWithPList:(
id)pList
228 if (
cache == NULL) OK = NO;
260- (id)objectForKey:(
id)key
271 return [[
result retain] autorelease];
275- (void)setObject:inObject forKey:(
id)key
289- (void)removeObjectForKey:(
id)key
299- (void)setPruneThreshold:(
unsigned)threshold
316- (void)setAutoPrune:(BOOL)flag
318 BOOL
prune = (flag != NO);
336 unsigned desiredCount;
345 pruneCount =
count - desiredCount;
347 NSString *logKey = [
NSString stringWithFormat:@"dataCache.prune.%@", CacheGetName(cache)];
375- (void)setName:(NSString *)name
389@implementation OOCache (Private)
391- (void)loadFromArray:(NSArray *)array
393 NSEnumerator *entryEnum =
nil;
394 NSDictionary *entry =
nil;
398 if (array ==
nil)
return;
400 for (entryEnum = [array objectEnumerator]; (entry = [
entryEnum nextObject]); )
402 if ([entry isKindOfClass:[NSDictionary
class]])
404 key = [
entry objectForKey:kSerializedEntryKeyKey];
405 value = [
entry objectForKey:kSerializedEntryKeyValue];
406 if ([key isKindOfClass:[NSString
class]] && value !=
nil)
424#if OOCACHE_PERFORM_INTEGRITY_CHECKS
425static NSString *CacheNodeGetDescription(
OOCacheNode *node);
431#if OOCACHE_PERFORM_INTEGRITY_CHECKS
439#if OOCACHE_PERFORM_INTEGRITY_CHECKS
440static void AgeListCheckIntegrity(
OOCacheImpl *cache, NSString *context);
454 if (cache == NULL)
return;
457 [cache->name autorelease];
466 if (cache == NULL || key ==
nil || value ==
nil)
return NO;
495 cache->
root = newRoot;
510 if (cache == NULL || cache->
oldest == NULL)
return NO;
522 if (cache == NULL || key == NULL)
return nil;
537 NSMutableArray *result =
nil;
539 if (cache == NULL || cache->
count == 0)
return nil;
541 result = [NSMutableArray arrayWithCapacity:cache->count];
543 for (node = cache->
youngest; node != NULL; node = node->
older)
545 [result addObject:node->value];
554 NSMutableArray *result =
nil;
556 if (cache == NULL || cache->
count == 0)
return nil;
558 result = [NSMutableArray arrayWithCapacity:cache->count];
560 for (node = cache->
oldest; node != NULL; node = node->
younger)
562 [result addObject:[NSDictionary dictionaryWithObjectsAndKeys:node->key, kSerializedEntryKeyKey, node->value, kSerializedEntryKeyValue, nil]];
576 [cache->name autorelease];
577 cache->
name = [name copy];
586#if OOCACHE_PERFORM_INTEGRITY_CHECKS
588static void CacheCheckIntegrity(
OOCacheImpl *cache, NSString *context)
592 cache->
root = TreeCheckIntegrity(cache, cache->
root, NULL, context);
594 trueCount = TreeCountNodes(cache->
root);
596 else if (cache->
count != trueCount)
598 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): count is %u, but should be %u.", context, cache->
name, cache->
count, trueCount);
599 cache->
count = trueCount;
602 AgeListCheckIntegrity(cache, context);
615 if (key ==
nil || value ==
nil)
return NULL;
617 result = calloc(
sizeof *result, 1);
620 result->
key = [key copy];
621 result->
value = [value retain];
633 if (node == NULL)
return;
655 if (node == NULL)
return nil;
664 if (node == NULL)
return;
666 id tmp = node->
value;
667 node->
value = [value retain];
672#if OOCACHE_PERFORM_INTEGRITY_CHECKS
674static NSString *CacheNodeGetDescription(
OOCacheNode *node)
676 if (node == NULL)
return @"0[null]";
678 return [NSString stringWithFormat:@"%p[\"%@\"]", node, node->key];
694 NSComparisonResult order;
695 OOCacheNode N = { .leftChild = NULL, .rightChild = NULL };
696 OOCacheNode *node = NULL, *temp = NULL, *l = &N, *r = &N;
699 if (root == NULL || *root == NULL || key ==
nil)
return NULL;
708 OOLog(
@"node.error",
@"%@",
@"node is NULL");
710 else if (node->
key == NULL)
712 OOLog(
@"node.error",
@"%@",
@"node->key is NULL");
715 order = [key compare:node->key];
716 if (order == NSOrderedAscending)
720 if ([key compare:node->
leftChild->
key] == NSOrderedAscending)
734 else if (order == NSOrderedDescending)
738 if ([key compare:node->
rightChild->
key] == NSOrderedDescending)
748 l->rightChild = node;
767 return exact ? node : NULL;
775 NSComparisonResult order;
777 if (cache == NULL || key ==
nil || value ==
nil)
return NULL;
779 if (cache->
root == NULL)
795 closest = cache->
root;
799 order = [key compare:closest->key];
801 if (order == NSOrderedAscending)
810 else if (order == NSOrderedDescending)
822 OOLog(
@"dataCache.inconsistency",
@"%s() internal inconsistency for cache \"%@\
", insertion failed.", __PRETTY_FUNCTION__, cache->
name);
833#if OOCACHE_PERFORM_INTEGRITY_CHECKS
836 if (node == NULL)
return 0;
837 return 1 + TreeCountNodes(node->leftChild) + TreeCountNodes(node->rightChild);
844 NSComparisonResult order;
847 if (node == NULL)
return NULL;
849 if (OK && node->
key ==
nil)
851 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): node \"%@\" has nil key; deleting subtree.", context, cache->
name, CacheNodeGetDescription(node));
857 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): node \"%@\" has nil value, deleting.", context, cache->
name, CacheNodeGetDescription(node));
862 order = [node->key compare:node->leftChild->key];
863 if (order != NSOrderedDescending)
865 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): node %@'s left child %@ is not correctly ordered. Deleting subtree.", context, cache->
name, CacheNodeGetDescription(node), CacheNodeGetDescription(node->
leftChild));
877 order = [node->key compare:node->rightChild->key];
878 if (order != NSOrderedAscending)
880 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): node \"%@\"'s right child \"%@\" is not correctly ordered. Deleting subtree.", context, cache->
name, CacheNodeGetDescription(node), CacheNodeGetDescription(node->
rightChild));
907 if (cache == NULL || node == NULL)
return;
923 if (node == NULL)
return;
934 if (younger != NULL) younger->
older = older;
935 if (older != NULL) older->
younger = younger;
939#if OOCACHE_PERFORM_INTEGRITY_CHECKS
941static void AgeListCheckIntegrity(
OOCacheImpl *cache, NSString *context)
944 unsigned seenCount = 0;
946 if (cache == NULL || context == NULL)
return;
954 if (next == NULL)
break;
956 if (next->younger != node)
958 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): node \"%@\" has invalid older link (should be \"%@\", is \"%@\"); repairing.", context, cache->
name, CacheNodeGetDescription(next), CacheNodeGetDescription(node), CacheNodeGetDescription(next->older));
964 if (seenCount != cache->
count)
967 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): expected %u nodes, found %u. Cannot repair; clearing cache.", context, cache->
name, cache->
count, seenCount);
978 if (next == NULL)
break;
980 if (node->
key != NULL)
982 OOLog(kOOLogCacheIntegrityCheck,
@"Key is: %@",node->
key);
986 OOLog(kOOLogCacheIntegrityCheck,
@"Key is: NULL");
989 if (node->
value != NULL)
991 OOLog(kOOLogCacheIntegrityCheck,
@"Value is: %@",node->
value);
995 OOLog(kOOLogCacheIntegrityCheck,
@"Value is: NULL");
1011 if (node != cache->
oldest)
1013 OOLog(kOOLogCacheIntegrityCheck,
@"Integrity check (%@ for \"%@\
"): oldest pointer in cache is wrong (should be \"%@\", is \"%@\"); repairing.", context, cache->
name, CacheNodeGetDescription(node), CacheNodeGetDescription(cache->
oldest));
1030@implementation OOCache (DebugGraphViz)
1032- (void) appendNodesFromSubTree:(
OOCacheNode *)subTree toString:(NSMutableString *)ioString
1034 [ioString appendFormat:@"\tn%p [label=\"<f0> | <f1> %@ | <f2>\"];\n", subTree, EscapedGraphVizString([subTree->key description])];
1038 [
self appendNodesFromSubTree:subTree->leftChild toString:ioString];
1039 [ioString appendFormat:@"\tn%p:f0 -> n%p:f1;\n", subTree, subTree->leftChild];
1043 [
self appendNodesFromSubTree:subTree->rightChild toString:ioString];
1044 [ioString appendFormat:@"\tn%p:f2 -> n%p:f1;\n", subTree, subTree->rightChild];
1049- (NSString *) generateGraphVizBodyWithRootNamed:(NSString *)rootName
1051 NSMutableString *result =
nil;
1053 result = [NSMutableString string];
1056 [result appendFormat:@"\t%@ [label=\"Cache \\\"%@\\\"\" shape=box];\n"
1057 "\tnode [shape=record];\n\t\n", rootName, EscapedGraphVizString([
self name])];
1059 if (cache == NULL)
return result;
1062 [
self appendNodesFromSubTree:cache->root toString:result];
1065 [result appendString:@"\tedge [color=black constraint=true];\n"];
1066 [result appendFormat:@"\t%@ -> n%p:f1;\n", rootName, cache->root];
1071 [result appendString:@"\t\n\t// Age-sorted list in blue\n\tedge [color=blue constraint=false];\n"];
1075 [result appendFormat:@"\tn%p:f2 -> n%p:f0;\n", node, node->younger];
1084- (NSString *) generateGraphViz
1086 NSMutableString *result =
nil;
1088 result = [NSMutableString string];
1091 [result appendFormat:
1092 @"// OOCache dump\n\n"
1095 "\tgraph [charset=\"UTF-8\", label=\"OOCache \"%@\" debug dump\", labelloc=t, labeljust=l];\n\t\n", [
self name]];
1097 [result appendString:[
self generateGraphVizBodyWithRootNamed:@"cache"]];
1099 [result appendString:@"}\n"];
1105- (void) writeGraphVizToURL:(NSURL *)url
1107 NSString *graphViz =
nil;
1110 graphViz = [
self generateGraphViz];
1111 data = [graphViz dataUsingEncoding:NSUTF8StringEncoding];
1115 [data writeToURL:url atomically:YES];
1120- (void) writeGraphVizToPath:(NSString *)path
1122 [
self writeGraphVizToURL:[NSURL fileURLWithPath:path]];
@ kOOCacheDefaultPruneThreshold
@ kOOCacheMinimumPruneThreshold
static void AgeListMakeYoungest(OOCacheImpl *cache, OOCacheNode *node)
static NSString *const kSerializedEntryKeyKey
static NSString * CacheGetName(OOCacheImpl *cache)
static void CacheNodeSetValue(OOCacheNode *node, id value)
static OOCacheNode * CacheNodeAllocate(id< OOCacheComparable > key, id value)
static void CacheFree(OOCacheImpl *cache)
static NSArray * CacheArrayOfContentsByAge(OOCacheImpl *cache)
static unsigned CacheGetCount(OOCacheImpl *cache)
#define CHECK_INTEGRITY(context)
struct OOCacheImpl OOCacheImpl
static OOCacheNode * TreeInsert(OOCacheImpl *cache, id< OOCacheComparable > key, id value)
static NSArray * CacheArrayOfNodesByAge(OOCacheImpl *cache)
static BOOL CacheRemoveOldest(OOCacheImpl *cache, NSString *logKey)
static OOCacheImpl * CacheAllocate(void)
struct OOCacheNode OOCacheNode
static id CacheNodeGetValue(OOCacheNode *node)
static id CacheRetrieve(OOCacheImpl *cache, id key)
static void AgeListRemove(OOCacheImpl *cache, OOCacheNode *node)
static void CacheNodeFree(OOCacheImpl *cache, OOCacheNode *node)
static BOOL CacheRemove(OOCacheImpl *cache, id key)
static NSString *const kSerializedEntryKeyValue
static void CacheSetName(OOCacheImpl *cache, NSString *name)
static BOOL CacheInsert(OOCacheImpl *cache, id key, id value)
static OOCacheNode * TreeSplay(OOCacheNode **root, id< OOCacheComparable > key)
#define OOLogOutdentIf(class)
#define OOLog(class, format,...)
#define OOLogIndentIf(class)
struct OOCacheImpl * cache
id initWithPList:(id pList)
void setObject:forKey:(id value,[forKey] id key)
void loadFromArray:(NSArray *inArray)
id< OOCacheComparable > key