API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPArray+KVO.j
Go to the documentation of this file.
1 /*
2  * CPArray+KVO.j
3  * Foundation
4  *
5  * Created by Ross Boucher.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @class CPIndexSet
25 
26 @implementation CPObject (CPArrayKVO)
27 
28 - (id)mutableArrayValueForKey:(id)aKey
29 {
30  return [[_CPKVCArray alloc] initWithKey:aKey forProxyObject:self];
31 }
32 
33 - (id)mutableArrayValueForKeyPath:(id)aKeyPath
34 {
35  var dotIndex = aKeyPath.indexOf(".");
36 
37  if (dotIndex < 0)
38  return [self mutableArrayValueForKey:aKeyPath];
39 
40  var firstPart = aKeyPath.substring(0, dotIndex),
41  lastPart = aKeyPath.substring(dotIndex + 1);
42 
43  return [[self valueForKeyPath:firstPart] mutableArrayValueForKeyPath:lastPart];
44 }
45 
46 @end
47 
48 @implementation _CPKVCArray : CPMutableArray
49 {
50  id _proxyObject;
51  id _key;
52 
53  SEL _insertSEL;
54  Function _insert;
55 
56  SEL _removeSEL;
57  Function _remove;
58 
59  SEL _replaceSEL;
60  Function _replace;
61 
62  SEL _insertManySEL;
63  Function _insertMany;
64 
65  SEL _removeManySEL;
66  Function _removeMany;
67 
68  SEL _replaceManySEL;
69  Function _replaceMany;
70 
71  SEL _objectAtIndexSEL;
72  Function _objectAtIndex;
73 
74  SEL _objectsAtIndexesSEL;
75  Function _objectsAtIndexes;
76 
77  SEL _countSEL;
78  Function _count;
79 
80  SEL _accessSEL;
81  Function _access;
82 
83  SEL _setSEL;
84  Function _set;
85 }
86 
87 + (id)alloc
88 {
89  var array = [];
90 
91  array.isa = self;
92 
93  var ivars = class_copyIvarList(self),
94  count = ivars.length;
95 
96  while (count--)
97  array[ivar_getName(ivars[count])] = nil;
98 
99  return array;
100 }
101 
102 - (id)initWithKey:(id)aKey forProxyObject:(id)anObject
103 {
104  self = [super init];
105 
106  _key = aKey;
107  _proxyObject = anObject;
108 
109  var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1);
110 
111  _insertSEL = sel_getName(@"insertObject:in" + capitalizedKey + "AtIndex:");
112 
113  if ([_proxyObject respondsToSelector:_insertSEL])
114  _insert = [_proxyObject methodForSelector:_insertSEL];
115 
116  _removeSEL = sel_getName(@"removeObjectFrom" + capitalizedKey + "AtIndex:");
117 
118  if ([_proxyObject respondsToSelector:_removeSEL])
119  _remove = [_proxyObject methodForSelector:_removeSEL];
120 
121  _replaceSEL = sel_getName(@"replaceObjectIn" + capitalizedKey + "AtIndex:withObject:");
122 
123  if ([_proxyObject respondsToSelector:_replaceSEL])
124  _replace = [_proxyObject methodForSelector:_replaceSEL];
125 
126  _insertManySEL = sel_getName(@"insert" + capitalizedKey + ":atIndexes:");
127 
128  if ([_proxyObject respondsToSelector:_insertManySEL])
129  _insertMany = [_proxyObject methodForSelector:_insertManySEL];
130 
131  _removeManySEL = sel_getName(@"remove" + capitalizedKey + "AtIndexes:");
132 
133  if ([_proxyObject respondsToSelector:_removeManySEL])
134  _removeMany = [_proxyObject methodForSelector:_removeManySEL];
135 
136  _replaceManySEL = sel_getName(@"replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":");
137 
138  if ([_proxyObject respondsToSelector:_replaceManySEL])
139  _replaceMany = [_proxyObject methodForSelector:_replaceManySEL];
140 
141  _objectAtIndexSEL = sel_getName(@"objectIn" + capitalizedKey + "AtIndex:");
142 
143  if ([_proxyObject respondsToSelector:_objectAtIndexSEL])
144  _objectAtIndex = [_proxyObject methodForSelector:_objectAtIndexSEL];
145 
146  _objectsAtIndexesSEL = sel_getName(_key + "AtIndexes:");
147 
148  if ([_proxyObject respondsToSelector:_objectsAtIndexesSEL])
149  _objectsAtIndexes = [_proxyObject methodForSelector:_objectsAtIndexesSEL];
150 
151  _countSEL = sel_getName(@"countOf" + capitalizedKey);
152 
153  if ([_proxyObject respondsToSelector:_countSEL])
154  _count = [_proxyObject methodForSelector:_countSEL];
155 
156  _accessSEL = sel_getName(_key);
157 
158  if ([_proxyObject respondsToSelector:_accessSEL])
159  _access = [_proxyObject methodForSelector:_accessSEL];
160 
161  _setSEL = sel_getName(@"set" + capitalizedKey + ":");
162 
163  if ([_proxyObject respondsToSelector:_setSEL])
164  _set = [_proxyObject methodForSelector:_setSEL];
165 
166  return self;
167 }
168 
169 - (id)copy
170 {
171  var i = 0,
172  theCopy = [],
173  count = [self count];
174 
175  for (; i < count; i++)
176  [theCopy addObject:[self objectAtIndex:i]];
177 
178  return theCopy;
179 }
180 
181 - (id)_representedObject
182 {
183  if (_access)
184  return _access(_proxyObject, _accessSEL);
185 
186  return [_proxyObject valueForKey:_key];
187 }
188 
189 - (void)_setRepresentedObject:(id)anObject
190 {
191  if (_set)
192  return _set(_proxyObject, _setSEL, anObject);
193 
194  [_proxyObject setValue:anObject forKey:_key];
195 }
196 
197 - (CPUInteger)count
198 {
199  if (_count)
200  return _count(_proxyObject, _countSEL);
201 
202  return [[self _representedObject] count];
203 }
204 
205 - (CPUInteger)indexOfObject:(id)anObject inRange:(CPRange)aRange
206 {
207  var index = aRange.location,
208  count = aRange.length,
209  shouldIsEqual = !!anObject.isa;
210 
211  for (; index < count; ++index)
212  {
213  var object = [self objectAtIndex:index];
214 
215  if (anObject === object || shouldIsEqual && !!object.isa && [anObject isEqual:object])
216  return index;
217  }
218 
219  return CPNotFound;
220 }
221 
222 - (CPUInteger)indexOfObject:(id)anObject
223 {
224  return [self indexOfObject:anObject inRange:CPMakeRange(0, [self count])];
225 }
226 
227 - (CPUInteger)indexOfObjectIdenticalTo:(id)anObject inRange:(CPRange)aRange
228 {
229  var index = aRange.location,
230  count = aRange.length;
231 
232  for (; index < count; ++index)
233  if (anObject === [self objectAtIndex:index])
234  return index;
235 
236  return CPNotFound;
237 }
238 
239 - (CPUInteger)indexOfObjectIdenticalTo:(id)anObject
240 {
241  return [self indexOfObjectIdenticalTo:anObject inRange:CPMakeRange(0, [self count])];
242 }
243 
244 - (id)objectAtIndex:(CPUInteger)anIndex
245 {
246  return [[self objectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]] firstObject];
247 }
248 
249 - (CPArray)objectsAtIndexes:(CPIndexSet)theIndexes
250 {
251  if (_objectsAtIndexes)
252  return _objectsAtIndexes(_proxyObject, _objectsAtIndexesSEL, theIndexes);
253 
254  if (_objectAtIndex)
255  {
256  var index = CPNotFound,
257  objects = [];
258 
259  while ((index = [theIndexes indexGreaterThanIndex:index]) !== CPNotFound)
260  objects.push(_objectAtIndex(_proxyObject, _objectAtIndexSEL, index));
261 
262  return objects;
263  }
264 
265  return [[self _representedObject] objectsAtIndexes:theIndexes];
266 }
267 
268 - (void)addObject:(id)anObject
269 {
270  [self insertObject:anObject atIndex:[self count]];
271 }
272 
273 - (void)addObjectsFromArray:(CPArray)anArray
274 {
275  var index = 0,
276  count = [anArray count];
277 
278  [self insertObjects:anArray atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange([self count], count)]];
279 }
280 
281 - (void)insertObject:(id)anObject atIndex:(CPUInteger)anIndex
282 {
283  [self insertObjects:[anObject] atIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
284 }
285 
286 - (void)insertObjects:(CPArray)theObjects atIndexes:(CPIndexSet)theIndexes
287 {
288  if (_insertMany)
289  _insertMany(_proxyObject, _insertManySEL, theObjects, theIndexes);
290  else if (_insert)
291  {
292  var indexesArray = [];
293  [theIndexes getIndexes:indexesArray maxCount:-1 inIndexRange:nil];
294 
295  for (var index = 0; index < [indexesArray count]; index++)
296  {
297  var objectIndex = [indexesArray objectAtIndex:index],
298  object = [theObjects objectAtIndex:index];
299 
300  _insert(_proxyObject, _insertSEL, object, objectIndex);
301  }
302  }
303  else
304  {
305  var target = [[self _representedObject] copy];
306 
307  [target insertObjects:theObjects atIndexes:theIndexes];
308  [self _setRepresentedObject:target];
309  }
310 }
311 
312 - (void)removeObject:(id)anObject
313 {
314  [self removeObject:anObject inRange:CPMakeRange(0, [self count])];
315 }
316 
317 - (void)removeObjectsInArray:(CPArray)theObjects
318 {
319  if (_removeMany)
320  {
321  var indexes = [CPIndexSet indexSet],
322  index = [theObjects count],
323  position = 0,
324  count = [self count];
325 
326  while (index--)
327  {
328  while ((position = [self indexOfObject:[theObjects objectAtIndex:index] inRange:CPMakeRange(position + 1, count)]) !== CPNotFound)
329  [indexes addIndex:position];
330  }
331 
332  _removeMany(_proxyObject, _removeManySEL, indexes);
333  }
334  else if (_remove)
335  {
336  var index = [theObjects count],
337  position;
338  while (index--)
339  {
340  while ((position = [self indexOfObject:[theObjects objectAtIndex:index]]) !== CPNotFound)
341  _remove(_proxyObject, _removeSEL, position);
342  }
343  }
344  else
345  {
346  var target = [[self _representedObject] copy];
347  [target removeObjectsInArray:theObjects];
348  [self _setRepresentedObject:target];
349  }
350 }
351 
352 - (void)removeObject:(id)theObject inRange:(CPRange)theRange
353 {
354  if (_remove)
355  _remove(_proxyObject, _removeSEL, [self indexOfObject:theObject inRange:theRange]);
356  else if (_removeMany)
357  {
358  var index = [self indexOfObject:theObject inRange:theRange];
359  _removeMany(_proxyObject, _removeManySEL, [CPIndexSet indexSetWithIndex:index]);
360  }
361  else
362  {
363  var index;
364 
365  while ((index = [self indexOfObject:theObject inRange:theRange]) !== CPNotFound)
366  {
367  [self removeObjectAtIndex:index];
368  theRange = CPIntersectionRange(CPMakeRange(index, self.length - index), theRange);
369  }
370  }
371 }
372 
373 - (void)removeLastObject
374 {
375  [self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:[self count] - 1]];
376 }
377 
378 - (void)removeObjectAtIndex:(CPUInteger)anIndex
379 {
380  [self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
381 }
382 
383 - (void)removeObjectsAtIndexes:(CPIndexSet)theIndexes
384 {
385  if (_removeMany)
386  _removeMany(_proxyObject, _removeManySEL, theIndexes);
387  else if (_remove)
388  {
389  var index = [theIndexes lastIndex];
390 
391  while (index !== CPNotFound)
392  {
393  _remove(_proxyObject, _removeSEL, index)
394  index = [theIndexes indexLessThanIndex:index];
395  }
396  }
397  else
398  {
399  var target = [[self _representedObject] copy];
400  [target removeObjectsAtIndexes:theIndexes];
401  [self _setRepresentedObject:target];
402  }
403 }
404 
405 - (void)replaceObjectAtIndex:(CPUInteger)anIndex withObject:(id)anObject
406 {
407  [self replaceObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] withObjects:[anObject]]
408 }
409 
410 - (void)replaceObjectsAtIndexes:(CPIndexSet)theIndexes withObjects:(CPArray)theObjects
411 {
412  if (_replaceMany)
413  return _replaceMany(_proxyObject, _replaceManySEL, theIndexes, theObjects);
414  else if (_replace)
415  {
416  var i = 0,
417  index = [theIndexes firstIndex];
418 
419  while (index !== CPNotFound)
420  {
421  _replace(_proxyObject, _replaceSEL, index, [theObjects objectAtIndex:i++]);
422  index = [theIndexes indexGreaterThanIndex:index];
423  }
424  }
425  else
426  {
427  var target = [[self _representedObject] copy];
428  [target replaceObjectsAtIndexes:theIndexes withObjects:theObjects];
429  [self _setRepresentedObject:target];
430  }
431 }
432 
433 @end
434 
435 
436 // KVC on CPArray objects act on each item of the array, rather than on the array itself
437 
438 @implementation CPArray (CPKeyValueCoding)
439 
440 - (id)valueForKey:(CPString)aKey
441 {
442  if (aKey.charAt(0) === "@")
443  {
444  if (aKey.indexOf(".") !== -1)
445  [CPException raise:CPInvalidArgumentException reason:"called valueForKey: on an array with a complex key (" + aKey + "). use valueForKeyPath:"];
446 
447  if (aKey === "@count")
448  return self.length;
449 
450  return [self valueForUndefinedKey:aKey];
451  }
452  else
453  {
454  var newArray = [],
455  enumerator = [self objectEnumerator],
456  object;
457 
458  while ((object = [enumerator nextObject]) !== nil)
459  {
460  var value = [object valueForKey:aKey];
461 
462  if (value === nil || value === undefined)
463  value = [CPNull null];
464 
465  newArray.push(value);
466  }
467 
468  return newArray;
469  }
470 }
471 
472 - (id)valueForKeyPath:(CPString)aKeyPath
473 {
474  if (!aKeyPath)
475  [self valueForUndefinedKey:@"<empty path>"];
476 
477  if (aKeyPath.charAt(0) === "@")
478  {
479  var dotIndex = aKeyPath.indexOf("."),
480  operator,
481  parameter;
482 
483  if (dotIndex !== -1)
484  {
485  operator = aKeyPath.substring(1, dotIndex);
486  parameter = aKeyPath.substring(dotIndex + 1);
487  }
488  else
489  operator = aKeyPath.substring(1);
490 
491  return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter];
492  }
493  else
494  {
495  var newArray = [],
496  enumerator = [self objectEnumerator],
497  object;
498 
499  while ((object = [enumerator nextObject]) !== nil)
500  {
501  var value = [object valueForKeyPath:aKeyPath];
502 
503  if (value === nil || value === undefined)
504  value = [CPNull null];
505 
506  newArray.push(value);
507  }
508 
509  return newArray;
510  }
511 }
512 
513 - (void)setValue:(id)aValue forKey:(CPString)aKey
514 {
515  var enumerator = [self objectEnumerator],
516  object;
517 
518  while ((object = [enumerator nextObject]) !== nil)
519  [object setValue:aValue forKey:aKey];
520 }
521 
522 - (void)setValue:(id)aValue forKeyPath:(CPString)aKeyPath
523 {
524  var enumerator = [self objectEnumerator],
525  object;
526 
527  while ((object = [enumerator nextObject]) !== nil)
528  [object setValue:aValue forKeyPath:aKeyPath];
529 }
530 
531 @end
532 
533 @implementation CPArray (KeyValueObserving)
534 
542 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)anOptions context:(id)aContext
543 {
544  if (aKeyPath !== @"@count")
545  [CPException raise:CPInvalidArgumentException reason:"[CPArray " + CPStringFromSelector(_cmd) + "] is not supported. Key path: " + aKeyPath];
546 }
547 
555 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
556 {
557  if (aKeyPath !== @"@count")
558  [CPException raise:CPInvalidArgumentException reason:"[CPArray " + CPStringFromSelector(_cmd) + "] is not supported. Key path: " + aKeyPath];
559 }
560 
564 - (void)addObserver:(id)anObserver toObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
565 {
566  var index = [indexes firstIndex];
567 
568  while (index >= 0)
569  {
570  [self[index] addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
571 
572  index = [indexes indexGreaterThanIndex:index];
573  }
574 }
575 
579 - (void)removeObserver:(id)anObserver fromObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath
580 {
581  var index = [indexes firstIndex];
582 
583  while (index >= 0)
584  {
585  [self[index] removeObserver:anObserver forKeyPath:aKeyPath];
586 
587  index = [indexes indexGreaterThanIndex:index];
588  }
589 }
590 
591 @end