Permalink
Browse files

Collection KVC fixes, nextObject correctness

- Moved common CPArray/CPSet collection KVC operators to _CPCollectionKVCOperators.
- Implemented KVC operator dispatch using Objective-J.
- Fixed infinite loop with empty  collection in @min, @max and @sum operators.
- Correctly return valueForUndefinedKey when necessary.
- valueForUndefinedKey reason uses raw description for consistency, class' overridden description may not helpful at all.
- Don't create a forwarder for @ operators with property paths.
- Fixed CPSet -valueForKeyPath to correctly deal with nil/undefined/empty values.
- Added tests for collection  KVC operators.
- enumerator -nextObject should always compare against nil for clarity, correctness, and consistency.
  • Loading branch information...
aparajita committed Apr 4, 2012
1 parent 029e45d commit e2ead5625610507440e684e41f0d706c82690186
View
@@ -104,10 +104,10 @@ var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
- (void)setFlashVars:(CPDictionary)aDictionary
{
var varString = @"",
- enumerator = [aDictionary keyEnumerator];
+ enumerator = [aDictionary keyEnumerator],
+ key;
- var key;
- while (key = [enumerator nextObject])
+ while ((key = [enumerator nextObject]) !== nil)
varString = [varString stringByAppendingFormat:@"&%@=%@", key, [aDictionary objectForKey:key]];
if (!_params)
@@ -146,7 +146,7 @@ var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
var enumerator = [_params keyEnumerator],
key;
- while (_DOMObjectElement && (key = [enumerator nextObject]))
+ while (_DOMObjectElement && (key = [enumerator nextObject]) !== nil)
{
var param = document.createElement(@"param");
param.name = key;
@@ -178,7 +178,7 @@ var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
paramEnumerator = [_params keyEnumerator],
key;
- while (key = [paramEnumerator nextObject])
+ while ((key = [paramEnumerator nextObject]) !== nil)
paramString = [paramString stringByAppendingFormat:@"<param name='%@' value='%@' />", key, [_params objectForKey:key]];
_DOMObjectElement = document.createElement(@"object");
@@ -473,7 +473,7 @@ var STICKY_TIME_INTERVAL = 500,
var iter = [selectorNames objectEnumerator],
obj;
- while (obj = [iter nextObject])
+ while ((obj = [iter nextObject]) !== nil)
{
var aSelector = CPSelectorFromString(obj);
@@ -504,7 +504,7 @@ var STICKY_TIME_INTERVAL = 500,
var iter = [[menu itemArray] objectEnumerator],
obj;
- while (obj = [iter nextObject])
+ while ((obj = [iter nextObject]) !== nil)
{
if ([obj isHidden] || ![obj isEnabled])
continue;
View
@@ -2689,7 +2689,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
// Remove every main descriptor equivalents (normally only one)
- while ((descriptor = [e nextObject]) != nil)
+ while ((descriptor = [e nextObject]) !== nil)
{
if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
[outdatedDescriptors addObject:descriptor];
View
@@ -255,7 +255,7 @@ var CPThemesByName = { },
attributeNames = [attributes keyEnumerator],
objectThemeClass = [anObject themeClass];
- while (attributeName = [attributeNames nextObject])
+ while ((attributeName = [attributeNames nextObject]) !== nil)
[self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
}
View
@@ -534,7 +534,7 @@ var CPViewFlags = { },
var addedSubview = nil,
addedSubviewEnumerator = [addedSubviews objectEnumerator];
- while (addedSubview = [addedSubviewEnumerator nextObject])
+ while ((addedSubview = [addedSubviewEnumerator nextObject]) !== nil)
[self addSubview:addedSubview];
// If the order is fine, no need to reorder.
View
@@ -137,7 +137,7 @@ var CPCibObjectDataKey = @"CPCibObjectDataKey";
var key = nil,
keyEnumerator = [replacementClasses keyEnumerator];
- while (key = [keyEnumerator nextObject])
+ while ((key = [keyEnumerator nextObject]) !== nil)
[unarchiver setClass:[replacementClasses objectForKey:key] forClassName:key];
}
@@ -106,7 +106,7 @@
var object = nil,
objectEnumerator = [_visibleWindows objectEnumerator];
- while (object = [objectEnumerator nextObject])
+ while ((object = [objectEnumerator nextObject]) !== nil)
[_replacementObjects[[object UID]] makeKeyAndOrderFront:self];
}
@@ -1625,7 +1625,7 @@ function CPWindowObjectList()
platformWindow = nil,
windowObjects = [];
- while (platformWindow = [platformWindowEnumerator nextObject])
+ while ((platformWindow = [platformWindowEnumerator nextObject]) !== nil)
{
var levels = platformWindow._windowLevels,
layers = platformWindow._windowLayers,
@@ -49,7 +49,7 @@ function findCibClassDependencies(cibPath) {
var key = nil,
keyEnumerator = [replacementClasses keyEnumerator];
- while (key = [keyEnumerator nextObject])
+ while ((key = [keyEnumerator nextObject]) !== nil)
[unarchiver setClass:[replacementClasses objectForKey:key] forClassName:key];
}
View
@@ -22,6 +22,7 @@
@import "CPArray.j"
@import "CPNull.j"
+@import "_CPCollectionKVCOperators.j"
@implementation CPObject (CPArrayKVO)
@@ -240,6 +241,7 @@
{
if (_objectsAtIndexes)
return _objectsAtIndexes(_proxyObject, _objectsAtIndexesSEL, theIndexes);
+
if (_objectAtIndex)
{
var index = CPNotFound,
@@ -413,9 +415,9 @@
@end
-//KVC on CPArray objects act on each item of the array, rather than on the array itself
+// KVC on CPArray objects act on each item of the array, rather than on the array itself
-@implementation CPArray (KeyValueCoding)
+@implementation CPArray (CPKeyValueCoding)
- (id)valueForKey:(CPString)aKey
{
@@ -424,10 +426,10 @@
if (aKey.indexOf(".") !== -1)
[CPException raise:CPInvalidArgumentException reason:"called valueForKey: on an array with a complex key ("+aKey+"). use valueForKeyPath:"];
- if (aKey == "@count")
+ if (aKey === "@count")
return length;
- return nil;
+ return [self valueForUndefinedKey:aKey];
}
else
{
@@ -451,6 +453,9 @@
- (id)valueForKeyPath:(CPString)aKeyPath
{
+ if (!aKeyPath)
+ [self valueForUndefinedKey:@"<empty path>"];
+
if (aKeyPath.charAt(0) === "@")
{
var dotIndex = aKeyPath.indexOf("."),
@@ -465,10 +470,7 @@
else
operator = aKeyPath.substring(1);
- if (kvoOperators[operator])
- return kvoOperators[operator](self, _cmd, parameter);
-
- return nil;
+ return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter];
}
else
{
@@ -495,7 +497,7 @@
var enumerator = [self objectEnumerator],
object;
- while (object = [enumerator nextObject])
+ while ((object = [enumerator nextObject]) !== nil)
[object setValue:aValue forKey:aKey];
}
@@ -504,86 +506,12 @@
var enumerator = [self objectEnumerator],
object;
- while (object = [enumerator nextObject])
+ while ((object = [enumerator nextObject]) !== nil)
[object setValue:aValue forKeyPath:aKeyPath];
}
@end
-var kvoOperators = [];
-
-// HACK: prevent these from becoming globals. workaround for obj-j "function foo(){}" behavior
-var avgOperator,
- maxOperator,
- minOperator,
- countOperator,
- sumOperator;
-
-kvoOperators["avg"] = function avgOperator(self, _cmd, param)
-{
- var objects = [self valueForKeyPath:param],
- length = [objects count],
- index = length,
- average = 0.0;
-
- if (!length)
- return 0;
-
- while (index--)
- average += [objects[index] doubleValue];
-
- return average / length;
-}
-
-kvoOperators["max"] = function maxOperator(self, _cmd, param)
-{
- var objects = [self valueForKeyPath:param],
- index = [objects count] - 1,
- max = [objects lastObject];
-
- while (index--)
- {
- var item = objects[index];
- if ([max compare:item] < 0)
- max = item;
- }
-
- return max;
-}
-
-kvoOperators["min"] = function minOperator(self, _cmd, param)
-{
- var objects = [self valueForKeyPath:param],
- index = [objects count] - 1,
- min = [objects lastObject];
-
- while (index--)
- {
- var item = objects[index];
- if ([min compare:item] > 0)
- min = item;
- }
-
- return min;
-}
-
-kvoOperators["count"] = function countOperator(self, _cmd, param)
-{
- return [self count];
-}
-
-kvoOperators["sum"] = function sumOperator(self, _cmd, param)
-{
- var objects = [self valueForKeyPath:param],
- index = [objects count],
- sum = 0.0;
-
- while (index--)
- sum += [objects[index] doubleValue];
-
- return sum;
-}
-
@implementation CPArray (KeyValueObserving)
- (void)addObserver:(id)anObserver toObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
@@ -610,20 +538,4 @@ kvoOperators["sum"] = function sumOperator(self, _cmd, param)
}
}
-- (void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
-{
- if ([isa instanceMethodForSelector:_cmd] === [CPArray instanceMethodForSelector:_cmd])
- [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"];
- else
- [super addObserver:observer forKeyPath:aKeyPath options:options context:context];
-}
-
-- (void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath
-{
- if ([isa instanceMethodForSelector:_cmd] === [CPArray instanceMethodForSelector:_cmd])
- [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"];
- else
- [super removeObserver:observer forKeyPath:aKeyPath];
-}
-
@end
@@ -241,7 +241,7 @@ var CPCharacterSetInvertedKey = @"CPCharacterSetInvertedKey";
var enu = [_ranges objectEnumerator],
range;
- while (range = [enu nextObject])
+ while ((range = [enu nextObject]) !== nil)
{
if (CPLocationInRange(c, range))
return !_inverted;
@@ -50,7 +50,7 @@
{
var key = [_keyEnumerator nextObject];
- if (!key)
+ if (key === nil)
return nil;
return [_dictionary objectForKey:key];
@@ -157,7 +157,7 @@ var CPObjectAccessorsForClassKey = @"$CPObjectAccessorsForClassKey",
- (id)valueForUndefinedKey:(CPString)aKey
{
[[CPException exceptionWithName:CPUndefinedKeyException
- reason:[self description] + " is not key value coding-compliant for the key " + aKey
+ reason:[self _objectDescription] + " is not key value coding-compliant for the key " + aKey
userInfo:[CPDictionary dictionaryWithObjects:[self, aKey] forKeys:[CPTargetObjectUserInfoKey, CPUnknownUserInfoKey]]] raise];
}
@@ -235,7 +235,7 @@ var CPObjectAccessorsForClassKey = @"$CPObjectAccessorsForClassKey",
key,
keyEnumerator = [keyedValues keyEnumerator];
- while (key = [keyEnumerator nextObject])
+ while ((key = [keyEnumerator nextObject]) !== nil)
{
value = [keyedValues objectForKey: key];
@@ -250,10 +250,15 @@ var CPObjectAccessorsForClassKey = @"$CPObjectAccessorsForClassKey",
- (void)setValue:(id)aValue forUndefinedKey:(CPString)aKey
{
[[CPException exceptionWithName:CPUndefinedKeyException
- reason:[self description] + " is not key value coding-compliant for the key " + aKey
+ reason:[self _objectDescription] + " is not key value coding-compliant for the key " + aKey
userInfo:[CPDictionary dictionaryWithObjects:[self, aKey] forKeys:[CPTargetObjectUserInfoKey, CPUnknownUserInfoKey]]] raise];
}
+- (CPString)_objectDescription
+{
+ return "<" + [self className] + " 0x" + [CPString stringWithHash:[self UID]] + ">";
+}
+
@end
@implementation CPDictionary (CPKeyValueCoding)
@@ -664,7 +664,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
var forwarder = nil;
- if (aPath.indexOf('.') != CPNotFound)
+ if (aPath.indexOf('.') !== CPNotFound && aPath.charAt(0) !== '@')
forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
else
[self _replaceModifiersForKey:aPath];
@@ -1092,7 +1092,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
var dotIndex = aKeyPath.indexOf('.');
- if (dotIndex == CPNotFound)
+ if (dotIndex === CPNotFound)
[CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: "+aKeyPath];
_firstPart = aKeyPath.substring(0, dotIndex);
@@ -398,7 +398,7 @@ var _CPKeyedArchiverStringClass = Nil,
keys = [aDictionary keyEnumerator],
references = [CPDictionary dictionary];
- while (key = [keys nextObject])
+ while ((key = [keys nextObject]) !== nil)
[references setObject:_CPKeyedArchiverEncodeObject(self, [aDictionary objectForKey:key], NO) forKey:key];
[_plistObject setObject:references forKey:aKey];
Oops, something went wrong.

0 comments on commit e2ead56

Please sign in to comment.