diff --git a/Foundation/CPMapTable.j b/Foundation/CPMapTable.j new file mode 100644 index 000000000..9399c347c --- /dev/null +++ b/Foundation/CPMapTable.j @@ -0,0 +1,125 @@ +@import "CPObject.j" +@import "CPEnumerator.j" +@import "CPDictionary.j" +@import "CPArray.j" + +/*! + @class CPMapTable + @ingroup foundation + A mutable collection modeled after NSDictionary that allows for arbitrary + objects or values to be used as keys. This implementation uses the native + ECMAScript 6 Map object for its underlying storage. + */ +@implementation CPMapTable : CPObject +{ + // The underlying ES6 Map to store the key-value pairs. + id _map; +} + +/*! + Initializes a new, empty CPMapTable. + */ +- (id)init +{ + if (self = [super init]) + { + _map = new Map(); + } + + return self; +} + +#pragma mark - +#pragma mark Accessing Content + +/*! + Returns the value associated with a given key. + @param aKey The key for which to return the corresponding value. + @return The value associated with aKey, or nil if no value is associated with aKey. + */ +- (id)objectForKey:(id)aKey +{ + var value = _map.get(aKey); + return value === undefined ? nil : value; +} + +/*! + Returns an enumerator object that lets you access each key in the map table. + @return A CPEnumerator object for the keys in the map table. + */ +- (CPEnumerator)keyEnumerator +{ + return [[..._map.keys()] objectEnumerator]; +} + +/*! + Returns an enumerator object that lets you access each value in the map table. + @return A CPEnumerator object for the values in the map table. + */ +- (CPEnumerator)objectEnumerator +{ + return [[..._map.values()] objectEnumerator]; +} + +/*! + Returns the number of key-value pairs in the map table. + @return The number of entries in the map table. + */ +- (unsigned int)count +{ + return _map.size; +} + +#pragma mark - +#pragma mark Manipulating Content + +/*! + Adds a given key-value pair to the map table. + If aKey already exists in the map table, anObject takes its place. + @param anObject The value for aKey. + @param aKey The key for anObject. + */ +- (void)setObject:(id)anObject forKey:(id)aKey +{ + _map.set(aKey, anObject); +} + +/*! + Removes a given key and its associated value from the map table. + @param aKey The key to remove. + */ +- (void)removeObjectForKey:(id)aKey +{ + _map.delete(aKey); +} + +/*! + Empties the map table of its entries. + */ +- (void)removeAllObjects +{ + _map.clear(); +} + +#pragma mark - +#pragma mark Creating a Dictionary Representation + +/*! + Returns a dictionary representation of the map table. + Note: This will only work correctly if all keys are strings. + + @return A CPDictionary containing the entries of the map table. + */ +- (CPDictionary)dictionaryRepresentation +{ + var dictionary = [CPDictionary dictionary]; + + for (var [key, value] of _map.entries()) + { + [dictionary setObject:value forKey:key]; + } + + return dictionary; +} + +@end diff --git a/Tests/Foundation/CPMapTableTest.j b/Tests/Foundation/CPMapTableTest.j new file mode 100644 index 000000000..d5c7864d8 --- /dev/null +++ b/Tests/Foundation/CPMapTableTest.j @@ -0,0 +1,153 @@ +@import +@import +@import +@import +@import + +@implementation CPMapTableTest : OJTestCase +{ + CPMapTable map_table; + id objectKey; +} + +- (void)setUp +{ + map_table = [[CPMapTable alloc] init]; + objectKey = [[CPObject alloc] init]; +} + +- (void)testInit +{ + [self assert:[map_table count] equals:0]; +} + +- (void)testSetObjectForKey +{ + [map_table setObject:@"value1" forKey:@"key1"]; + [self assert:[map_table count] equals:1]; + [self assert:[map_table objectForKey:@"key1"] equals:@"value1"]; + + [map_table setObject:@"value2" forKey:123]; + [self assert:[map_table count] equals:2]; + [self assert:[map_table objectForKey:123] equals:@"value2"]; + + [map_table setObject:@"value3" forKey:objectKey]; + [self assert:[map_table count] equals:3]; + [self assert:[map_table objectForKey:objectKey] equals:@"value3"]; + + // Test overriding a value + [map_table setObject:@"newValue" forKey:@"key1"]; + [self assert:[map_table count] equals:3]; + [self assert:[map_table objectForKey:@"key1"] equals:@"newValue"]; +} + +- (void)testObjectForKey +{ + [self assertNull:[map_table objectForKey:@"nonExistentKey"]]; + + [map_table setObject:@"value1" forKey:@"key1"]; + [self assert:[map_table objectForKey:@"key1"] equals:@"value1"]; +} + +- (void)testCount +{ + [self assert:[map_table count] equals:0]; + + [map_table setObject:@"value1" forKey:@"key1"]; + [self assert:[map_table count] equals:1]; + + [map_table setObject:@"value2" forKey:@"key2"]; + [self assert:[map_table count] equals:2]; + + [map_table removeObjectForKey:@"key1"]; + [self assert:[map_table count] equals:1]; +} + +- (void)testRemoveObjectForKey +{ + [map_table setObject:@"value1" forKey:@"key1"]; + [map_table setObject:@"value2" forKey:123]; + + [map_table removeObjectForKey:@"key1"]; + [self assert:[map_table count] equals:1]; + [self assertNull:[map_table objectForKey:@"key1"]]; + [self assert:[map_table objectForKey:123] equals:@"value2"]; + + [map_table removeObjectForKey:123]; + [self assert:[map_table count] equals:0]; + [self assertNull:[map_table objectForKey:123]]; +} + +- (void)testRemoveAllObjects +{ + [map_table setObject:@"value1" forKey:@"key1"]; + [map_table setObject:@"value2" forKey:@"key2"]; + + [map_table removeAllObjects]; + [self assert:[map_table count] equals:0]; + [self assertNull:[map_table objectForKey:@"key1"]]; + [self assertNull:[map_table objectForKey:@"key2"]]; +} + +- (void)testKeyEnumeratorOnEmptyMap +{ + [self assert:[map_table count] equals:0 message:@"Pre-condition failed: map should be empty."]; + [self assertNull:[[map_table keyEnumerator] nextObject]]; +} + +- (void)testKeyEnumeratorOnPopulatedMap +{ + [map_table setObject:@"value1" forKey:@"key1"]; + [map_table setObject:@"value2" forKey:123]; + [map_table setObject:@"value3" forKey:objectKey]; + + var foundKeys = [CPMutableArray array], + enumerator = [map_table keyEnumerator], + aKey; + + while (aKey = [enumerator nextObject]) + [foundKeys addObject:aKey]; + + [self assert:[foundKeys count] equals:3]; + [self assertTrue:[foundKeys containsObject:@"key1"]]; + [self assertTrue:[foundKeys containsObject:123]]; + [self assertTrue:[foundKeys containsObject:objectKey]]; +} + +- (void)testObjectEnumeratorOnEmptyMap +{ + [self assert:[map_table count] equals:0 message:@"Pre-condition failed: map should be empty."]; + [self assertNull:[[map_table objectEnumerator] nextObject]]; +} + +- (void)testObjectEnumeratorOnPopulatedMap +{ + [map_table setObject:@"value1" forKey:@"key1"]; + [map_table setObject:@"value2" forKey:123]; + [map_table setObject:@"value3" forKey:objectKey]; + + var foundValues = [CPMutableArray array], + enumerator = [map_table objectEnumerator], + aValue; + + while (aValue = [enumerator nextObject]) + [foundValues addObject:aValue]; + + [self assert:[foundValues count] equals:3]; + [self assertTrue:[foundValues containsObject:@"value1"]]; + [self assertTrue:[foundValues containsObject:@"value2"]]; + [self assertTrue:[foundValues containsObject:@"value3"]]; +} + +- (void)testDictionaryRepresentation +{ + [map_table setObject:@"value1" forKey:@"key1"]; + [map_table setObject:@"value2" forKey:@"key2"]; + + var dictionary = [map_table dictionaryRepresentation]; + [self assert:[dictionary count] equals:2]; + [self assert:[dictionary objectForKey:@"key1"] equals:@"value1"]; + [self assert:[dictionary objectForKey:@"key2"] equals:@"value2"]; +} + +@end