API  1.0.0
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 
112  if ([_proxyObject respondsToSelector:_insertSEL])
113  _insert = [_proxyObject methodForSelector:_insertSEL];
114 
115  _removeSEL = sel_getName(@"removeObjectFrom" + capitalizedKey + "AtIndex:");
116 
117  if ([_proxyObject respondsToSelector:_removeSEL])
118  _remove = [_proxyObject methodForSelector:_removeSEL];
119 
120  _replaceSEL = sel_getName(@"replaceObjectIn" + capitalizedKey + "AtIndex:withObject:");
121 
122  if ([_proxyObject respondsToSelector:_replaceSEL])
123  _replace = [_proxyObject methodForSelector:_replaceSEL];
124 
125  _insertManySEL = sel_getName(@"insert" + capitalizedKey + ":atIndexes:");
126 
127  if ([_proxyObject respondsToSelector:_insertManySEL])
128  _insertMany = [_proxyObject methodForSelector:_insertManySEL];
129 
130  _removeManySEL = sel_getName(@"remove" + capitalizedKey + "AtIndexes:");
131 
132  if ([_proxyObject respondsToSelector:_removeManySEL])
133  _removeMany = [_proxyObject methodForSelector:_removeManySEL];
134 
135  _replaceManySEL = sel_getName(@"replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":");
136 
137  if ([_proxyObject respondsToSelector:_replaceManySEL])
138  _replaceMany = [_proxyObject methodForSelector:_replaceManySEL];
139 
140  _objectAtIndexSEL = sel_getName(@"objectIn" + capitalizedKey + "AtIndex:");
141 
142  if ([_proxyObject respondsToSelector:_objectAtIndexSEL])
143  _objectAtIndex = [_proxyObject methodForSelector:_objectAtIndexSEL];
144 
145  _objectsAtIndexesSEL = sel_getName(_key + "AtIndexes:");
146 
147  if ([_proxyObject respondsToSelector:_objectsAtIndexesSEL])
148  _objectsAtIndexes = [_proxyObject methodForSelector:_objectsAtIndexesSEL];
149 
150  _countSEL = sel_getName(@"countOf" + capitalizedKey);
151 
152  if ([_proxyObject respondsToSelector:_countSEL])
153  _count = [_proxyObject methodForSelector:_countSEL];
154 
155  _accessSEL = sel_getName(_key);
156 
157  if ([_proxyObject respondsToSelector:_accessSEL])
158  _access = [_proxyObject methodForSelector:_accessSEL];
159 
160  _setSEL = sel_getName(@"set" + capitalizedKey + ":");
161 
162  if ([_proxyObject respondsToSelector:_setSEL])
163  _set = [_proxyObject methodForSelector:_setSEL];
164 
165  return self;
166 }
167 
168 - (id)copy
169 {
170  var i = 0,
171  theCopy = [],
172  count = [self count];
173 
174  for (; i < count; i++)
175  [theCopy addObject:[self objectAtIndex:i]];
176 
177  return theCopy;
178 }
179 
180 - (id)_representedObject
181 {
182  if (_access)
183  return _access(_proxyObject, _accessSEL);
184 
185  return [_proxyObject valueForKey:_key];
186 }
187 
188 - (void)_setRepresentedObject:(id)anObject
189 {
190  if (_set)
191  return _set(_proxyObject, _setSEL, anObject);
192 
193  [_proxyObject setValue:anObject forKey:_key];
194 }
195 
196 - (CPUInteger)count
197 {
198  if (_count)
199  return _count(_proxyObject, _countSEL);
200 
201  return [[self _representedObject] count];
202 }
203 
204 - (CPUInteger)indexOfObject:(id)anObject inRange:(CPRange)aRange
205 {
206  var index = aRange.location,
207  count = aRange.length,
208  shouldIsEqual = !!anObject.isa;
209 
210  for (; index < count; ++index)
211  {
212  var object = [self objectAtIndex:index];
213 
214  if (anObject === object || shouldIsEqual && !!object.isa && [anObject isEqual:object])
215  return index;
216  }
217 
218  return CPNotFound;
219 }
220 
221 - (CPUInteger)indexOfObject:(id)anObject
222 {
223  return [self indexOfObject:anObject inRange:CPMakeRange(0, [self count])];
224 }
225 
226 - (CPUInteger)indexOfObjectIdenticalTo:(id)anObject inRange:(CPRange)aRange
227 {
228  var index = aRange.location,
229  count = aRange.length;
230 
231  for (; index < count; ++index)
232  if (anObject === [self objectAtIndex:index])
233  return index;
234 
235  return CPNotFound;
236 }
237 
238 - (CPUInteger)indexOfObjectIdenticalTo:(id)anObject
239 {
240  return [self indexOfObjectIdenticalTo:anObject inRange:CPMakeRange(0, [self count])];
241 }
242 
243 - (id)objectAtIndex:(CPUInteger)anIndex
244 {
245  return [[self objectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]] firstObject];
246 }
247 
248 - (CPArray)objectsAtIndexes:(CPIndexSet)theIndexes
249 {
250  if (_objectsAtIndexes)
251  return _objectsAtIndexes(_proxyObject, _objectsAtIndexesSEL, theIndexes);
252 
253  if (_objectAtIndex)
254  {
255  var index = CPNotFound,
256  objects = [];
257 
258  while ((index = [theIndexes indexGreaterThanIndex:index]) !== CPNotFound)
259  objects.push(_objectAtIndex(_proxyObject, _objectAtIndexSEL, index));
260 
261  return objects;
262  }
263 
264  return [[self _representedObject] objectsAtIndexes:theIndexes];
265 }
266 
267 - (void)addObject:(id)anObject
268 {
269  [self insertObject:anObject atIndex:[self count]];
270 }
271 
272 - (void)addObjectsFromArray:(CPArray)anArray
273 {
274  var index = 0,
275  count = [anArray count];
276 
277  [self insertObjects:anArray atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange([self count], count)]];
278 }
279 
280 - (void)insertObject:(id)anObject atIndex:(CPUInteger)anIndex
281 {
282  [self insertObjects:[anObject] atIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
283 }
284 
285 - (void)insertObjects:(CPArray)theObjects atIndexes:(CPIndexSet)theIndexes
286 {
287  if (_insertMany)
288  _insertMany(_proxyObject, _insertManySEL, theObjects, theIndexes);
289  else if (_insert)
290  {
291  var indexesArray = [];
292  [theIndexes getIndexes:indexesArray maxCount:-1 inIndexRange:nil];
293 
294  for (var index = 0; index < [indexesArray count]; index++)
295  {
296  var objectIndex = [indexesArray objectAtIndex:index],
297  object = [theObjects objectAtIndex:index];
298 
299  _insert(_proxyObject, _insertSEL, object, objectIndex);
300  }
301  }
302  else
303  {
304  var target = [[self _representedObject] copy];
305 
306  [target insertObjects:theObjects atIndexes:theIndexes];
307  [self _setRepresentedObject:target];
308  }
309 }
310 
311 - (void)removeObject:(id)anObject
312 {
313  [self removeObject:anObject inRange:CPMakeRange(0, [self count])];
314 }
315 
316 - (void)removeObjectsInArray:(CPArray)theObjects
317 {
318  if (_removeMany)
319  {
320  var indexes = [CPIndexSet indexSet],
321  index = [theObjects count],
322  position = 0,
323  count = [self count];
324 
325  while (index--)
326  {
327  while ((position = [self indexOfObject:[theObjects objectAtIndex:index] inRange:CPMakeRange(position + 1, count)]) !== CPNotFound)
328  [indexes addIndex:position];
329  }
330 
331  _removeMany(_proxyObject, _removeManySEL, indexes);
332  }
333  else if (_remove)
334  {
335  var index = [theObjects count],
336  position;
337  while (index--)
338  {
339  while ((position = [self indexOfObject:[theObjects objectAtIndex:index]]) !== CPNotFound)
340  _remove(_proxyObject, _removeSEL, position);
341  }
342  }
343  else
344  {
345  var target = [[self _representedObject] copy];
346  [target removeObjectsInArray:theObjects];
347  [self _setRepresentedObject:target];
348  }
349 }
350 
351 - (void)removeObject:(id)theObject inRange:(CPRange)theRange
352 {
353  if (_remove)
354  _remove(_proxyObject, _removeSEL, [self indexOfObject:theObject inRange:theRange]);
355  else if (_removeMany)
356  {
357  var index = [self indexOfObject:theObject inRange:theRange];
358  _removeMany(_proxyObject, _removeManySEL, [CPIndexSet indexSetWithIndex:index]);
359  }
360  else
361  {
362  var index;
363 
364  while ((index = [self indexOfObject:theObject inRange:theRange]) !== CPNotFound)
365  {
366  [self removeObjectAtIndex:index];
367  theRange = CPIntersectionRange(CPMakeRange(index, self.length - index), theRange);
368  }
369  }
370 }
371 
372 - (void)removeLastObject
373 {
374  [self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:[self count] - 1]];
375 }
376 
377 - (void)removeObjectAtIndex:(CPUInteger)anIndex
378 {
379  [self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
380 }
381 
382 - (void)removeObjectsAtIndexes:(CPIndexSet)theIndexes
383 {
384  if (_removeMany)
385  _removeMany(_proxyObject, _removeManySEL, theIndexes);
386  else if (_remove)
387  {
388  var index = [theIndexes lastIndex];
389 
390  while (index !== CPNotFound)
391  {
392  _remove(_proxyObject, _removeSEL, index);
393  index = [theIndexes indexLessThanIndex:index];
394  }
395  }
396  else
397  {
398  var target = [[self _representedObject] copy];
399  [target removeObjectsAtIndexes:theIndexes];
400  [self _setRepresentedObject:target];
401  }
402 }
403 
404 - (void)replaceObjectAtIndex:(CPUInteger)anIndex withObject:(id)anObject
405 {
406  [self replaceObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] withObjects:[anObject]]
407 }
408 
409 - (void)replaceObjectsAtIndexes:(CPIndexSet)theIndexes withObjects:(CPArray)theObjects
410 {
411  if (_replaceMany)
412  return _replaceMany(_proxyObject, _replaceManySEL, theIndexes, theObjects);
413  else if (_replace)
414  {
415  var i = 0,
416  index = [theIndexes firstIndex];
417 
418  while (index !== CPNotFound)
419  {
420  _replace(_proxyObject, _replaceSEL, index, [theObjects objectAtIndex:i++]);
421  index = [theIndexes indexGreaterThanIndex:index];
422  }
423  }
424  else
425  {
426  var target = [[self _representedObject] copy];
427  [target replaceObjectsAtIndexes:theIndexes withObjects:theObjects];
428  [self _setRepresentedObject:target];
429  }
430 }
431 
432 @end
433 
434 
435 // KVC on CPArray objects act on each item of the array, rather than on the array itself
436 
438 
439 - (id)valueForKey:(CPString)aKey
440 {
441  if (aKey.charAt(0) === "@")
442  {
443  if (aKey.indexOf(".") !== -1)
444  [CPException raise:CPInvalidArgumentException reason:"called valueForKey: on an array with a complex key (" + aKey + "). use valueForKeyPath:"];
445 
446  if (aKey === "@count")
447  return self.length;
448 
449  return [self valueForUndefinedKey:aKey];
450  }
451  else
452  {
453  var newArray = [],
454  enumerator = [self objectEnumerator],
455  object;
456 
457  while ((object = [enumerator nextObject]) !== nil)
458  {
459  var value = [object valueForKey:aKey];
460 
461  if (value === nil || value === undefined)
462  value = [CPNull null];
463 
464  newArray.push(value);
465  }
466 
467  return newArray;
468  }
469 }
470 
471 - (id)valueForKeyPath:(CPString)aKeyPath
472 {
473  if (!aKeyPath)
474  [self valueForUndefinedKey:@"<empty path>"];
475 
476  if (aKeyPath.charAt(0) === "@")
477  {
478  var dotIndex = aKeyPath.indexOf("."),
479  operator,
480  parameter;
481 
482  if (dotIndex !== -1)
483  {
484  operator = aKeyPath.substring(1, dotIndex);
485  parameter = aKeyPath.substring(dotIndex + 1);
486  }
487  else
488  operator = aKeyPath.substring(1);
489 
490  return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter];
491  }
492  else
493  {
494  var newArray = [],
495  enumerator = [self objectEnumerator],
496  object;
497 
498  while ((object = [enumerator nextObject]) !== nil)
499  {
500  var value = [object valueForKeyPath:aKeyPath];
501 
502  if (value === nil || value === undefined)
503  value = [CPNull null];
504 
505  newArray.push(value);
506  }
507 
508  return newArray;
509  }
510 }
511 
512 - (void)setValue:(id)aValue forKey:(CPString)aKey
513 {
514  var enumerator = [self objectEnumerator],
515  object;
516 
517  while ((object = [enumerator nextObject]) !== nil)
518  [object setValue:aValue forKey:aKey];
519 }
520 
521 - (void)setValue:(id)aValue forKeyPath:(CPString)aKeyPath
522 {
523  var enumerator = [self objectEnumerator],
524  object;
525 
526  while ((object = [enumerator nextObject]) !== nil)
527  [object setValue:aValue forKeyPath:aKeyPath];
528 }
529 
530 @end
531 
533 
541 - (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)anOptions context:(id)aContext
542 {
543  if (aKeyPath !== @"@count")
544  [CPException raise:CPInvalidArgumentException reason:"[CPArray " + CPStringFromSelector(_cmd) + "] is not supported. Key path: " + aKeyPath];
545 }
546 
554 - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
555 {
556  if (aKeyPath !== @"@count")
557  [CPException raise:CPInvalidArgumentException reason:"[CPArray " + CPStringFromSelector(_cmd) + "] is not supported. Key path: " + aKeyPath];
558 }
559 
563 - (void)addObserver:(id)anObserver toObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
564 {
565  var index = [indexes firstIndex];
566 
567  while (index >= 0)
568  {
569  [self[index] addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
570 
571  index = [indexes indexGreaterThanIndex:index];
572  }
573 }
574 
578 - (void)removeObserver:(id)anObserver fromObjectsAtIndexes:(CPIndexSet)indexes forKeyPath:(CPString)aKeyPath
579 {
580  var index = [indexes firstIndex];
581 
582  while (index >= 0)
583  {
584  [self[index] removeObserver:anObserver forKeyPath:aKeyPath];
585 
586  index = [indexes indexGreaterThanIndex:index];
587  }
588 }
589 
590 @end
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
CPInteger lastIndex()
Definition: CPIndexSet.j:289
CGPoint position()
Definition: CALayer.j:225
var isEqual
An object representation of nil.
Definition: CPNull.h:2
CFData prototype isa
Definition: CPData.j:214
CPInteger getIndexes:maxCount:inIndexRange:(CPArray anArray, [maxCount] CPInteger aMaxCount, [inIndexRange] CPRange aRange)
Definition: CPIndexSet.j:386
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
A collection of unique integers.
Definition: CPIndexSet.h:2
CPInteger firstIndex()
Definition: CPIndexSet.j:278
CPInvalidArgumentException
Definition: CPException.j:25
id mutableArrayValueForKey:(id aKey)
Definition: CPArray+KVO.j:27
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
if(CPFeatureIsCompatible(CPHTMLCanvasFeature))
CPInteger indexGreaterThanIndex:(CPInteger anIndex)
Definition: CPIndexSet.j:301
function CPIntersectionRange(lhsRange, rhsRange)
Definition: CPRange.j:120
int length()
Definition: CPString.j:186
id valueForKeyPath:(CPString aKeyPath)
CPNotFound
Definition: CPObjJRuntime.j:62
CPInteger indexLessThanIndex:(CPInteger anIndex)
Definition: CPIndexSet.j:332
id indexSetWithIndexesInRange:(CPRange aRange)
Definition: CPIndexSet.j:60
id indexSetWithIndex:(int anIndex)
Definition: CPIndexSet.j:51
CPRange function CPMakeRange(location, length)
Definition: CPRange.j:37
id indexSet()
Definition: CPIndexSet.j:43