mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-11-09 08:28:45 +00:00
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.
This commit is contained in:
1 parent
029e45d027
commit
e2ead56256
30 files changed
+265
-272
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
+1
-1
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+1
-1
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
+12
-100
@@ -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];
|
||||
|
||||
@@ -103,7 +103,7 @@ var CPNotificationDefaultCenter = nil;
|
||||
var name = nil,
|
||||
names = [_namedRegistries keyEnumerator];
|
||||
|
||||
while (name = [names nextObject])
|
||||
while ((name = [names nextObject]) !== nil)
|
||||
[[_namedRegistries objectForKey:name] removeObserver:anObserver object:nil];
|
||||
|
||||
[_unnamedRegistry removeObserver:anObserver object:nil];
|
||||
@@ -122,7 +122,7 @@ var CPNotificationDefaultCenter = nil;
|
||||
var name = nil,
|
||||
names = [_namedRegistries keyEnumerator];
|
||||
|
||||
while (name = [names nextObject])
|
||||
while ((name = [names nextObject]) !== nil)
|
||||
[[_namedRegistries objectForKey:name] removeObserver:anObserver object:anObject];
|
||||
|
||||
[_unnamedRegistry removeObserver:anObserver object:anObject];
|
||||
@@ -225,7 +225,7 @@ var _CPNotificationCenterPostNotification = function(/* CPNotificationCenter */
|
||||
keys = [_objectObservers keyEnumerator];
|
||||
|
||||
// Iterate through every set of observers
|
||||
while (key = [keys nextObject])
|
||||
while ((key = [keys nextObject]) !== nil)
|
||||
{
|
||||
var observers = [_objectObservers objectForKey:key],
|
||||
observer = nil,
|
||||
|
||||
@@ -512,9 +512,10 @@ var CPComparisonPredicateModifier,
|
||||
result = (_modifier == CPAllPredicateModifier),
|
||||
value;
|
||||
|
||||
while (value = [e nextObject])
|
||||
while ((value = [e nextObject]) !== nil)
|
||||
{
|
||||
var eval = [self _evaluateValue:value rightValue:rightValue];
|
||||
|
||||
if (eval != result)
|
||||
return eval;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
collection = [_aggregate objectEnumerator],
|
||||
exp;
|
||||
|
||||
while (exp = [collection nextObject])
|
||||
while ((exp = [collection nextObject]) !== nil)
|
||||
{
|
||||
var eval = [exp expressionValueWithObject:object context:context];
|
||||
[eval_array addObject:eval];
|
||||
|
||||
+26
-114
@@ -23,6 +23,7 @@
|
||||
@import "CPException.j"
|
||||
@import "CPObject.j"
|
||||
@import "CPSet.j"
|
||||
@import "_CPCollectionKVCOperators.j"
|
||||
|
||||
@implementation CPObject (CPSetKVO)
|
||||
|
||||
@@ -218,7 +219,8 @@
|
||||
{
|
||||
var object,
|
||||
objectEnumerator = [objects objectEnumerator];
|
||||
while (object = [objectEnumerator nextObject])
|
||||
|
||||
while ((object = [objectEnumerator nextObject]) !== nil)
|
||||
_add(_proxyObject, _addSEL, object);
|
||||
}
|
||||
else
|
||||
@@ -237,7 +239,8 @@
|
||||
{
|
||||
var object,
|
||||
objectEnumerator = [aSet objectEnumerator];
|
||||
while (object = [objectEnumerator nextObject])
|
||||
|
||||
while ((object = [objectEnumerator nextObject]) !== nil)
|
||||
_add(_proxyObject, _addSEL, object);
|
||||
}
|
||||
else
|
||||
@@ -273,7 +276,8 @@
|
||||
{
|
||||
var object,
|
||||
objectEnumerator = [aSet objectEnumerator];
|
||||
while (object = [objectEnumerator nextObject])
|
||||
|
||||
while ((object = [objectEnumerator nextObject]) !== nil)
|
||||
_remove(_proxyObject, _removeSEL, object);
|
||||
}
|
||||
else
|
||||
@@ -295,7 +299,8 @@
|
||||
{
|
||||
var object,
|
||||
objectEnumerator = [objects objectEnumerator];
|
||||
while (object = [objectEnumerator nextObject])
|
||||
|
||||
while ((object = [objectEnumerator nextObject]) !== nil)
|
||||
_remove(_proxyObject, _removeSEL, object);
|
||||
}
|
||||
else
|
||||
@@ -317,7 +322,8 @@
|
||||
{
|
||||
var object,
|
||||
objectEnumerator = [[[self _representedObject] copy] objectEnumerator];
|
||||
while (object = [objectEnumerator nextObject])
|
||||
|
||||
while ((object = [objectEnumerator nextObject]) !== nil)
|
||||
_remove(_proxyObject, _removeSEL, object);
|
||||
}
|
||||
else
|
||||
@@ -381,7 +387,10 @@
|
||||
|
||||
- (id)valueForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (aKeyPath.indexOf("@") === 0)
|
||||
if (!aKeyPath)
|
||||
[self valueForUndefinedKey:@"<empty path>"];
|
||||
|
||||
if (aKeyPath.charAt(0) === "@")
|
||||
{
|
||||
var dotIndex = aKeyPath.indexOf("."),
|
||||
operator,
|
||||
@@ -395,10 +404,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
|
||||
{
|
||||
@@ -406,12 +412,17 @@
|
||||
containedObject,
|
||||
containedObjectValue,
|
||||
containedObjectEnumerator = [self objectEnumerator];
|
||||
while (containedObject = [containedObjectEnumerator nextObject])
|
||||
|
||||
while ((containedObject = [containedObjectEnumerator nextObject]) !== nil)
|
||||
{
|
||||
containedObjectValue = [containedObject valueForKeyPath:aKeyPath];
|
||||
if (containedObjectValue)
|
||||
[valuesForKeySet addObject:containedObjectValue];
|
||||
|
||||
if (containedObjectValue === nil || containedObjectValue === undefined)
|
||||
containedObjectValue = [CPNull null];
|
||||
|
||||
[valuesForKeySet addObject:containedObjectValue];
|
||||
}
|
||||
|
||||
return valuesForKeySet;
|
||||
}
|
||||
}
|
||||
@@ -420,108 +431,9 @@
|
||||
{
|
||||
var containedObject,
|
||||
containedObjectEnumerator = [self objectEnumerator];
|
||||
while (containedObject = [containedObjectEnumerator nextObject])
|
||||
|
||||
while ((containedObject = [containedObjectEnumerator nextObject]) !== nil)
|
||||
[containedObject setValue:aValue forKey:aKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var kvoOperators = [];
|
||||
|
||||
// FIXME: this is not DRY because the implementation in CPArray+KVO is merely the same!
|
||||
// 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)
|
||||
{
|
||||
//CPSet returns a CPSet - to obtain an array call allObjects
|
||||
var objects = [[self valueForKeyPath:param] allObjects],
|
||||
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)
|
||||
{
|
||||
//CPSet returns a CPSet - to obtain an array call allObjects
|
||||
var objects = [[self valueForKeyPath:param] allObjects],
|
||||
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)
|
||||
{
|
||||
//CPSet returns a CPSet - to obtain an array call allObjects
|
||||
var objects = [[self valueForKeyPath:param] allObjects],
|
||||
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)
|
||||
{
|
||||
//CPSet returns a CPSet - to obtain an array call allObjects
|
||||
var objects = [[self valueForKeyPath:param] allObjects],
|
||||
index = [objects count],
|
||||
sum = 0.0;
|
||||
|
||||
while (index--)
|
||||
sum += [objects[index] doubleValue];
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
@implementation CPSet (CPKeyValueObserving)
|
||||
|
||||
- (void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
|
||||
{
|
||||
if ([isa instanceMethodForSelector:_cmd] === [CPSet instanceMethodForSelector:_cmd])
|
||||
[CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPSet"];
|
||||
else
|
||||
[super addObserver:observer forKeyPath:aKeyPath options:options context:context];
|
||||
}
|
||||
|
||||
- (void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
if ([isa instanceMethodForSelector:_cmd] === [CPSet instanceMethodForSelector:_cmd])
|
||||
[CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPSet"];
|
||||
else
|
||||
[super removeObserver:observer forKeyPath:aKeyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -106,7 +106,7 @@ var CPURLConnectionDelegate = nil;
|
||||
key = nil,
|
||||
keys = [fields keyEnumerator];
|
||||
|
||||
while (key = [keys nextObject])
|
||||
while ((key = [keys nextObject]) !== nil)
|
||||
request.setRequestHeader(key, [fields objectForKey:key]);
|
||||
|
||||
request.send([aRequest HTTPBody]);
|
||||
@@ -196,7 +196,7 @@ var CPURLConnectionDelegate = nil;
|
||||
key = nil,
|
||||
keys = [fields keyEnumerator];
|
||||
|
||||
while (key = [keys nextObject])
|
||||
while ((key = [keys nextObject]) !== nil)
|
||||
_HTTPRequest.setRequestHeader(key, [fields objectForKey:key]);
|
||||
|
||||
_HTTPRequest.send([_request HTTPBody]);
|
||||
|
||||
@@ -84,7 +84,7 @@ CPWebDAVManagerNonCollectionResourceType = 0;
|
||||
URLString = nil,
|
||||
URLStrings = [response keyEnumerator];
|
||||
|
||||
while (URLString = [URLStrings nextObject])
|
||||
while ((URLString = [URLStrings nextObject]) !== nil)
|
||||
{
|
||||
var URL = [CPURL URLWithString:URLString],
|
||||
properties = [response objectForKey:URLString];
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* _CPCollectionKVCOperators.j
|
||||
* Foundation
|
||||
*
|
||||
* Created by Aparajita Fishman.
|
||||
* Copyright 2012 Cappuccino Foundation.
|
||||
*
|
||||
* 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.1 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
|
||||
* Lesser 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 02110-1301 USA
|
||||
*/
|
||||
|
||||
@implementation _CPCollectionKVCOperator : CPObject
|
||||
|
||||
+ (id)performOperation:(CPString)operator withCollection:(id)aCollection propertyPath:(CPString)propertyPath
|
||||
{
|
||||
var selector = CPSelectorFromString(operator + @"ForCollection:propertyPath:");
|
||||
|
||||
if (![self respondsToSelector:selector])
|
||||
return [aCollection valueForUndefinedKey:@"@" + operator];
|
||||
|
||||
return [self performSelector:selector withObject:aCollection withObject:propertyPath];
|
||||
}
|
||||
|
||||
+ (double)avgForCollection:(id)aCollection propertyPath:(CPString)propertyPath
|
||||
{
|
||||
if (!propertyPath)
|
||||
return [aCollection valueForUndefinedKey:@"@avg"];
|
||||
|
||||
var objects = [aCollection valueForKeyPath:propertyPath],
|
||||
average = 0.0,
|
||||
enumerator = [objects objectEnumerator],
|
||||
object;
|
||||
|
||||
while ((object = [enumerator nextObject]) !== nil)
|
||||
average += [object doubleValue];
|
||||
|
||||
return average / [objects count];
|
||||
}
|
||||
|
||||
+ (double)minForCollection:(id)aCollection propertyPath:(CPString)propertyPath
|
||||
{
|
||||
if (!propertyPath)
|
||||
return [aCollection valueForUndefinedKey:@"@min"];
|
||||
|
||||
var objects = [aCollection valueForKeyPath:propertyPath];
|
||||
|
||||
if ([objects count] === 0)
|
||||
return nil;
|
||||
|
||||
var enumerator = [objects objectEnumerator],
|
||||
min = [enumerator nextObject],
|
||||
object;
|
||||
|
||||
while ((object = [enumerator nextObject]) !== nil)
|
||||
{
|
||||
if ([min compare:object] > 0)
|
||||
min = object;
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
+ (double)maxForCollection:(id)aCollection propertyPath:(CPString)propertyPath
|
||||
{
|
||||
if (!propertyPath)
|
||||
return [aCollection valueForUndefinedKey:@"@max"];
|
||||
|
||||
var objects = [aCollection valueForKeyPath:propertyPath];
|
||||
|
||||
if ([objects count] === 0)
|
||||
return nil;
|
||||
|
||||
var enumerator = [objects objectEnumerator],
|
||||
max = [enumerator nextObject],
|
||||
object;
|
||||
|
||||
while ((object = [enumerator nextObject]) !== nil)
|
||||
{
|
||||
if ([max compare:object] < 0)
|
||||
max = object;
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
+ (double)sumForCollection:(id)aCollection propertyPath:(CPString)propertyPath
|
||||
{
|
||||
if (!propertyPath)
|
||||
return [aCollection valueForUndefinedKey:@"@sum"];
|
||||
|
||||
var objects = [aCollection valueForKeyPath:propertyPath],
|
||||
sum = 0.0,
|
||||
enumerator = [objects objectEnumerator],
|
||||
object;
|
||||
|
||||
while ((object = [enumerator nextObject]) !== nil)
|
||||
sum += [object doubleValue];
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
+ (int)countForCollection:(id)aCollection propertyPath:(CPString)propertyPath
|
||||
{
|
||||
return [aCollection count];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -2,8 +2,9 @@ var COUNTER;
|
||||
|
||||
@implementation CPKVCArrayTest : OJTestCase
|
||||
{
|
||||
TestObject _object @accessors(property=object);
|
||||
ImplementedTestObject _implementedObject @accessors(property=implementedObject);
|
||||
TestObject _object @accessors(property=object);
|
||||
ImplementedTestObject _implementedObject @accessors(property=implementedObject);
|
||||
CPArray _kvcArray @accessors(property=kvcArray);
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
@@ -298,11 +299,45 @@ var COUNTER;
|
||||
message:@"replaceObjectInValuesAtIndexes:withValues: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testKVCArrayOperators
|
||||
{
|
||||
var one = [1, 1, 1, 1, 1, 1, 1, 1],
|
||||
two = [1, 2, 3, 4, 8, 0];
|
||||
|
||||
[self assert:[one valueForKey:"@count"] equals:8];
|
||||
[self assert:[one valueForKeyPath:"@sum.intValue"] equals:8];
|
||||
[self assert:[two valueForKeyPath:"@avg.intValue"] equals:3];
|
||||
[self assert:[two valueForKeyPath:"@max.intValue"] equals:8];
|
||||
[self assert:[two valueForKeyPath:"@min.intValue"] equals:0];
|
||||
|
||||
var a = [A new];
|
||||
[a setValue:one forKey:"b"];
|
||||
[self assert:[a valueForKeyPath:"b.@count"] equals:8];
|
||||
[self assert:[a valueForKeyPath:"b.@sum.intValue"] equals:8];
|
||||
|
||||
var b = [];
|
||||
[b addObject:[CPDictionary dictionaryWithObjects:[@"Tom", 27] forKeys:[@"name", @"age"]]];
|
||||
[b addObject:[CPDictionary dictionaryWithObjects:[@"Dick", 31] forKeys:[@"name", @"age"]]];
|
||||
[b addObject:[CPDictionary dictionaryWithObjects:[@"Harry", 47] forKeys:[@"name", @"age"]]];
|
||||
[self assert:[b valueForKeyPath:@"@sum.age"] equals:105];
|
||||
[self assert:[b valueForKeyPath:@"@avg.age"] equals:35];
|
||||
[self assert:[b valueForKeyPath:@"@min.age"] equals:27];
|
||||
[self assert:[b valueForKeyPath:@"@max.age"] equals:47];
|
||||
[self assert:[b valueForKeyPath:@"@min.name"] equals:@"Dick"];
|
||||
[self assert:[b valueForKeyPath:@"@max.name"] equals:@"Tom"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation A : CPObject
|
||||
{
|
||||
id b;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TestObject : CPObject
|
||||
{
|
||||
CPArray _values @accessors(property=values);
|
||||
CPArray _values @accessors(property=values);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
@@ -393,4 +428,4 @@ var COUNTER;
|
||||
[[self values] replaceObjectsAtIndexes:theIndexes withObjects:theObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -382,23 +382,6 @@
|
||||
[self assertTrue: _sawObservation message:"Never recieved an observation"];
|
||||
}
|
||||
|
||||
- (void)testKVCArrayOperators
|
||||
{
|
||||
var one = [1, 1, 1, 1, 1, 1, 1, 1],
|
||||
two = [1, 2, 3, 4, 8, 0];
|
||||
|
||||
[self assert:[one valueForKey:"@count"] equals:8];
|
||||
[self assert:[one valueForKeyPath:"@sum.intValue"] equals:8];
|
||||
[self assert:[two valueForKeyPath:"@avg.intValue"] equals:3];
|
||||
[self assert:[two valueForKeyPath:"@max.intValue"] equals:8];
|
||||
[self assert:[two valueForKeyPath:"@min.intValue"] equals:0];
|
||||
|
||||
var a = [A new];
|
||||
[a setValue:one forKey:"b"];
|
||||
[self assert:[a valueForKeyPath:"b.@count"] equals:8];
|
||||
[self assert:[a valueForKeyPath:"b.@sum.intValue"] equals:8];
|
||||
}
|
||||
|
||||
- (void)testPerformance
|
||||
{
|
||||
bob = [PersonTester new];
|
||||
|
||||
@@ -232,7 +232,7 @@ var accessIVARS = YES;
|
||||
value,
|
||||
keyEnumerator = [dictForKeys keyEnumerator];
|
||||
|
||||
while (key = [keyEnumerator nextObject])
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
{
|
||||
value = [dictForKeys objectForKey: key];
|
||||
[self assert: [kvcTestObject valueForKey: key] same: value];
|
||||
@@ -384,7 +384,8 @@ var accessIVARS = YES;
|
||||
var key,
|
||||
aValue,
|
||||
keyEnumerator = [dictForKeys keyEnumerator];
|
||||
while (key = [keyEnumerator nextObject])
|
||||
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
{
|
||||
aValue = [dictForKeys objectForKey: key];
|
||||
[self assert: [kvcTestObject valueForKey: key] same: aValue];
|
||||
@@ -417,7 +418,7 @@ var accessIVARS = YES;
|
||||
value,
|
||||
keyEnumerator = [dictForKeys keyEnumerator];
|
||||
|
||||
while (key = [keyEnumerator nextObject])
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
{
|
||||
value = [dictForKeys objectForKey: key];
|
||||
[self assert: [CPNull null] same:value ];
|
||||
|
||||
@@ -235,4 +235,30 @@
|
||||
[self assertSet:set onlyHasObjects:["horizon", "surfer", "7"]];
|
||||
}
|
||||
|
||||
- (void)testKVCSetOperators
|
||||
{
|
||||
var one = [CPSet setWithArray:[@"one", @"two", @"three"]],
|
||||
two = [CPSet setWithArray:[1, 2, 3, 4, 8, 0]];
|
||||
|
||||
[self assert:[one valueForKey:"@count"] equals:3];
|
||||
[self assert:[two valueForKey:"@count"] equals:6];
|
||||
[self assert:[two valueForKeyPath:"@sum.intValue"] equals:18];
|
||||
[self assert:[two valueForKeyPath:"@avg.doubleValue"] equals:3.0];
|
||||
[self assert:[one valueForKeyPath:"@max.description"] equals:@"two"];
|
||||
[self assert:[two valueForKeyPath:"@max.intValue"] equals:8];
|
||||
[self assert:[one valueForKeyPath:"@min.description"] equals:@"one"];
|
||||
[self assert:[two valueForKeyPath:"@min.intValue"] equals:0];
|
||||
|
||||
var b = [CPSet set];
|
||||
[b addObject:[CPDictionary dictionaryWithObjects:[@"Tom", 27] forKeys:[@"name", @"age"]]];
|
||||
[b addObject:[CPDictionary dictionaryWithObjects:[@"Dick", 31] forKeys:[@"name", @"age"]]];
|
||||
[b addObject:[CPDictionary dictionaryWithObjects:[@"Harry", 47] forKeys:[@"name", @"age"]]];
|
||||
[self assert:[b valueForKeyPath:@"@sum.age"] equals:105];
|
||||
[self assert:[b valueForKeyPath:@"@avg.age"] equals:35];
|
||||
[self assert:[b valueForKeyPath:@"@min.age"] equals:27];
|
||||
[self assert:[b valueForKeyPath:@"@max.age"] equals:47];
|
||||
[self assert:[b valueForKeyPath:@"@min.name"] equals:@"Dick"];
|
||||
[self assert:[b valueForKeyPath:@"@max.name"] equals:@"Tom"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -189,7 +189,7 @@ function config(/*va_args*/)
|
||||
var key = nil,
|
||||
keyEnumerator = [configuration storedKeyEnumerator];
|
||||
|
||||
while (key = [keyEnumerator nextObject])
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
print(key + '=' + [configuration valueForKey:key]);
|
||||
}
|
||||
else if (action === "get")
|
||||
|
||||
@@ -166,7 +166,7 @@ function gen(/*va_args*/)
|
||||
key = null,
|
||||
keyEnumerator = [configuration keyEnumerator];
|
||||
|
||||
while (key = [keyEnumerator nextObject])
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
contents = contents.replace(new RegExp("__" + RegExp.escape(key) + "__", 'g'), [configuration valueForKey:key]);
|
||||
|
||||
FILE.write(path, contents, { charset: "UTF-8"});
|
||||
|
||||
@@ -133,7 +133,7 @@ var NSTransformers = [CPSet setWithObjects:
|
||||
keyEnumerator = [NSOptions keyEnumerator],
|
||||
key;
|
||||
|
||||
while (key = [keyEnumerator nextObject])
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
{
|
||||
var CPKey = @"CP" + key.substring(2),
|
||||
NSValue = [NSOptions objectForKey:key];
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
// Some of the item identifiers will be changed when loaded by NSToolbarItem, so we must change
|
||||
// the map to correspond.
|
||||
while (key = [keyEnumerator nextObject])
|
||||
while ((key = [keyEnumerator nextObject]) !== nil)
|
||||
{
|
||||
var transformedKey = NS_CPToolbarItemIdentifierMap[key] || key;
|
||||
[_identifiedItems setObject:[nsIdentifiedItems objectForKey:key] forKey:transformedKey];
|
||||
|
||||
Reference in new issue
Block a user