![]() |
API 0.9.5
|
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