API 0.9.5
Foundation/CPSet+KVO.j
Go to the documentation of this file.
00001 /*
00002  * CPSet+KVO.j
00003  * Foundation
00004  *
00005  * Created by Daniel Stolzenberg.
00006  * Copyright 2010, University of Rostock
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 @implementation CPObject (CPSetKVO)
00025 
00026 - (id)mutableSetValueForKey:(id)aKey
00027 {
00028     return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self];
00029 }
00030 
00031 - (id)mutableSetValueForKeyPath:(id)aKeyPath
00032 {
00033     var dotIndex = aKeyPath.indexOf(".");
00034 
00035     if (dotIndex < 0)
00036         return [self mutableSetValueForKey:aKeyPath];
00037 
00038     var firstPart = aKeyPath.substring(0, dotIndex),
00039         lastPart = aKeyPath.substring(dotIndex + 1);
00040 
00041     return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart];
00042 }
00043 
00044 @end
00045 
00046 @implementation _CPKVCSet : CPMutableSet
00047 {
00048     id _proxyObject;
00049     id _key;
00050 
00051 
00052     SEL         _accessSEL;
00053     Function    _access;
00054 
00055     SEL         _setSEL;
00056     Function    _set;
00057 
00058     SEL         _countSEL;
00059     Function    _count;
00060 
00061     SEL         _enumeratorSEL;
00062     Function    _enumerator;
00063 
00064     SEL         _memberSEL;
00065     Function    _member;
00066 
00067     SEL         _addSEL;
00068     Function    _add;
00069 
00070     SEL         _addManySEL;
00071     Function    _addMany;
00072 
00073     SEL         _removeSEL;
00074     Function    _remove;
00075 
00076     SEL         _removeManySEL;
00077     Function    _removeMany;
00078 
00079     SEL         _intersectSEL;
00080     Function    _intersect;
00081 }
00082 
00083 + (id)alloc
00084 {
00085     var set = [CPMutableSet set];
00086 
00087     set.isa = self;
00088 
00089     var ivars = class_copyIvarList(self),
00090         count = ivars.length;
00091 
00092     while (count--)
00093         set[ivar_getName(ivars[count])] = nil;
00094 
00095     return set;
00096 }
00097 
00098 - (id)initWithKey:(id)aKey forProxyObject:(id)anObject
00099 {
00100     self = [super init];
00101 
00102     _key = aKey;
00103     _proxyObject = anObject;
00104 
00105     var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1);
00106 
00107     _accessSEL = sel_getName(_key);
00108     if ([_proxyObject respondsToSelector:_accessSEL])
00109         _access = [_proxyObject methodForSelector:_accessSEL];
00110 
00111     _setSEL = sel_getName(@"set"+capitalizedKey+":");
00112     if ([_proxyObject respondsToSelector:_setSEL])
00113         _set = [_proxyObject methodForSelector:_setSEL];
00114 
00115     _countSEL = sel_getName(@"countOf"+capitalizedKey);
00116     if ([_proxyObject respondsToSelector:_countSEL])
00117         _count = [_proxyObject methodForSelector:_countSEL];
00118 
00119     _enumeratorSEL = sel_getName(@"enumeratorOf"+capitalizedKey);
00120     if ([_proxyObject respondsToSelector:_enumeratorSEL])
00121         _enumerator = [_proxyObject methodForSelector:_enumeratorSEL];
00122 
00123     _memberSEL = sel_getName(@"memberOf"+capitalizedKey+":");
00124     if ([_proxyObject respondsToSelector:_memberSEL])
00125         _member = [_proxyObject methodForSelector:_memberSEL];
00126 
00127     _addSEL = sel_getName(@"add"+capitalizedKey+"Object:");
00128     if ([_proxyObject respondsToSelector:_addSEL])
00129         _add = [_proxyObject methodForSelector:_addSEL];
00130 
00131     _addManySEL = sel_getName(@"add"+capitalizedKey+":");
00132     if ([_proxyObject respondsToSelector:_addManySEL])
00133         _addMany = [_proxyObject methodForSelector:_addManySEL];
00134 
00135     _removeSEL = sel_getName(@"remove"+capitalizedKey+"Object:");
00136     if ([_proxyObject respondsToSelector:_removeSEL])
00137         _remove = [_proxyObject methodForSelector:_removeSEL];
00138 
00139     _removeManySEL = sel_getName(@"remove"+capitalizedKey+":");
00140     if ([_proxyObject respondsToSelector:_removeManySEL])
00141         _removeMany = [_proxyObject methodForSelector:_removeManySEL];
00142 
00143     _intersectSEL = sel_getName(@"intersect"+capitalizedKey+":");
00144     if ([_proxyObject respondsToSelector:_intersectSEL])
00145         _intersect = [_proxyObject methodForSelector:_intersectSEL];
00146 
00147     return self;
00148 }
00149 
00150 - (id)_representedObject
00151 {
00152     if (_access)
00153         return _access(_proxyObject, _accessSEL);
00154 
00155     return [_proxyObject valueForKey:_key];
00156 }
00157 
00158 - (void)_setRepresentedObject:(id)anObject
00159 {
00160     if (_set)
00161         return _set(_proxyObject, _setSEL, anObject);
00162 
00163     [_proxyObject setValue:anObject forKey:_key];
00164 }
00165 
00166 - (unsigned)count
00167 {
00168     if (_count)
00169         return _count(_proxyObject, _countSEL);
00170 
00171     return [[self _representedObject] count];
00172 }
00173 
00174 - (CPEnumerator)objectEnumerator
00175 {
00176     if (_enumerator)
00177         return _enumerator(_proxyObject, _enumeratorSEL);
00178 
00179     return [[self _representedObject] objectEnumerator];
00180 }
00181 
00182 - (id)member:(id)anObject
00183 {
00184     if (_member)
00185         return _member(_proxyObject, _memberSEL, anObject);
00186 
00187     return [[self _representedObject] member:anObject];
00188 }
00189 
00190 - (void)addObject:(id)anObject
00191 {
00192     if (_add)
00193         _add(_proxyObject, _addSEL, anObject);
00194     else if (_addMany)
00195     {
00196         var objectSet = [CPSet setWithObject: anObject];
00197         _addMany(_proxyObject, _addManySEL, objectSet);
00198     }
00199     else
00200     {
00201         var target = [[self _representedObject] copy];
00202         [target addObject:anObject];
00203         [self _setRepresentedObject:target];
00204     }
00205 }
00206 
00207 - (void)addObjectsFromArray:(CPArray)objects
00208 {
00209     if (_addMany)
00210     {
00211         var objectSet = [CPSet setWithArray: objects];
00212         _addMany(_proxyObject, _addManySEL, objectSet);
00213     }
00214     else if (_add)
00215     {
00216         var object,
00217             objectEnumerator = [objects objectEnumerator];
00218         while (object = [objectEnumerator nextObject])
00219             _add(_proxyObject, _addSEL, object);
00220     }
00221     else
00222     {
00223         var target = [[self _representedObject] copy];
00224         [target addObjectsFromArray:objects];
00225         [self _setRepresentedObject:target];
00226     }
00227 }
00228 
00229 - (void)unionSet:(CPSet)aSet
00230 {
00231     if (_addMany)
00232         _addMany(_proxyObject, _addManySEL, aSet);
00233     else if (_add)
00234     {
00235         var object,
00236             objectEnumerator = [aSet objectEnumerator];
00237         while (object = [objectEnumerator nextObject])
00238             _add(_proxyObject, _addSEL, object);
00239     }
00240     else
00241     {
00242         var target = [[self _representedObject] copy];
00243         [target unionSet:aSet];
00244         [self _setRepresentedObject:target];
00245     }
00246 }
00247 
00248 - (void)removeObject:(id)anObject
00249 {
00250     if (_remove)
00251         _remove(_proxyObject, _removeSEL, anObject);
00252     else if (_removeMany)
00253     {
00254         var objectSet = [CPSet setWithObject: anObject];
00255         _removeMany(_proxyObject, _removeManySEL, objectSet);
00256     }
00257     else
00258     {
00259         var target = [[self _representedObject] copy];
00260         [target removeObject:anObject];
00261         [self _setRepresentedObject:target];
00262     }
00263 }
00264 
00265 - (void)minusSet:(CPSet)aSet
00266 {
00267     if (_removeMany)
00268         _removeMany(_proxyObject, _removeManySEL, aSet);
00269     else if (_remove)
00270     {
00271         var object,
00272             objectEnumerator = [aSet objectEnumerator];
00273         while (object = [objectEnumerator nextObject])
00274             _remove(_proxyObject, _removeSEL, object);
00275     }
00276     else
00277     {
00278         var target = [[self _representedObject] copy];
00279         [target minusSet:aSet];
00280         [self _setRepresentedObject:target];
00281     }
00282 }
00283 
00284 - (void)removeObjectsInArray:(CPArray)objects
00285 {
00286     if (_removeMany)
00287     {
00288         var objectSet = [CPSet setWithArray:objects];
00289         _removeMany(_proxyObject, _removeManySEL, objectSet);
00290     }
00291     else if (_remove)
00292     {
00293         var object,
00294             objectEnumerator = [objects objectEnumerator];
00295         while (object = [objectEnumerator nextObject])
00296             _remove(_proxyObject, _removeSEL, object);
00297     }
00298     else
00299     {
00300         var target = [[self _representedObject] copy];
00301         [target removeObjectsInArray:objects];
00302         [self _setRepresentedObject:target];
00303     }
00304 }
00305 
00306 - (void)removeAllObjects
00307 {
00308     if (_removeMany)
00309     {
00310         var allObjectsSet = [[self _representedObject] copy];
00311         _removeMany(_proxyObject, _removeManySEL, allObjectsSet);
00312     }
00313     else if (_remove)
00314     {
00315         var object,
00316             objectEnumerator = [[[self _representedObject] copy] objectEnumerator];
00317         while (object = [objectEnumerator nextObject])
00318             _remove(_proxyObject, _removeSEL, object);
00319     }
00320     else
00321     {
00322         var target = [[self _representedObject] copy];
00323         [target removeAllObjects];
00324         [self _setRepresentedObject:target];
00325     }
00326 }
00327 
00328 - (void)intersectSet:(CPSet)aSet
00329 {
00330     if (_intersect)
00331         _intersect(_proxyObject, _intersectSEL, aSet);
00332     else
00333     {
00334         var target = [[self _representedObject] copy];
00335         [target intersectSet:aSet];
00336         [self _setRepresentedObject:target];
00337     }
00338 }
00339 
00340 - (void)setSet:(CPSet)set
00341 {
00342     [self _setRepresentedObject: set];
00343 }
00344 
00345 - (CPArray)allObjects
00346 {
00347     return [[self _representedObject] allObjects];
00348 }
00349 
00350 - (id)anyObject
00351 {
00352     return [[self _representedObject] anyObject];
00353 }
00354 
00355 - (BOOL)containsObject:(id)anObject
00356 {
00357     return [[self _representedObject] containsObject: anObject];
00358 }
00359 
00360 - (BOOL)intersectsSet:(CPSet)aSet
00361 {
00362     return [[self _representedObject] intersectsSet: aSet];
00363 }
00364 
00365 - (BOOL)isEqualToSet:(CPSet)aSet
00366 {
00367     return [[self _representedObject] isEqualToSet: aSet];
00368 }
00369 
00370 - (id)copy
00371 {
00372     return [[self _representedObject] copy];
00373 }
00374 
00375 @end
00376 
00377 @implementation CPSet (CPKeyValueCoding)
00378 
00379 - (id)valueForKeyPath:(CPString)aKeyPath
00380 {
00381     if (aKeyPath.indexOf("@") === 0)
00382     {
00383         var dotIndex = aKeyPath.indexOf("."),
00384             operator,
00385             parameter;
00386 
00387         if (dotIndex !== -1)
00388         {
00389             operator = aKeyPath.substring(1, dotIndex);
00390             parameter = aKeyPath.substring(dotIndex + 1);
00391         }
00392         else
00393             operator = aKeyPath.substring(1);
00394 
00395         if (kvoOperators[operator])
00396             return kvoOperators[operator](self, _cmd, parameter);
00397 
00398         return nil;
00399     }
00400     else
00401     {
00402         var valuesForKeySet = [CPSet set],
00403             containedObject,
00404             containedObjectValue,
00405             containedObjectEnumerator = [self objectEnumerator];
00406         while (containedObject = [containedObjectEnumerator nextObject])
00407         {
00408             containedObjectValue = [containedObject valueForKeyPath:aKeyPath];
00409             if (containedObjectValue)
00410                 [valuesForKeySet addObject:containedObjectValue];
00411         }
00412         return valuesForKeySet;
00413     }
00414 }
00415 
00416 - (void)setValue:(id)aValue forKey:(CPString)aKey
00417 {
00418     var containedObject,
00419         containedObjectEnumerator = [self objectEnumerator];
00420     while (containedObject = [containedObjectEnumerator nextObject])
00421         [containedObject setValue:aValue forKey:aKey];
00422 }
00423 
00424 @end
00425 
00426 var kvoOperators = [];
00427 
00428 // FIXME: this is not DRY because the implementation in CPArray+KVO is merely the same!
00429 // HACK: prevent these from becoming globals. workaround for obj-j "function foo(){}" behavior
00430 var avgOperator,
00431     maxOperator,
00432     minOperator,
00433     countOperator,
00434     sumOperator;
00435 
00436 kvoOperators["avg"] = function avgOperator(self, _cmd, param)
00437 {
00438     //CPSet returns a CPSet - to obtain an array call allObjects
00439     var objects = [[self valueForKeyPath:param] allObjects],
00440         length = [objects count],
00441         index = length,
00442         average = 0.0;
00443 
00444     if (!length)
00445         return 0;
00446 
00447     while (index--)
00448         average += [objects[index] doubleValue];
00449 
00450     return average / length;
00451 }
00452 
00453 kvoOperators["max"] = function maxOperator(self, _cmd, param)
00454 {
00455     //CPSet returns a CPSet - to obtain an array call allObjects
00456     var objects = [[self valueForKeyPath:param] allObjects],
00457         index = [objects count] - 1,
00458         max = [objects lastObject];
00459 
00460     while (index--)
00461     {
00462         var item = objects[index];
00463         if ([max compare:item] < 0)
00464             max = item;
00465     }
00466 
00467     return max;
00468 }
00469 
00470 kvoOperators["min"] = function minOperator(self, _cmd, param)
00471 {
00472     //CPSet returns a CPSet - to obtain an array call allObjects
00473     var objects = [[self valueForKeyPath:param] allObjects],
00474         index = [objects count] - 1,
00475         min = [objects lastObject];
00476 
00477     while (index--)
00478     {
00479         var item = objects[index];
00480         if ([min compare:item] > 0)
00481             min = item;
00482     }
00483 
00484     return min;
00485 }
00486 
00487 kvoOperators["count"] = function countOperator(self, _cmd, param)
00488 {
00489     return [self count];
00490 }
00491 
00492 kvoOperators["sum"] = function sumOperator(self, _cmd, param)
00493 {
00494     //CPSet returns a CPSet - to obtain an array call allObjects
00495     var objects = [[self valueForKeyPath:param] allObjects],
00496         index = [objects count],
00497         sum = 0.0;
00498 
00499     while (index--)
00500         sum += [objects[index] doubleValue];
00501 
00502     return sum;
00503 }
00504 
00505 
00506 @implementation CPSet (CPKeyValueObserving)
00507 
00508 - (void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
00509 {
00510     if ([isa instanceMethodForSelector:_cmd] === [CPSet instanceMethodForSelector:_cmd])
00511         [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPSet"];
00512     else
00513         [super addObserver:observer forKeyPath:aKeyPath options:options context:context];
00514 }
00515 
00516 - (void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath
00517 {
00518     if ([isa instanceMethodForSelector:_cmd] === [CPSet instanceMethodForSelector:_cmd])
00519         [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPSet"];
00520     else
00521         [super removeObserver:observer forKeyPath:aKeyPath];
00522 }
00523 
00524 @end
 All Classes Files Functions Variables Defines