Files
cappuccino/Foundation/CPSet+KVO.j
T
2011-01-15 09:11:46 +02:00

528 lines
13 KiB
Plaintext

/*
* CPSet+KVO.j
* Foundation
*
* Created by Daniel Stolzenberg.
* Copyright 2010, University of Rostock
*
* 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
*/
@import "CPException.j"
@import "CPObject.j"
@import "CPSet.j"
@implementation CPObject (CPSetKVO)
- (id)mutableSetValueForKey:(id)aKey
{
return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self];
}
- (id)mutableSetValueForKeyPath:(id)aKeyPath
{
var dotIndex = aKeyPath.indexOf(".");
if (dotIndex < 0)
return [self mutableSetValueForKey:aKeyPath];
var firstPart = aKeyPath.substring(0, dotIndex),
lastPart = aKeyPath.substring(dotIndex + 1);
return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart];
}
@end
@implementation _CPKVCSet : CPMutableSet
{
id _proxyObject;
id _key;
SEL _accessSEL;
Function _access;
SEL _setSEL;
Function _set;
SEL _countSEL;
Function _count;
SEL _enumeratorSEL;
Function _enumerator;
SEL _memberSEL;
Function _member;
SEL _addSEL;
Function _add;
SEL _addManySEL;
Function _addMany;
SEL _removeSEL;
Function _remove;
SEL _removeManySEL;
Function _removeMany;
SEL _intersectSEL;
Function _intersect;
}
+ (id)alloc
{
var set = [CPMutableSet set];
set.isa = self;
var ivars = class_copyIvarList(self),
count = ivars.length;
while (count--)
set[ivar_getName(ivars[count])] = nil;
return set;
}
- (id)initWithKey:(id)aKey forProxyObject:(id)anObject
{
self = [super init];
_key = aKey;
_proxyObject = anObject;
var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1);
_accessSEL = sel_getName(_key);
if ([_proxyObject respondsToSelector:_accessSEL])
_access = [_proxyObject methodForSelector:_accessSEL];
_setSEL = sel_getName(@"set"+capitalizedKey+":");
if ([_proxyObject respondsToSelector:_setSEL])
_set = [_proxyObject methodForSelector:_setSEL];
_countSEL = sel_getName(@"countOf"+capitalizedKey);
if ([_proxyObject respondsToSelector:_countSEL])
_count = [_proxyObject methodForSelector:_countSEL];
_enumeratorSEL = sel_getName(@"enumeratorOf"+capitalizedKey);
if ([_proxyObject respondsToSelector:_enumeratorSEL])
_enumerator = [_proxyObject methodForSelector:_enumeratorSEL];
_memberSEL = sel_getName(@"memberOf"+capitalizedKey+":");
if ([_proxyObject respondsToSelector:_memberSEL])
_member = [_proxyObject methodForSelector:_memberSEL];
_addSEL = sel_getName(@"add"+capitalizedKey+"Object:");
if ([_proxyObject respondsToSelector:_addSEL])
_add = [_proxyObject methodForSelector:_addSEL];
_addManySEL = sel_getName(@"add"+capitalizedKey+":");
if ([_proxyObject respondsToSelector:_addManySEL])
_addMany = [_proxyObject methodForSelector:_addManySEL];
_removeSEL = sel_getName(@"remove"+capitalizedKey+"Object:");
if ([_proxyObject respondsToSelector:_removeSEL])
_remove = [_proxyObject methodForSelector:_removeSEL];
_removeManySEL = sel_getName(@"remove"+capitalizedKey+":");
if ([_proxyObject respondsToSelector:_removeManySEL])
_removeMany = [_proxyObject methodForSelector:_removeManySEL];
_intersectSEL = sel_getName(@"intersect"+capitalizedKey+":");
if ([_proxyObject respondsToSelector:_intersectSEL])
_intersect = [_proxyObject methodForSelector:_intersectSEL];
return self;
}
- (id)_representedObject
{
if (_access)
return _access(_proxyObject, _accessSEL);
return [_proxyObject valueForKey:_key];
}
- (void)_setRepresentedObject:(id)anObject
{
if (_set)
return _set(_proxyObject, _setSEL, anObject);
[_proxyObject setValue:anObject forKey:_key];
}
- (unsigned)count
{
if (_count)
return _count(_proxyObject, _countSEL);
return [[self _representedObject] count];
}
- (CPEnumerator)objectEnumerator
{
if (_enumerator)
return _enumerator(_proxyObject, _enumeratorSEL);
return [[self _representedObject] objectEnumerator];
}
- (id)member:(id)anObject
{
if (_member)
return _member(_proxyObject, _memberSEL, anObject);
return [[self _representedObject] member:anObject];
}
- (void)addObject:(id)anObject
{
if (_add)
_add(_proxyObject, _addSEL, anObject);
else if (_addMany)
{
var objectSet = [CPSet setWithObject: anObject];
_addMany(_proxyObject, _addManySEL, objectSet);
}
else
{
var target = [[self _representedObject] copy];
[target addObject:anObject];
[self _setRepresentedObject:target];
}
}
- (void)addObjectsFromArray:(CPArray)objects
{
if (_addMany)
{
var objectSet = [CPSet setWithArray: objects];
_addMany(_proxyObject, _addManySEL, objectSet);
}
else if (_add)
{
var object,
objectEnumerator = [objects objectEnumerator];
while (object = [objectEnumerator nextObject])
_add(_proxyObject, _addSEL, object);
}
else
{
var target = [[self _representedObject] copy];
[target addObjectsFromArray:objects];
[self _setRepresentedObject:target];
}
}
- (void)unionSet:(CPSet)aSet
{
if (_addMany)
_addMany(_proxyObject, _addManySEL, aSet);
else if (_add)
{
var object,
objectEnumerator = [aSet objectEnumerator];
while (object = [objectEnumerator nextObject])
_add(_proxyObject, _addSEL, object);
}
else
{
var target = [[self _representedObject] copy];
[target unionSet:aSet];
[self _setRepresentedObject:target];
}
}
- (void)removeObject:(id)anObject
{
if (_remove)
_remove(_proxyObject, _removeSEL, anObject);
else if (_removeMany)
{
var objectSet = [CPSet setWithObject: anObject];
_removeMany(_proxyObject, _removeManySEL, objectSet);
}
else
{
var target = [[self _representedObject] copy];
[target removeObject:anObject];
[self _setRepresentedObject:target];
}
}
- (void)minusSet:(CPSet)aSet
{
if (_removeMany)
_removeMany(_proxyObject, _removeManySEL, aSet);
else if (_remove)
{
var object,
objectEnumerator = [aSet objectEnumerator];
while (object = [objectEnumerator nextObject])
_remove(_proxyObject, _removeSEL, object);
}
else
{
var target = [[self _representedObject] copy];
[target minusSet:aSet];
[self _setRepresentedObject:target];
}
}
- (void)removeObjectsInArray:(CPArray)objects
{
if (_removeMany)
{
var objectSet = [CPSet setWithArray:objects];
_removeMany(_proxyObject, _removeManySEL, objectSet);
}
else if (_remove)
{
var object,
objectEnumerator = [objects objectEnumerator];
while (object = [objectEnumerator nextObject])
_remove(_proxyObject, _removeSEL, object);
}
else
{
var target = [[self _representedObject] copy];
[target removeObjectsInArray:objects];
[self _setRepresentedObject:target];
}
}
- (void)removeAllObjects
{
if (_removeMany)
{
var allObjectsSet = [[self _representedObject] copy];
_removeMany(_proxyObject, _removeManySEL, allObjectsSet);
}
else if (_remove)
{
var object,
objectEnumerator = [[[self _representedObject] copy] objectEnumerator];
while (object = [objectEnumerator nextObject])
_remove(_proxyObject, _removeSEL, object);
}
else
{
var target = [[self _representedObject] copy];
[target removeAllObjects];
[self _setRepresentedObject:target];
}
}
- (void)intersectSet:(CPSet)aSet
{
if (_intersect)
_intersect(_proxyObject, _intersectSEL, aSet);
else
{
var target = [[self _representedObject] copy];
[target intersectSet:aSet];
[self _setRepresentedObject:target];
}
}
- (void)setSet:(CPSet)set
{
[self _setRepresentedObject: set];
}
- (CPArray)allObjects
{
return [[self _representedObject] allObjects];
}
- (id)anyObject
{
return [[self _representedObject] anyObject];
}
- (BOOL)containsObject:(id)anObject
{
return [[self _representedObject] containsObject: anObject];
}
- (BOOL)intersectsSet:(CPSet)aSet
{
return [[self _representedObject] intersectsSet: aSet];
}
- (BOOL)isEqualToSet:(CPSet)aSet
{
return [[self _representedObject] isEqualToSet: aSet];
}
- (id)copy
{
return [[self _representedObject] copy];
}
@end
@implementation CPSet (CPKeyValueCoding)
- (id)valueForKeyPath:(CPString)aKeyPath
{
if (aKeyPath.indexOf("@") === 0)
{
var dotIndex = aKeyPath.indexOf("."),
operator,
parameter;
if (dotIndex !== -1)
{
operator = aKeyPath.substring(1, dotIndex);
parameter = aKeyPath.substring(dotIndex + 1);
}
else
operator = aKeyPath.substring(1);
if (kvoOperators[operator])
return kvoOperators[operator](self, _cmd, parameter);
return nil;
}
else
{
var valuesForKeySet = [CPSet set],
containedObject,
containedObjectValue,
containedObjectEnumerator = [self objectEnumerator];
while (containedObject = [containedObjectEnumerator nextObject])
{
containedObjectValue = [containedObject valueForKeyPath:aKeyPath];
if (containedObjectValue)
[valuesForKeySet addObject:containedObjectValue];
}
return valuesForKeySet;
}
}
- (void)setValue:(id)aValue forKey:(CPString)aKey
{
var containedObject,
containedObjectEnumerator = [self objectEnumerator];
while (containedObject = [containedObjectEnumerator nextObject])
[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