mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-27 14:07:03 +00:00
528 lines
13 KiB
Plaintext
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
|