/** Interface to concrete implementation of NSDictionary Copyright (C) 1998 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald Date: September 1998 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #import "config.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSEnumerator.h" #import "Foundation/NSAutoreleasePool.h" #import "Foundation/NSString.h" #import "Foundation/NSException.h" #import "Foundation/NSPortCoder.h" #import "Foundation/NSDebug.h" // For private method _decodeArrayOfObjectsForKey: #import "Foundation/NSKeyedArchiver.h" #import "GNUstepBase/GSObjCRuntime.h" /* * The 'Fastmap' stuff provides an inline implementation of a mapping * table - for maximum performance. */ #define GSI_MAP_KTYPES GSUNION_OBJ #define GSI_MAP_VTYPES GSUNION_OBJ #define GSI_MAP_HASH(M, X) [X.obj hash] #define GSI_MAP_EQUAL(M, X,Y) [X.obj isEqual: Y.obj] #define GSI_MAP_RETAIN_KEY(M, X) ((X).obj) = \ [((id)(X).obj) copyWithZone: map->zone] #if GS_WITH_GC #include static GC_descr nodeDesc; // Type descriptor for map node. #define GSI_MAP_NODES(M, X) \ (GSIMapNode)GC_calloc_explicitly_typed(X, sizeof(GSIMapNode_t), nodeDesc) #endif #include "GNUstepBase/GSIMap.h" @interface GSDictionary : NSDictionary { @public GSIMapTable_t map; } @end @interface GSMutableDictionary : NSMutableDictionary { @public GSIMapTable_t map; } @end @interface GSDictionaryKeyEnumerator : NSEnumerator { GSDictionary *dictionary; GSIMapEnumerator_t enumerator; } @end @interface GSDictionaryObjectEnumerator : GSDictionaryKeyEnumerator @end @implementation GSDictionary static SEL nxtSel; static SEL objSel; + (void) initialize { if (self == [GSDictionary class]) { #if GS_WITH_GC /* We create a typed memory descriptor for map nodes. * The pointers to the key and value need to be scanned. */ GC_word w[GC_BITMAP_SIZE(GSIMapNode_t)] = {0}; GC_set_bit(w, GC_WORD_OFFSET(GSIMapNode_t, key)); GC_set_bit(w, GC_WORD_OFFSET(GSIMapNode_t, value)); nodeDesc = GC_make_descriptor(w, GC_WORD_LEN(GSIMapNode_t)); #endif nxtSel = @selector(nextObject); objSel = @selector(objectForKey:); } } - (id) copyWithZone: (NSZone*)zone { return RETAIN(self); } - (unsigned) count { return map.nodeCount; } - (void) dealloc { GSIMapEmptyMap(&map); [super dealloc]; } - (void) encodeWithCoder: (NSCoder*)aCoder { if ([aCoder allowsKeyedCoding]) { [super encodeWithCoder: aCoder]; } else { unsigned count = map.nodeCount; SEL sel = @selector(encodeObject:); IMP imp = [aCoder methodForSelector: sel]; GSIMapEnumerator_t enumerator = GSIMapEnumeratorForMap(&map); GSIMapNode node = GSIMapEnumeratorNextNode(&enumerator); [aCoder encodeValueOfObjCType: @encode(unsigned) at: &count]; while (node != 0) { (*imp)(aCoder, sel, node->key.obj); (*imp)(aCoder, sel, node->value.obj); node = GSIMapEnumeratorNextNode(&enumerator); } GSIMapEndEnumerator(&enumerator); } } - (unsigned) hash { return map.nodeCount; } - (id) init { return [self initWithObjects: 0 forKeys: 0 count: 0]; } - (id) initWithCoder: (NSCoder*)aCoder { if ([aCoder allowsKeyedCoding]) { self = [super initWithCoder: aCoder]; } else { unsigned count; id key; id value; SEL sel = @selector(decodeValueOfObjCType:at:); IMP imp = [aCoder methodForSelector: sel]; const char *type = @encode(id); [aCoder decodeValueOfObjCType: @encode(unsigned) at: &count]; GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), count); while (count-- > 0) { (*imp)(aCoder, sel, type, &key); (*imp)(aCoder, sel, type, &value); GSIMapAddPairNoRetain(&map, (GSIMapKey)key, (GSIMapVal)value); } } return self; } /* Designated initialiser */ - (id) initWithObjects: (id*)objs forKeys: (id*)keys count: (unsigned)c { unsigned int i; GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), c); for (i = 0; i < c; i++) { GSIMapNode node; if (keys[i] == nil) { IF_NO_GC(AUTORELEASE(self)); [NSException raise: NSInvalidArgumentException format: @"Tried to init dictionary with nil key"]; } if (objs[i] == nil) { IF_NO_GC(AUTORELEASE(self)); [NSException raise: NSInvalidArgumentException format: @"Tried to init dictionary with nil value"]; } node = GSIMapNodeForKey(&map, (GSIMapKey)keys[i]); if (node) { IF_NO_GC(RETAIN(objs[i])); RELEASE(node->value.obj); node->value.obj = objs[i]; } else { GSIMapAddPair(&map, (GSIMapKey)keys[i], (GSIMapVal)objs[i]); } } return self; } /* * This avoids using the designated initialiser for performance reasons. */ - (id) initWithDictionary: (NSDictionary*)other copyItems: (BOOL)shouldCopy { NSZone *z = GSObjCZone(self); unsigned c = [other count]; GSIMapInitWithZoneAndCapacity(&map, z, c); if (c > 0) { NSEnumerator *e = [other keyEnumerator]; IMP nxtObj = [e methodForSelector: nxtSel]; IMP otherObj = [other methodForSelector: objSel]; BOOL isProxy = [other isProxy]; unsigned i; for (i = 0; i < c; i++) { GSIMapNode node; id k; id o; if (isProxy == YES) { k = [e nextObject]; o = [other objectForKey: k]; } else { k = (*nxtObj)(e, nxtSel); o = (*otherObj)(other, objSel, k); } k = [k copyWithZone: z]; if (k == nil) { IF_NO_GC(AUTORELEASE(self)); [NSException raise: NSInvalidArgumentException format: @"Tried to init dictionary with nil key"]; } if (shouldCopy) { o = [o copyWithZone: z]; } else { o = RETAIN(o); } if (o == nil) { IF_NO_GC(AUTORELEASE(self)); [NSException raise: NSInvalidArgumentException format: @"Tried to init dictionary with nil value"]; } node = GSIMapNodeForKey(&map, (GSIMapKey)k); if (node) { RELEASE(node->value.obj); node->value.obj = o; } else { GSIMapAddPairNoRetain(&map, (GSIMapKey)k, (GSIMapVal)o); } } } return self; } - (BOOL) isEqualToDictionary: (NSDictionary*)other { unsigned count; if (other == self) { return YES; } count = map.nodeCount; if (count == [other count]) { if (count > 0) { GSIMapEnumerator_t enumerator; GSIMapNode node; IMP otherObj = [other methodForSelector: objSel]; enumerator = GSIMapEnumeratorForMap(&map); while ((node = GSIMapEnumeratorNextNode(&enumerator)) != 0) { id o1 = node->value.obj; id o2 = (*otherObj)(other, objSel, node->key.obj); if (o1 != o2 && [o1 isEqual: o2] == NO) { GSIMapEndEnumerator(&enumerator); return NO; } } GSIMapEndEnumerator(&enumerator); } return YES; } return NO; } - (NSEnumerator*) keyEnumerator { return AUTORELEASE([[GSDictionaryKeyEnumerator allocWithZone: NSDefaultMallocZone()] initWithDictionary: self]); } - (NSEnumerator*) objectEnumerator { return AUTORELEASE([[GSDictionaryObjectEnumerator allocWithZone: NSDefaultMallocZone()] initWithDictionary: self]); } - (id) objectForKey: aKey { if (aKey != nil) { GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)aKey); if (node) { return node->value.obj; } } return nil; } @end @implementation GSMutableDictionary + (void) initialize { if (self == [GSMutableDictionary class]) { GSObjCAddClassBehavior(self, [GSDictionary class]); } } - (id) copyWithZone: (NSZone*)zone { NSDictionary *copy = [GSDictionary allocWithZone: zone]; return [copy initWithDictionary: self copyItems: NO]; } - (id) init { return [self initWithCapacity: 0]; } /* Designated initialiser */ - (id) initWithCapacity: (unsigned)cap { GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), cap); return self; } - (id) makeImmutableCopyOnFail: (BOOL)force { #ifndef NDEBUG GSDebugAllocationRemove(isa, self); #endif isa = [GSDictionary class]; #ifndef NDEBUG GSDebugAllocationAdd(isa, self); #endif return self; } - (void) setObject: (id)anObject forKey: (id)aKey { GSIMapNode node; if (aKey == nil) { NSException *e; e = [NSException exceptionWithName: NSInvalidArgumentException reason: @"Tried to add nil key to dictionary" userInfo: self]; [e raise]; } if (anObject == nil) { NSException *e; NSString *s; s = [NSString stringWithFormat: @"Tried to add nil value for key '%@' to dictionary", aKey]; e = [NSException exceptionWithName: NSInvalidArgumentException reason: s userInfo: self]; [e raise]; } node = GSIMapNodeForKey(&map, (GSIMapKey)aKey); if (node) { IF_NO_GC(RETAIN(anObject)); RELEASE(node->value.obj); node->value.obj = anObject; } else { GSIMapAddPair(&map, (GSIMapKey)aKey, (GSIMapVal)anObject); } } - (void) removeAllObjects { GSIMapCleanMap(&map); } - (void) removeObjectForKey: (id)aKey { if (aKey == nil) { NSWarnMLog(@"attempt to remove nil key from dictionary %@", self); return; } GSIMapRemoveKey(&map, (GSIMapKey)aKey); } @end @implementation GSDictionaryKeyEnumerator - (id) initWithDictionary: (NSDictionary*)d { [super init]; dictionary = (GSDictionary*)RETAIN(d); enumerator = GSIMapEnumeratorForMap(&dictionary->map); return self; } - (id) nextObject { GSIMapNode node = GSIMapEnumeratorNextNode(&enumerator); if (node == 0) { return nil; } return node->key.obj; } - (void) dealloc { GSIMapEndEnumerator(&enumerator); RELEASE(dictionary); [super dealloc]; } @end @implementation GSDictionaryObjectEnumerator - (id) nextObject { GSIMapNode node = GSIMapEnumeratorNextNode(&enumerator); if (node == 0) { return nil; } return node->value.obj; } @end @interface NSGDictionary : NSDictionary @end @implementation NSGDictionary - (id) initWithCoder: (NSCoder*)aCoder { NSLog(@"Warning - decoding archive containing obsolete %@ object - please delete/replace this archive", NSStringFromClass([self class])); RELEASE(self); self = (id)NSAllocateObject([GSDictionary class], 0, NSDefaultMallocZone()); self = [self initWithCoder: aCoder]; return self; } @end @interface NSGMutableDictionary : NSMutableDictionary @end @implementation NSGMutableDictionary - (id) initWithCoder: (NSCoder*)aCoder { NSLog(@"Warning - decoding archive containing obsolete %@ object - please delete/replace this archive", NSStringFromClass([self class])); RELEASE(self); self = (id)NSAllocateObject([GSMutableDictionary class], 0, NSDefaultMallocZone()); self = [self initWithCoder: aCoder]; return self; } @end