API 0.9.5
Foundation/CPArray+KVO.j
Go to the documentation of this file.
00001 /*
00002  * CPArray+KVO.j
00003  * Foundation
00004  *
00005  * Created by Ross Boucher.
00006  * Copyright 2008, 280 North, Inc.
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 
00025 @implementation CPObject (CPArrayKVO)
00026 
00027 - (id)mutableArrayValueForKey:(id)aKey
00028 {
00029     return [[_CPKVCArray alloc] initWithKey:aKey forProxyObject:self];
00030 }
00031 
00032 - (id)mutableArrayValueForKeyPath:(id)aKeyPath
00033 {
00034     var dotIndex = aKeyPath.indexOf(".");
00035 
00036     if (dotIndex < 0)
00037         return [self mutableArrayValueForKey:aKeyPath];
00038 
00039     var firstPart = aKeyPath.substring(0, dotIndex),
00040         lastPart = aKeyPath.substring(dotIndex + 1);
00041 
00042     return [[self valueForKeyPath:firstPart] mutableArrayValueForKeyPath:lastPart];
00043 }
00044 
00045 @end
00046 
00047 @implementation _CPKVCArray : CPMutableArray
00048 {
00049     id _proxyObject;
00050     id _key;
00051 
00052     SEL         _insertSEL;
00053     Function    _insert;
00054 
00055     SEL         _removeSEL;
00056     Function    _remove;
00057 
00058     SEL         _replaceSEL;
00059     Function    _replace;
00060 
00061     SEL         _insertManySEL;
00062     Function    _insertMany;
00063 
00064     SEL         _removeManySEL;
00065     Function    _removeMany;
00066 
00067     SEL         _replaceManySEL;
00068     Function    _replaceMany;
00069 
00070     SEL         _objectAtIndexSEL;
00071     Function    _objectAtIndex;
00072 
00073     SEL         _objectsAtIndexesSEL;
00074     Function    _objectsAtIndexes;
00075 
00076     SEL         _countSEL;
00077     Function    _count;
00078 
00079     SEL         _accessSEL;
00080     Function    _access;
00081 
00082     SEL         _setSEL;
00083     Function    _set;
00084 }
00085 
00086 + (id)alloc
00087 {
00088     var array = [];
00089 
00090     array.isa = self;
00091 
00092     var ivars = class_copyIvarList(self),
00093         count = ivars.length;
00094 
00095     while (count--)
00096         array[ivar_getName(ivars[count])] = nil;
00097 
00098     return array;
00099 }
00100 
00101 - (id)initWithKey:(id)aKey forProxyObject:(id)anObject
00102 {
00103     self = [super init];
00104 
00105     _key = aKey;
00106     _proxyObject = anObject;
00107 
00108     var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1);
00109 
00110     _insertSEL = sel_getName(@"insertObject:in" + capitalizedKey + "AtIndex:");
00111     if ([_proxyObject respondsToSelector:_insertSEL])
00112         _insert = [_proxyObject methodForSelector:_insertSEL];
00113 
00114     _removeSEL = sel_getName(@"removeObjectFrom" + capitalizedKey + "AtIndex:");
00115     if ([_proxyObject respondsToSelector:_removeSEL])
00116         _remove = [_proxyObject methodForSelector:_removeSEL];
00117 
00118     _replaceSEL = sel_getName(@"replaceObjectIn" + capitalizedKey + "AtIndex:withObject:");
00119     if ([_proxyObject respondsToSelector:_replaceSEL])
00120         _replace = [_proxyObject methodForSelector:_replaceSEL];
00121 
00122     _insertManySEL = sel_getName(@"insert" + capitalizedKey + ":atIndexes:");
00123     if ([_proxyObject respondsToSelector:_insertManySEL])
00124         _insertMany = [_proxyObject methodForSelector:_insertManySEL];
00125 
00126     _removeManySEL = sel_getName(@"remove" + capitalizedKey + "AtIndexes:");
00127     if ([_proxyObject respondsToSelector:_removeManySEL])
00128         _removeMany = [_proxyObject methodForSelector:_removeManySEL];
00129 
00130     _replaceManySEL = sel_getName(@"replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":");
00131     if ([_proxyObject respondsToSelector:_replaceManySEL])
00132         _replaceMany = [_proxyObject methodForSelector:_replaceManySEL];
00133 
00134     _objectAtIndexSEL = sel_getName(@"objectIn" + capitalizedKey + "AtIndex:");
00135     if ([_proxyObject respondsToSelector:_objectAtIndexSEL])
00136         _objectAtIndex = [_proxyObject methodForSelector:_objectAtIndexSEL];
00137 
00138     _objectsAtIndexesSEL = sel_getName(_key + "AtIndexes:");
00139     if ([_proxyObject respondsToSelector:_objectsAtIndexesSEL])
00140         _objectsAtIndexes = [_proxyObject methodForSelector:_objectsAtIndexesSEL];
00141 
00142     _countSEL = sel_getName(@"countOf" + capitalizedKey);
00143     if ([_proxyObject respondsToSelector:_countSEL])
00144         _count = [_proxyObject methodForSelector:_countSEL];
00145 
00146     _accessSEL = sel_getName(_key);
00147     if ([_proxyObject respondsToSelector:_accessSEL])
00148         _access = [_proxyObject methodForSelector:_accessSEL];
00149 
00150     _setSEL = sel_getName(@"set" + capitalizedKey + ":");
00151     if ([_proxyObject respondsToSelector:_setSEL])
00152         _set = [_proxyObject methodForSelector:_setSEL];
00153 
00154     return self;
00155 }
00156 
00157 - (id)copy
00158 {
00159     var i = 0,
00160         theCopy = [],
00161         count = [self count];
00162 
00163     for (; i < count; i++)
00164         [theCopy addObject:[self objectAtIndex:i]];
00165 
00166     return theCopy;
00167 }
00168 
00169 - (id)_representedObject
00170 {
00171     if (_access)
00172         return _access(_proxyObject, _accessSEL);
00173 
00174     return [_proxyObject valueForKey:_key];
00175 }
00176 
00177 - (void)_setRepresentedObject:(id)anObject
00178 {
00179     if (_set)
00180         return _set(_proxyObject, _setSEL, anObject);
00181 
00182     [_proxyObject setValue:anObject forKey:_key];
00183 }
00184 
00185 - (unsigned)count
00186 {
00187     if (_count)
00188         return _count(_proxyObject, _countSEL);
00189 
00190     return [[self _representedObject] count];
00191 }
00192 
00193 - (int)indexOfObject:(CPObject)anObject inRange:(CPRange)aRange
00194 {
00195     var index = aRange.location,
00196         count = aRange.length,
00197         shouldIsEqual = !!anObject.isa;
00198 
00199     for (; index < count; ++index)
00200     {
00201         var object = [self objectAtIndex:index];
00202 
00203         if (anObject === object || shouldIsEqual && !!object.isa && [anObject isEqual:object])
00204             return index;
00205     }
00206 
00207     return CPNotFound;
00208 }
00209 
00210 - (int)indexOfObject:(CPObject)anObject
00211 {
00212     return [self indexOfObject:anObject inRange:CPMakeRange(0, [self count])];
00213 }
00214 
00215 - (int)indexOfObjectIdenticalTo:(CPObject)anObject inRange:(CPRange)aRange
00216 {
00217     var index = aRange.location,
00218         count = aRange.length;
00219 
00220     for (; index < count; ++index)
00221         if (anObject === [self objectAtIndex:index])
00222             return index;
00223 
00224     return CPNotFound;
00225 }
00226 
00227 - (int)indexOfObjectIdenticalTo:(CPObject)anObject
00228 {
00229     return [self indexOfObjectIdenticalTo:anObject inRange:CPMakeRange(0, [self count])];
00230 }
00231 
00232 - (id)objectAtIndex:(unsigned)anIndex
00233 {
00234     return [[self objectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]] firstObject];
00235 }
00236 
00237 - (CPArray)objectsAtIndexes:(CPIndexSet)theIndexes
00238 {
00239     if (_objectsAtIndexes)
00240         return _objectsAtIndexes(_proxyObject, _objectsAtIndexesSEL, theIndexes);
00241     if (_objectAtIndex)
00242     {
00243         var index = CPNotFound,
00244             objects = [];
00245 
00246         while ((index = [theIndexes indexGreaterThanIndex:index]) !== CPNotFound)
00247             objects.push(_objectAtIndex(_proxyObject, _objectAtIndexSEL, index));
00248 
00249         return objects;
00250     }
00251 
00252     return [[self _representedObject] objectsAtIndexes:theIndexes];
00253 }
00254 
00255 - (void)addObject:(id)anObject
00256 {
00257     [self insertObject:anObject atIndex:[self count]];
00258 }
00259 
00260 - (void)addObjectsFromArray:(CPArray)anArray
00261 {
00262     var index = 0,
00263         count = [anArray count];
00264 
00265     [self insertObjects:anArray atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange([self count], count)]];
00266 }
00267 
00268 - (void)insertObject:(id)anObject atIndex:(unsigned)anIndex
00269 {
00270     [self insertObjects:[anObject] atIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
00271 }
00272 
00273 - (void)insertObjects:(CPArray)theObjects atIndexes:(CPIndexSet)theIndexes
00274 {
00275     if (_insertMany)
00276         _insertMany(_proxyObject, _insertManySEL, theObjects, theIndexes);
00277     else if (_insert)
00278     {
00279         var indexesArray = [];
00280         [theIndexes getIndexes:indexesArray maxCount:-1 inIndexRange:nil];
00281 
00282         for (var index = 0; index < [indexesArray count]; index++)
00283         {
00284             var objectIndex = [indexesArray objectAtIndex:index],
00285                 object = [theObjects objectAtIndex:index];
00286 
00287             _insert(_proxyObject, _insertSEL, object, objectIndex);
00288         }
00289     }
00290     else
00291     {
00292         var target = [[self _representedObject] copy];
00293 
00294         [target insertObjects:theObjects atIndexes:theIndexes];
00295         [self _setRepresentedObject:target];
00296     }
00297 }
00298 
00299 - (void)removeObject:(id)anObject
00300 {
00301     [self removeObject:anObject inRange:CPMakeRange(0, [self count])];
00302 }
00303 
00304 - (void)removeObjectsInArray:(CPArray)theObjects
00305 {
00306     if (_removeMany)
00307     {
00308         var indexes = [CPIndexSet indexSet],
00309             index = [theObjects count];
00310 
00311         while (index--)
00312             [indexes addIndex:[self indexOfObject:[theObjects objectAtIndex:index]]];
00313 
00314         _removeMany(_proxyObject, _removeManySEL, indexes);
00315     }
00316     else if (_remove)
00317     {
00318         var index = [theObjects count];
00319         while (index--)
00320             _remove(_proxyObject, _removeSEL, [self indexOfObject:[theObjects objectAtIndex:index]]);
00321     }
00322     else
00323     {
00324         var target = [[self _representedObject] copy];
00325         [target removeObjectsInArray:theObjects];
00326         [self _setRepresentedObject:target];
00327     }
00328 }
00329 
00330 - (void)removeObject:(id)theObject inRange:(CPRange)theRange
00331 {
00332     if (_remove)
00333         _remove(_proxyObject, _removeSEL, [self indexOfObject:theObject inRange:theRange]);
00334     else if (_removeMany)
00335     {
00336         var index = [self indexOfObject:theObject inRange:theRange];
00337         _removeMany(_proxyObject, _removeManySEL, [CPIndexSet indexSetWithIndex:index]);
00338     }
00339     else
00340     {
00341         var index;
00342 
00343         while ((index = [self indexOfObject:theObject inRange:theRange]) !== CPNotFound)
00344         {
00345             [self removeObjectAtIndex:index];
00346             theRange = CPIntersectionRange(CPMakeRange(index, length - index), theRange);
00347         }
00348     }
00349 }
00350 
00351 - (void)removeLastObject
00352 {
00353     [self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:[self count] - 1]];
00354 }
00355 
00356 - (void)removeObjectAtIndex:(unsigned)anIndex
00357 {
00358     [self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
00359 }
00360 
00361 - (void)removeObjectsAtIndexes:(CPIndexSet)theIndexes
00362 {
00363     if (_removeMany)
00364         _removeMany(_proxyObject, _removeManySEL, theIndexes);
00365     else if (_remove)
00366     {
00367         var index = [theIndexes lastIndex];
00368 
00369         while (index !== CPNotFound)
00370         {
00371             _remove(_proxyObject, _removeSEL, index)
00372             index = [theIndexes indexLessThanIndex:index];
00373         }
00374     }
00375     else
00376     {
00377         var target = [[self _representedObject] copy];
00378         [target removeObjectsAtIndexes:theIndexes];
00379         [self _setRepresentedObject:target];
00380     }
00381 }
00382 
00383 - (void)replaceObjectAtIndex:(unsigned)anIndex withObject:(id)anObject
00384 {
00385     [self replaceObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] withObjects:[anObject]]
00386 }
00387 
00388 - (void)replaceObjectsAtIndexes:(CPIndexSet)theIndexes withObjects:(CPArray)theObjects
00389 {
00390     if (_replaceMany)
00391         return _replaceMany(_proxyObject, _replaceManySEL, theIndexes, theObjects);
00392     else if (_replace)
00393     {
00394         var i = 0,
00395             index = [theIndexes firstIndex];
00396 
00397         while (index !== CPNotFound)
00398         {
00399             _replace(_proxyObject, _replaceSEL, index, [theObjects objectAtIndex:i++]);
00400             index = [theIndexes indexGreaterThanIndex:index];
00401         }
00402     }
00403     else
00404     {
00405         var target = [[self _representedObject] copy];
00406         [target replaceObjectsAtIndexes:theIndexes withObjects:theObjects];
00407         [self _setRepresentedObject:target];
00408     }
00409 }
00410 
00411 @end
00412 
00413 
00414 //KVC on CPArray objects act on each item of the array, rather than on the array itself
00415 
00416 @implementation CPArray (KeyValueCoding)
00417 
00418 - (id)valueForKey:(CPString)aKey
00419 {
00420     if (aKey.indexOf("@") === 0)
00421     {
00422         if (aKey.indexOf(".") !== -1)
00423             [CPException raise:CPInvalidArgumentException reason:"called valueForKey: on an array with a complex key ("+aKey+"). use valueForKeyPath:"];
00424 
00425         if (aKey == "@count")
00426             return length;
00427 
00428         return nil;
00429     }
00430     else
00431     {
00432         var newArray = [],
00433             enumerator = [self objectEnumerator],
00434             object;
00435 
00436         while ((object = [enumerator nextObject]) !== nil)
00437         {
00438             var value = [object valueForKey:aKey];
00439 
00440             if (value === nil || value === undefined)
00441                 value = [CPNull null];
00442 
00443             newArray.push(value);
00444         }
00445 
00446         return newArray;
00447     }
00448 }
00449 
00450 - (id)valueForKeyPath:(CPString)aKeyPath
00451 {
00452     if (aKeyPath.charAt(0) === "@")
00453     {
00454         var dotIndex = aKeyPath.indexOf("."),
00455             operator,
00456             parameter;
00457 
00458         if (dotIndex !== -1)
00459         {
00460             operator = aKeyPath.substring(1, dotIndex);
00461             parameter = aKeyPath.substring(dotIndex + 1);
00462         }
00463         else
00464             operator = aKeyPath.substring(1);
00465 
00466         if (kvoOperators[operator])
00467             return kvoOperators[operator](self, _cmd, parameter);
00468 
00469         return nil;
00470     }
00471     else
00472     {
00473         var newArray = [],
00474             enumerator = [self objectEnumerator],
00475             object;
00476 
00477         while ((object = [enumerator nextObject]) !== nil)
00478         {
00479             var value = [object valueForKeyPath:aKeyPath];
00480 
00481             if (value === nil || value === undefined)
00482                 value = [CPNull null];
00483 
00484             newArray.push(value);
00485         }
00486 
00487         return newArray;
00488     }
00489 }
00490 
00491 - (void)setValue:(id)aValue forKey:(CPString)aKey
00492 {
00493     var enumerator = [self objectEnumerator],
00494         object;
00495 
00496     while (object = [enumerator nextObject])
00497         [object setValue:aValue forKey:aKey];
00498 }
00499 
00500 - (void)setValue:(id)aValue forKeyPath:(CPString)aKeyPath
00501 {
00502     var enumerator = [self objectEnumerator],
00503         object;
00504 
00505     while (object = [enumerator nextObject])
00506         [object setValue:aValue forKeyPath:aKeyPath];
00507 }
00508 
00509 @end
00510 
00511 var kvoOperators = [];
00512 
00513 // HACK: prevent these from becoming globals. workaround for obj-j "function foo(){}" behavior
00514 var avgOperator,
00515     maxOperator,
00516     minOperator,
00517     countOperator,
00518     sumOperator;
00519 
00520 kvoOperators["avg"] = function avgOperator(self, _cmd, param)
00521 {
00522     var objects = [self valueForKeyPath:param],
00523         length = [objects count],
00524         index = length,
00525         average = 0.0;
00526 
00527     if (!length)
00528         return 0;
00529 
00530     while (index--)
00531         average += [objects[index] doubleValue];
00532 
00533     return average / length;
00534 }
00535 
00536 kvoOperators["max"] = function maxOperator(self, _cmd, param)
00537 {
00538     var objects = [self valueForKeyPath:param],
00539         index = [objects count] - 1,
00540         max = [objects lastObject];
00541 
00542     while (index--)
00543     {
00544         var item = objects[index];
00545         if ([max compare:item] < 0)
00546             max = item;
00547     }
00548 
00549     return max;
00550 }
00551 
00552 kvoOperators["min"] = function minOperator(self, _cmd, param)
00553 {
00554     var objects = [self valueForKeyPath:param],
00555         index = [objects count] - 1,
00556         min = [objects lastObject];
00557 
00558     while (index--)
00559     {
00560         var item = objects[index];
00561         if ([min compare:item] > 0)
00562             min = item;
00563     }
00564 
00565     return min;
00566 }
00567 
00568 kvoOperators["count"] = function countOperator(self, _cmd, param)
00569 {
00570     return [self count];
00571 }
00572 
00573 kvoOperators["sum"] = function sumOperator(self, _cmd, param)
00574 {
00575     var objects = [self valueForKeyPath:param],
00576         index = [objects count],
00577         sum = 0.0;
00578 
00579     while (index--)
00580         sum += [objects[index] doubleValue];
00581 
00582     return sum;
00583 }
00584 
00585 @implementation CPArray (KeyValueObserving)
00586 
00587 - (void)addObserver:(id)anObserver toObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
00588 {
00589     var index = [indexes firstIndex];
00590 
00591     while (index >= 0)
00592     {
00593         [self[index] addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
00594 
00595         index = [indexes indexGreaterThanIndex:index];
00596     }
00597 }
00598 
00599 - (void)removeObserver:(id)anObserver fromObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath
00600 {
00601     var index = [indexes firstIndex];
00602 
00603     while (index >= 0)
00604     {
00605         [self[index] removeObserver:anObserver forKeyPath:aKeyPath];
00606 
00607         index = [indexes indexGreaterThanIndex:index];
00608     }
00609 }
00610 
00611 - (void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context
00612 {
00613     if ([isa instanceMethodForSelector:_cmd] === [CPArray instanceMethodForSelector:_cmd])
00614         [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"];
00615     else
00616         [super addObserver:observer forKeyPath:aKeyPath options:options context:context];
00617 }
00618 
00619 - (void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath
00620 {
00621     if ([isa instanceMethodForSelector:_cmd] === [CPArray instanceMethodForSelector:_cmd])
00622         [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPArray"];
00623     else
00624         [super removeObserver:observer forKeyPath:aKeyPath];
00625 }
00626 
00627 @end
 All Classes Files Functions Variables Defines