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