PageRenderTime 164ms CodeModel.GetById 151ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

/Foundation/CPKeyValueObserving.j

http://github.com/cacaodev/cappuccino
Unknown | 1360 lines | 1064 code | 296 blank | 0 comment | 0 complexity | 45183e325cba3acf780edaff0a8d639f MD5 | raw file
   1/*
   2 * CPKeyValueObserving.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@import "CPArray.j"
  24@import "CPDictionary.j"
  25@import "CPException.j"
  26@import "CPIndexSet.j"
  27@import "CPNull.j"
  28@import "CPObject.j"
  29@import "CPSet.j"
  30
  31@implementation CPObject (KeyValueObserving)
  32
  33- (void)willChangeValueForKey:(CPString)aKey
  34{
  35    if (!aKey)
  36        return;
  37
  38    if (!self[KVOProxyKey])
  39    {
  40        if (!self._willChangeMessageCounter)
  41            self._willChangeMessageCounter = new Object();
  42
  43        if (!self._willChangeMessageCounter[aKey])
  44            self._willChangeMessageCounter[aKey] = 1;
  45        else
  46            self._willChangeMessageCounter[aKey] += 1;
  47    }
  48}
  49
  50- (void)didChangeValueForKey:(CPString)aKey
  51{
  52    if (!aKey)
  53        return;
  54
  55    if (!self[KVOProxyKey])
  56    {
  57        if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
  58        {
  59            self._willChangeMessageCounter[aKey] -= 1;
  60
  61            if (!self._willChangeMessageCounter[aKey])
  62                delete self._willChangeMessageCounter[aKey];
  63        }
  64        else
  65            [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
  66    }
  67}
  68
  69- (void)willChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
  70{
  71    if (!aKey)
  72        return;
  73
  74    if (!self[KVOProxyKey])
  75    {
  76        if (!self._willChangeMessageCounter)
  77            self._willChangeMessageCounter = new Object();
  78
  79        if (!self._willChangeMessageCounter[aKey])
  80            self._willChangeMessageCounter[aKey] = 1;
  81        else
  82            self._willChangeMessageCounter[aKey] += 1;
  83    }
  84}
  85
  86- (void)didChange:(CPKeyValueChange)aChange valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
  87{
  88    if (!aKey)
  89        return;
  90
  91    if (!self[KVOProxyKey])
  92    {
  93        if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
  94        {
  95            self._willChangeMessageCounter[aKey] -= 1;
  96
  97            if (!self._willChangeMessageCounter[aKey])
  98                delete self._willChangeMessageCounter[aKey];
  99        }
 100        else
 101            [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
 102    }
 103}
 104
 105- (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
 106{
 107    if (!aKey)
 108        return;
 109
 110    if (!self[KVOProxyKey])
 111    {
 112        if (!self._willChangeMessageCounter)
 113            self._willChangeMessageCounter = new Object();
 114
 115        if (!self._willChangeMessageCounter[aKey])
 116            self._willChangeMessageCounter[aKey] = 1;
 117        else
 118            self._willChangeMessageCounter[aKey] += 1;
 119    }
 120}
 121
 122- (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)aMutationKind usingObjects:(CPSet)objects
 123{
 124    if (!self[KVOProxyKey])
 125    {
 126        if (self._willChangeMessageCounter && self._willChangeMessageCounter[aKey])
 127        {
 128            self._willChangeMessageCounter[aKey] -= 1;
 129
 130            if (!self._willChangeMessageCounter[aKey])
 131                delete self._willChangeMessageCounter[aKey];
 132        }
 133        else
 134            [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
 135    }
 136}
 137
 138- (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(CPKeyValueObservingOptions)options context:(id)aContext
 139{
 140    if (!anObserver || !aPath)
 141        return;
 142
 143    [[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext];
 144}
 145
 146- (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath
 147{
 148    if (!anObserver || !aPath)
 149        return;
 150
 151    [self[KVOProxyKey] _removeObserver:anObserver forKeyPath:aPath];
 152}
 153
 154/*!
 155    Whether -willChangeValueForKey/-didChangeValueForKey should automatically be invoked when the
 156    setter of the given key is used. The default is YES. If you override this method to return NO
 157    for some key, you will need to call -willChangeValueForKey/-didChangeValueForKey manually to
 158    be KVO compliant.
 159
 160    The default implementation of this method will check if the receiving class implements
 161    `+ (BOOL)automaticallyNotifiesObserversOf<aKey>` and return the response of that method if it
 162    exists.
 163*/
 164+ (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
 165{
 166    var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1),
 167        selector = "automaticallyNotifiesObserversOf" + capitalizedKey;
 168
 169    if ([[self class] respondsToSelector:selector])
 170        return objj_msgSend([self class], selector);
 171
 172    return YES;
 173}
 174
 175+ (CPSet)keyPathsForValuesAffectingValueForKey:(CPString)aKey
 176{
 177    var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1),
 178        selector = "keyPathsForValuesAffecting" + capitalizedKey;
 179
 180    if ([[self class] respondsToSelector:selector])
 181        return objj_msgSend([self class], selector);
 182
 183    return [CPSet set];
 184}
 185
 186- (void)applyChange:(CPDictionary)aChange toKeyPath:(CPString)aKeyPath
 187{
 188    var changeKind = [aChange objectForKey:CPKeyValueChangeKindKey],
 189        oldValue = [aChange objectForKey:CPKeyValueChangeOldKey],
 190        newValue = [aChange objectForKey:CPKeyValueChangeNewKey];
 191
 192    if (newValue === [CPNull null])
 193        newValue = nil;
 194
 195    if (changeKind === CPKeyValueChangeSetting)
 196        return [self setValue:newValue forKeyPath:aKeyPath];
 197
 198    var indexes = [aChange objectForKey:CPKeyValueChangeIndexesKey];
 199
 200    // If we have an indexes entry, then we have an ordered to-many relationship
 201    if (indexes)
 202    {
 203        if (changeKind === CPKeyValueChangeInsertion)
 204            [[self mutableArrayValueForKeyPath:aKeyPath] insertObjects:newValue atIndexes:indexes];
 205
 206        else if (changeKind === CPKeyValueChangeRemoval)
 207            [[self mutableArrayValueForKeyPath:aKeyPath] removeObjectsAtIndexes:indexes];
 208
 209        else if (changeKind === CPKeyValueChangeReplacement)
 210            [[self mutableArrayValueForKeyPath:aKeyPath] replaceObjectAtIndexes:indexes withObjects:newValue];
 211    }
 212    else
 213    {
 214        if (changeKind === CPKeyValueChangeInsertion)
 215            [[self mutableSetValueForKeyPath:aKeyPath] unionSet:newValue];
 216
 217        else if (changeKind === CPKeyValueChangeRemoval)
 218            [[self mutableSetValueForKeyPath:aKeyPath] minusSet:oldValue];
 219
 220        else if (changeKind === CPKeyValueChangeReplacement)
 221            [[self mutableSetValueForKeyPath:aKeyPath] setSet:newValue];
 222    }
 223}
 224
 225@end
 226
 227@implementation CPDictionary (KeyValueObserving)
 228
 229- (CPDictionary)inverseChangeDictionary
 230{
 231    var inverseChangeDictionary = [self mutableCopy],
 232        changeKind = [self objectForKey:CPKeyValueChangeKindKey];
 233
 234    if (changeKind === CPKeyValueChangeSetting || changeKind === CPKeyValueChangeReplacement)
 235    {
 236        [inverseChangeDictionary
 237            setObject:[self objectForKey:CPKeyValueChangeOldKey]
 238               forKey:CPKeyValueChangeNewKey];
 239
 240        [inverseChangeDictionary
 241            setObject:[self objectForKey:CPKeyValueChangeNewKey]
 242               forKey:CPKeyValueChangeOldKey];
 243    }
 244
 245    else if (changeKind === CPKeyValueChangeInsertion)
 246    {
 247        [inverseChangeDictionary
 248            setObject:CPKeyValueChangeRemoval
 249               forKey:CPKeyValueChangeKindKey];
 250
 251        [inverseChangeDictionary
 252            setObject:[self objectForKey:CPKeyValueChangeNewKey]
 253               forKey:CPKeyValueChangeOldKey];
 254
 255        [inverseChangeDictionary removeObjectForKey:CPKeyValueChangeNewKey];
 256    }
 257
 258    else if (changeKind === CPKeyValueChangeRemoval)
 259    {
 260        [inverseChangeDictionary
 261            setObject:CPKeyValueChangeInsertion
 262               forKey:CPKeyValueChangeKindKey];
 263
 264        [inverseChangeDictionary
 265            setObject:[self objectForKey:CPKeyValueChangeOldKey]
 266               forKey:CPKeyValueChangeNewKey];
 267
 268        [inverseChangeDictionary removeObjectForKey:CPKeyValueChangeOldKey];
 269    }
 270
 271    return inverseChangeDictionary;
 272}
 273
 274@end
 275
 276// KVO Options
 277CPKeyValueObservingOptionNew        = 1 << 0;
 278CPKeyValueObservingOptionOld        = 1 << 1;
 279CPKeyValueObservingOptionInitial    = 1 << 2;
 280CPKeyValueObservingOptionPrior      = 1 << 3;
 281
 282// KVO Change Dictionary Keys
 283CPKeyValueChangeKindKey                 = @"CPKeyValueChangeKindKey";
 284CPKeyValueChangeNewKey                  = @"CPKeyValueChangeNewKey";
 285CPKeyValueChangeOldKey                  = @"CPKeyValueChangeOldKey";
 286CPKeyValueChangeIndexesKey              = @"CPKeyValueChangeIndexesKey";
 287CPKeyValueChangeNotificationIsPriorKey  = @"CPKeyValueChangeNotificationIsPriorKey";
 288
 289// KVO Change Types
 290CPKeyValueChangeSetting     = 1;
 291CPKeyValueChangeInsertion   = 2;
 292CPKeyValueChangeRemoval     = 3;
 293CPKeyValueChangeReplacement = 4;
 294
 295// CPKeyValueSetMutationKind
 296CPKeyValueUnionSetMutation = 1;
 297CPKeyValueMinusSetMutation = 2;
 298CPKeyValueIntersectSetMutation = 3;
 299CPKeyValueSetSetMutation = 4;
 300
 301//FIXME: "secret" dict ivar-keys are workaround to support unordered to-many relationships without too many modifications
 302_CPKeyValueChangeSetMutationObjectsKey  = @"_CPKeyValueChangeSetMutationObjectsKey";
 303_CPKeyValueChangeSetMutationKindKey     = @"_CPKeyValueChangeSetMutationKindKey";
 304_CPKeyValueChangeSetMutationNewValueKey = @"_CPKeyValueChangeSetMutationNewValueKey";
 305
 306var _changeKindForSetMutationKind = function(mutationKind)
 307{
 308    switch (mutationKind)
 309    {
 310        case CPKeyValueUnionSetMutation:        return CPKeyValueChangeInsertion;
 311        case CPKeyValueMinusSetMutation:        return CPKeyValueChangeRemoval;
 312        case CPKeyValueIntersectSetMutation:    return CPKeyValueChangeRemoval;
 313        case CPKeyValueSetSetMutation:          return CPKeyValueChangeReplacement;
 314    }
 315};
 316
 317var kvoNewAndOld        = CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld,
 318    DependentKeysKey    = "$KVODEPENDENT",
 319    KVOProxyKey         = "$KVOPROXY";
 320
 321//rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object)
 322
 323/* @ignore */
 324@implementation _CPKVOProxy : CPObject
 325{
 326    id              _targetObject;
 327    Class           _nativeClass;
 328    CPDictionary    _changesForKey;
 329    CPDictionary    _nestingForKey;
 330    CPDictionary    _minOptionsForKey;
 331    Object          _observersForKey;
 332    int             _observersForKeyLength;
 333    CPSet           _replacedKeys;
 334
 335    // TODO: Remove this line when granular notifications are implemented
 336    BOOL            _adding @accessors(property=adding);
 337}
 338
 339+ (id)proxyForObject:(CPObject)anObject
 340{
 341    var proxy = anObject[KVOProxyKey];
 342
 343    if (proxy)
 344        return proxy;
 345
 346    return [[self alloc] initWithTarget:anObject];
 347}
 348
 349- (id)initWithTarget:(id)aTarget
 350{
 351    if (self = [super init])
 352    {
 353        _targetObject       = aTarget;
 354        _nativeClass        = [aTarget class];
 355        _observersForKey    = {};
 356        _changesForKey      = {};
 357        _nestingForKey      = {};
 358        _minOptionsForKey   = {};
 359        _observersForKeyLength = 0;
 360
 361        [self _replaceClass];
 362        aTarget[KVOProxyKey] = self;
 363    }
 364    return self;
 365}
 366
 367- (void)_replaceClass
 368{
 369    var currentClass = _nativeClass,
 370        kvoClassName = "$KVO_" + class_getName(_nativeClass),
 371        existingKVOClass = objj_lookUpClass(kvoClassName);
 372
 373    if (existingKVOClass)
 374    {
 375        _targetObject.isa = existingKVOClass;
 376        _replacedKeys = existingKVOClass._replacedKeys;
 377        return;
 378    }
 379
 380    var kvoClass = objj_allocateClassPair(currentClass, kvoClassName);
 381
 382    objj_registerClassPair(kvoClass);
 383
 384    _replacedKeys = [CPSet set];
 385    kvoClass._replacedKeys = _replacedKeys;
 386
 387    //copy in the methods from our model subclass
 388    var methods = class_copyMethodList(_CPKVOModelSubclass);
 389
 390    if ([_targetObject isKindOfClass:[CPDictionary class]])
 391        methods = methods.concat(class_copyMethodList(_CPKVOModelDictionarySubclass));
 392
 393    class_addMethods(kvoClass, methods);
 394
 395    _targetObject.isa = kvoClass;
 396}
 397
 398- (void)_replaceModifiersForKey:(CPString)aKey
 399{
 400    if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey])
 401        return;
 402
 403    [_replacedKeys addObject:aKey];
 404
 405    var theClass = _nativeClass,
 406        KVOClass = _targetObject.isa,
 407        capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1);
 408
 409    // Attribute and To-One Relationships
 410    var setKey_selector = sel_getUid("set" + capitalizedKey + ":"),
 411        setKey_method = class_getInstanceMethod(theClass, setKey_selector);
 412
 413    if (setKey_method)
 414    {
 415        var setKey_method_imp = setKey_method.method_imp;
 416
 417        class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject)
 418        {
 419            [self willChangeValueForKey:aKey];
 420
 421            setKey_method_imp(self, _cmd, anObject);
 422
 423            [self didChangeValueForKey:aKey];
 424        }, "");
 425    }
 426
 427    // FIXME: Deprecated.
 428    var _setKey_selector = sel_getUid("_set" + capitalizedKey + ":"),
 429        _setKey_method = class_getInstanceMethod(theClass, _setKey_selector);
 430
 431    if (_setKey_method)
 432    {
 433        var _setKey_method_imp = _setKey_method.method_imp;
 434
 435        class_addMethod(KVOClass, _setKey_selector, function(self, _cmd, anObject)
 436        {
 437            [self willChangeValueForKey:aKey];
 438
 439            _setKey_method_imp(self, _cmd, anObject);
 440
 441            [self didChangeValueForKey:aKey];
 442        }, "");
 443    }
 444
 445    // Ordered To-Many Relationships
 446    var insertObject_inKeyAtIndex_selector = sel_getUid("insertObject:in" + capitalizedKey + "AtIndex:"),
 447        insertObject_inKeyAtIndex_method =
 448            class_getInstanceMethod(theClass, insertObject_inKeyAtIndex_selector),
 449
 450        insertKey_atIndexes_selector = sel_getUid("insert" + capitalizedKey + ":atIndexes:"),
 451        insertKey_atIndexes_method =
 452            class_getInstanceMethod(theClass, insertKey_atIndexes_selector),
 453
 454        removeObjectFromKeyAtIndex_selector = sel_getUid("removeObjectFrom" + capitalizedKey + "AtIndex:"),
 455        removeObjectFromKeyAtIndex_method =
 456            class_getInstanceMethod(theClass, removeObjectFromKeyAtIndex_selector),
 457
 458        removeKeyAtIndexes_selector = sel_getUid("remove" + capitalizedKey + "AtIndexes:"),
 459        removeKeyAtIndexes_method = class_getInstanceMethod(theClass, removeKeyAtIndexes_selector);
 460
 461    if ((insertObject_inKeyAtIndex_method || insertKey_atIndexes_method) &&
 462        (removeObjectFromKeyAtIndex_method || removeKeyAtIndexes_method))
 463    {
 464        if (insertObject_inKeyAtIndex_method)
 465        {
 466            var insertObject_inKeyAtIndex_method_imp = insertObject_inKeyAtIndex_method.method_imp;
 467
 468            class_addMethod(KVOClass, insertObject_inKeyAtIndex_selector, function(self, _cmd, anObject, anIndex)
 469            {
 470                [self willChange:CPKeyValueChangeInsertion
 471                 valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
 472                          forKey:aKey];
 473
 474                insertObject_inKeyAtIndex_method_imp(self, _cmd, anObject, anIndex);
 475
 476                [self didChange:CPKeyValueChangeInsertion
 477                valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
 478                         forKey:aKey];
 479            }, "");
 480        }
 481
 482        if (insertKey_atIndexes_method)
 483        {
 484            var insertKey_atIndexes_method_imp = insertKey_atIndexes_method.method_imp;
 485
 486            class_addMethod(KVOClass, insertKey_atIndexes_selector, function(self, _cmd, objects, indexes)
 487            {
 488                [self willChange:CPKeyValueChangeInsertion
 489                 valuesAtIndexes:[indexes copy]
 490                          forKey:aKey];
 491
 492                insertKey_atIndexes_method_imp(self, _cmd, objects, indexes);
 493
 494                [self didChange:CPKeyValueChangeInsertion
 495                valuesAtIndexes:[indexes copy]
 496                         forKey:aKey];
 497            }, "");
 498        }
 499
 500        if (removeObjectFromKeyAtIndex_method)
 501        {
 502            var removeObjectFromKeyAtIndex_method_imp = removeObjectFromKeyAtIndex_method.method_imp;
 503
 504            class_addMethod(KVOClass, removeObjectFromKeyAtIndex_selector, function(self, _cmd, anIndex)
 505            {
 506                [self willChange:CPKeyValueChangeRemoval
 507                 valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
 508                          forKey:aKey];
 509
 510                removeObjectFromKeyAtIndex_method_imp(self, _cmd, anIndex);
 511
 512                [self didChange:CPKeyValueChangeRemoval
 513                valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
 514                         forKey:aKey];
 515            }, "");
 516        }
 517
 518        if (removeKeyAtIndexes_method)
 519        {
 520            var removeKeyAtIndexes_method_imp = removeKeyAtIndexes_method.method_imp;
 521
 522            class_addMethod(KVOClass, removeKeyAtIndexes_selector, function(self, _cmd, indexes)
 523            {
 524                [self willChange:CPKeyValueChangeRemoval
 525                 valuesAtIndexes:[indexes copy]
 526                          forKey:aKey];
 527
 528                removeKeyAtIndexes_method_imp(self, _cmd, indexes);
 529
 530                [self didChange:CPKeyValueChangeRemoval
 531                valuesAtIndexes:[indexes copy]
 532                         forKey:aKey];
 533            }, "");
 534        }
 535
 536        // These are optional.
 537        var replaceObjectInKeyAtIndex_withObject_selector =
 538                sel_getUid("replaceObjectIn" + capitalizedKey + "AtIndex:withObject:"),
 539            replaceObjectInKeyAtIndex_withObject_method =
 540                class_getInstanceMethod(theClass, replaceObjectInKeyAtIndex_withObject_selector);
 541
 542        if (replaceObjectInKeyAtIndex_withObject_method)
 543        {
 544            var replaceObjectInKeyAtIndex_withObject_method_imp =
 545                    replaceObjectInKeyAtIndex_withObject_method.method_imp;
 546
 547            class_addMethod(KVOClass, replaceObjectInKeyAtIndex_withObject_selector,
 548            function(self, _cmd, anIndex, anObject)
 549            {
 550                [self willChange:CPKeyValueChangeReplacement
 551                 valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
 552                          forKey:aKey];
 553
 554                replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, anIndex, anObject);
 555
 556                [self didChange:CPKeyValueChangeReplacement
 557                valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]
 558                         forKey:aKey];
 559            }, "");
 560        }
 561
 562        var replaceKeyAtIndexes_withKey_selector =
 563                sel_getUid("replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":"),
 564            replaceKeyAtIndexes_withKey_method =
 565                class_getInstanceMethod(theClass, replaceKeyAtIndexes_withKey_selector);
 566
 567        if (replaceKeyAtIndexes_withKey_method)
 568        {
 569            var replaceKeyAtIndexes_withKey_method_imp = replaceKeyAtIndexes_withKey_method.method_imp;
 570
 571            class_addMethod(KVOClass, replaceKeyAtIndexes_withKey_selector, function(self, _cmd, indexes, objects)
 572            {
 573                [self willChange:CPKeyValueChangeReplacement
 574                 valuesAtIndexes:[indexes copy]
 575                          forKey:aKey];
 576
 577                replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, indexes, objects);
 578
 579                [self didChange:CPKeyValueChangeReplacement
 580                valuesAtIndexes:[indexes copy]
 581                         forKey:aKey];
 582            }, "");
 583        }
 584    }
 585
 586    // Unordered To-Many Relationships
 587    var addKeyObject_selector = sel_getUid("add" + capitalizedKey + "Object:"),
 588        addKeyObject_method = class_getInstanceMethod(theClass, addKeyObject_selector),
 589
 590        addKey_selector = sel_getUid("add" + capitalizedKey + ":"),
 591        addKey_method = class_getInstanceMethod(theClass, addKey_selector),
 592
 593        removeKeyObject_selector = sel_getUid("remove" + capitalizedKey + "Object:"),
 594        removeKeyObject_method = class_getInstanceMethod(theClass, removeKeyObject_selector),
 595
 596        removeKey_selector = sel_getUid("remove" + capitalizedKey + ":"),
 597        removeKey_method = class_getInstanceMethod(theClass, removeKey_selector);
 598
 599    if ((addKeyObject_method || addKey_method) && (removeKeyObject_method || removeKey_method))
 600    {
 601        if (addKeyObject_method)
 602        {
 603            var addKeyObject_method_imp = addKeyObject_method.method_imp;
 604
 605            class_addMethod(KVOClass, addKeyObject_selector, function(self, _cmd, anObject)
 606            {
 607                [self willChangeValueForKey:aKey
 608                            withSetMutation:CPKeyValueUnionSetMutation
 609                               usingObjects:[CPSet setWithObject:anObject]];
 610
 611                addKeyObject_method_imp(self, _cmd, anObject);
 612
 613                [self didChangeValueForKey:aKey
 614                           withSetMutation:CPKeyValueUnionSetMutation
 615                              usingObjects:[CPSet setWithObject:anObject]];
 616            }, "");
 617        }
 618
 619        if (addKey_method)
 620        {
 621            var addKey_method_imp = addKey_method.method_imp;
 622
 623            class_addMethod(KVOClass, addKey_selector, function(self, _cmd, objects)
 624            {
 625                [self willChangeValueForKey:aKey
 626                            withSetMutation:CPKeyValueUnionSetMutation
 627                               usingObjects:[objects copy]];
 628
 629                addKey_method_imp(self, _cmd, objects);
 630
 631                [self didChangeValueForKey:aKey
 632                           withSetMutation:CPKeyValueUnionSetMutation
 633                              usingObjects:[objects copy]];
 634            }, "");
 635        }
 636
 637        if (removeKeyObject_method)
 638        {
 639            var removeKeyObject_method_imp = removeKeyObject_method.method_imp;
 640
 641            class_addMethod(KVOClass, removeKeyObject_selector, function(self, _cmd, anObject)
 642            {
 643                [self willChangeValueForKey:aKey
 644                            withSetMutation:CPKeyValueMinusSetMutation
 645                               usingObjects:[CPSet setWithObject:anObject]];
 646
 647                removeKeyObject_method_imp(self, _cmd, anObject);
 648
 649                [self didChangeValueForKey:aKey
 650                           withSetMutation:CPKeyValueMinusSetMutation
 651                              usingObjects:[CPSet setWithObject:anObject]];
 652            }, "");
 653        }
 654
 655        if (removeKey_method)
 656        {
 657            var removeKey_method_imp = removeKey_method.method_imp;
 658
 659            class_addMethod(KVOClass, removeKey_selector, function(self, _cmd, objects)
 660            {
 661                [self willChangeValueForKey:aKey
 662                            withSetMutation:CPKeyValueMinusSetMutation
 663                               usingObjects:[objects copy]];
 664
 665                removeKey_method_imp(self, _cmd, objects);
 666
 667                [self didChangeValueForKey:aKey
 668                           withSetMutation:CPKeyValueMinusSetMutation
 669                              usingObjects:[objects copy]];
 670            }, "");
 671        }
 672
 673        // intersect<Key>: is optional.
 674        var intersectKey_selector = sel_getUid("intersect" + capitalizedKey + ":"),
 675            intersectKey_method = class_getInstanceMethod(theClass, intersectKey_selector);
 676
 677        if (intersectKey_method)
 678        {
 679            var intersectKey_method_imp = intersectKey_method.method_imp;
 680
 681            class_addMethod(KVOClass, intersectKey_selector, function(self, _cmd, aSet)
 682            {
 683                [self willChangeValueForKey:aKey
 684                            withSetMutation:CPKeyValueIntersectSetMutation
 685                               usingObjects:[aSet copy]];
 686
 687                intersectKey_method_imp(self, _cmd, aSet);
 688
 689                [self didChangeValueForKey:aKey
 690                           withSetMutation:CPKeyValueIntersectSetMutation
 691                              usingObjects:[aSet copy]];
 692            }, "");
 693        }
 694    }
 695
 696    var affectingKeys = [[_nativeClass keyPathsForValuesAffectingValueForKey:aKey] allObjects],
 697        affectingKeysCount = affectingKeys ? affectingKeys.length : 0;
 698
 699    if (!affectingKeysCount)
 700        return;
 701
 702    var dependentKeysForClass = _nativeClass[DependentKeysKey];
 703
 704    if (!dependentKeysForClass)
 705    {
 706        dependentKeysForClass = {};
 707        _nativeClass[DependentKeysKey] = dependentKeysForClass;
 708    }
 709
 710    while (affectingKeysCount--)
 711    {
 712        var affectingKey = affectingKeys[affectingKeysCount],
 713            affectedKeys = dependentKeysForClass[affectingKey];
 714
 715        if (!affectedKeys)
 716        {
 717            affectedKeys = [CPSet new];
 718            dependentKeysForClass[affectingKey] = affectedKeys;
 719        }
 720
 721        [affectedKeys addObject:aKey];
 722
 723        //observe key paths of objects other then ourselves, so we are notified of the changes
 724        //use CPKeyValueObservingOptionPrior to ensure proper wrapping around changes
 725        //so CPKeyValueObservingOptionPrior and CPKeyValueObservingOptionOld can be fulfilled even for dependent keys
 726        if (affectingKey.indexOf(@".") !== -1)
 727            [_targetObject addObserver:self forKeyPath:affectingKey options:CPKeyValueObservingOptionPrior | kvoNewAndOld  context:nil];
 728        else
 729            [self _replaceModifiersForKey:affectingKey];
 730    }
 731}
 732
 733- (void)observeValueForKeyPath:(CPString)theKeyPath ofObject:(id)theObject change:(CPDictionary)theChanges context:(id)theContext
 734{
 735    // Fire change events for the dependent keys
 736    var dependentKeysForClass = _nativeClass[DependentKeysKey],
 737        dependantKeys = [dependentKeysForClass[theKeyPath] allObjects],
 738        isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
 739
 740    for (var i = 0; i < [dependantKeys count]; i++)
 741    {
 742        var dependantKey = [dependantKeys objectAtIndex:i];
 743        [self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag];
 744    }
 745}
 746
 747- (void)_addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(CPKeyValueObservingOptions)options context:(id)aContext
 748{
 749    if (!anObserver)
 750        return;
 751
 752    var forwarder = nil;
 753
 754    if (aPath.indexOf('.') !== CPNotFound && aPath.charAt(0) !== '@')
 755        forwarder = [[_CPKVOForwardingObserver alloc] initWithKeyPath:aPath object:_targetObject observer:anObserver options:options context:aContext];
 756    else
 757        [self _replaceModifiersForKey:aPath];
 758
 759    var observers = _observersForKey[aPath];
 760
 761    if (!observers)
 762    {
 763        observers = @{};
 764        _observersForKey[aPath] = observers;
 765        _observersForKeyLength++;
 766    }
 767
 768    [observers setObject:_CPKVOInfoMake(anObserver, options, aContext, forwarder) forKey:[anObserver UID]];
 769
 770    if (options & CPKeyValueObservingOptionInitial)
 771    {
 772        var changes;
 773
 774        if (options & CPKeyValueObservingOptionNew)
 775        {
 776            var newValue = [_targetObject valueForKeyPath:aPath];
 777
 778            if (newValue == nil)
 779                newValue = [CPNull null];
 780
 781            changes = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting, CPKeyValueChangeNewKey: newValue };
 782        } else {
 783            changes = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting };
 784        }
 785
 786        [anObserver observeValueForKeyPath:aPath ofObject:_targetObject change:changes context:aContext];
 787    }
 788}
 789
 790- (void)_removeObserver:(id)anObserver forKeyPath:(CPString)aPath
 791{
 792    var observers = _observersForKey[aPath];
 793
 794    if (!observers)
 795    {
 796        // TODO: Remove this line when granular notifications are implemented
 797        if (!_adding)
 798            CPLog.warn(@"Cannot remove an observer %@ for the key path \"%@\" from %@ because it is not registered as an observer.", _targetObject, aPath, anObserver);
 799
 800        return;
 801    }
 802
 803    if (aPath.indexOf('.') != CPNotFound)
 804    {
 805        // During cib instantiation, it is possible for the forwarder to not yet be available,
 806        // so we have to check for nil.
 807        var observer = [observers objectForKey:[anObserver UID]],
 808            forwarder = observer ? observer.forwarder : nil;
 809
 810        [forwarder finalize];
 811    }
 812
 813    [observers removeObjectForKey:[anObserver UID]];
 814
 815    if (![observers count])
 816    {
 817        _observersForKeyLength--;
 818        delete _observersForKey[aPath];
 819    }
 820
 821    if (!_observersForKeyLength)
 822    {
 823        _targetObject.isa = _nativeClass; //restore the original class
 824        delete _targetObject[KVOProxyKey];
 825    }
 826}
 827
 828//FIXME: We do not compute and cache if CPKeyValueObservingOptionOld is needed, so we may do unnecessary work
 829
 830- (void)_sendNotificationsForKey:(CPString)aKey changeOptions:(CPDictionary)changeOptions isBefore:(BOOL)isBefore
 831{
 832    var changes = _changesForKey[aKey],
 833        observers = [_observersForKey[aKey] allValues],
 834        observersMinimumOptions = 0;
 835
 836    if (isBefore)
 837    {
 838        if (changes)
 839        {
 840            // "willChange:X" nesting.
 841            var level = _nestingForKey[aKey];
 842
 843            if (!level)
 844                [CPException raise:CPInternalInconsistencyException reason:@"_changesForKey without _nestingForKey"];
 845
 846            _nestingForKey[aKey] = level + 1;
 847            // Only notify on the first willChange..., silently note any following nested calls.
 848            return;
 849        }
 850
 851        _nestingForKey[aKey] = 1;
 852
 853        // Get the combined minimum of the ...Old and ...New options for all observers
 854        var count = observers ? observers.length : 0;
 855
 856        while (count--)
 857        {
 858            var observerInfo = observers[count];
 859
 860            observersMinimumOptions |= observerInfo.options & kvoNewAndOld;
 861        }
 862
 863        _minOptionsForKey[aKey] = observersMinimumOptions;
 864        changes = changeOptions;
 865
 866        if (observersMinimumOptions & CPKeyValueObservingOptionOld)
 867        {
 868            var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
 869                setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
 870
 871            if (setMutationKind)
 872            {
 873                var setMutationObjects = [changes[_CPKeyValueChangeSetMutationObjectsKey] copy],
 874                    setExistingObjects = [[_targetObject valueForKey: aKey] copy];
 875
 876                if (setMutationKind == CPKeyValueMinusSetMutation)
 877                {
 878                    [setExistingObjects intersectSet: setMutationObjects];
 879                    [changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
 880                }
 881                else if (setMutationKind === CPKeyValueIntersectSetMutation || setMutationKind === CPKeyValueSetSetMutation)
 882                {
 883                    [setExistingObjects minusSet: setMutationObjects];
 884                    [changes setValue:setExistingObjects forKey:CPKeyValueChangeOldKey];
 885                }
 886
 887                //for unordered to-many relationships (CPSet) even new values can only be calculated before!!!
 888                if (setMutationKind === CPKeyValueUnionSetMutation || setMutationKind === CPKeyValueSetSetMutation)
 889                {
 890                    [setMutationObjects minusSet: setExistingObjects];
 891                    //hide new value (for CPKeyValueObservingOptionPrior messages)
 892                    //as long as "didChangeValue..." is not yet called!
 893                    changes[_CPKeyValueChangeSetMutationNewValueKey] = setMutationObjects;
 894                }
 895            }
 896            else if (indexes)
 897            {
 898                var type = [changes objectForKey:CPKeyValueChangeKindKey];
 899
 900                // for ordered to-many relationships, oldvalue is only sensible for replace and remove
 901                if (type === CPKeyValueChangeReplacement || type === CPKeyValueChangeRemoval)
 902                {
 903                    //FIXME: do we need to go through and replace "" with CPNull?
 904                    var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
 905                    [changes setValue:newValues forKey:CPKeyValueChangeOldKey];
 906                }
 907            }
 908            else
 909            {
 910                var oldValue = [_targetObject valueForKey:aKey];
 911
 912                if (oldValue === nil || oldValue === undefined)
 913                    oldValue = [CPNull null];
 914
 915                [changes setObject:oldValue forKey:CPKeyValueChangeOldKey];
 916            }
 917
 918        }
 919
 920        [changes setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey];
 921        _changesForKey[aKey] = changes;
 922
 923        // Clear ...New option as it should never be sent for a ...Prior option
 924        observersMinimumOptions &= ~CPKeyValueObservingOptionNew;
 925    }
 926    else
 927    {
 928        var level = _nestingForKey[aKey];
 929
 930        if (!changes || !level)
 931        {
 932            if (_targetObject._willChangeMessageCounter && _targetObject._willChangeMessageCounter[aKey])
 933            {
 934                // Close unobserved willChange for a given key.
 935                _targetObject._willChangeMessageCounter[aKey] -= 1;
 936
 937                if (!_targetObject._willChangeMessageCounter[aKey])
 938                    delete _targetObject._willChangeMessageCounter[aKey];
 939
 940                return;
 941            }
 942            else
 943                [CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
 944        }
 945
 946        _nestingForKey[aKey] = level - 1;
 947
 948        if (level - 1 > 0)
 949        {
 950            // willChange... was called multiple times. Only fire observation notifications when
 951            // didChange... has been called an equal number of times.
 952            return;
 953        }
 954
 955        delete _nestingForKey[aKey];
 956
 957        [changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
 958
 959        observersMinimumOptions = _minOptionsForKey[aKey];
 960
 961        if (observersMinimumOptions & CPKeyValueObservingOptionNew)
 962        {
 963            var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
 964                setMutationKind = changes[_CPKeyValueChangeSetMutationKindKey];
 965
 966            if (setMutationKind)
 967            {
 968                //old and new values for unordered to-many relationships can only be calculated before
 969                //set recalculated hidden new value as soon as "didChangeValue..." is called!
 970                var newValue = changes[_CPKeyValueChangeSetMutationNewValueKey];
 971                [changes setValue:newValue forKey:CPKeyValueChangeNewKey];
 972
 973                //delete hidden values
 974                delete changes[_CPKeyValueChangeSetMutationNewValueKey];
 975                delete changes[_CPKeyValueChangeSetMutationObjectsKey];
 976                delete changes[_CPKeyValueChangeSetMutationKindKey];
 977            }
 978            else if (indexes)
 979            {
 980                var type = [changes objectForKey:CPKeyValueChangeKindKey];
 981
 982                // for ordered to-many relationships, newvalue is only sensible for replace and insert
 983                if (type == CPKeyValueChangeReplacement || type == CPKeyValueChangeInsertion)
 984                {
 985                    //FIXME: do we need to go through and replace "" with CPNull?
 986                    var newValues = [[_targetObject mutableArrayValueForKeyPath:aKey] objectsAtIndexes:indexes];
 987                    [changes setValue:newValues forKey:CPKeyValueChangeNewKey];
 988                }
 989            }
 990            else
 991            {
 992                var newValue = [_targetObject valueForKey:aKey];
 993
 994                if (newValue === nil || newValue === undefined)
 995                    newValue = [CPNull null];
 996
 997                [changes setObject:newValue forKey:CPKeyValueChangeNewKey];
 998            }
 999        }
1000
1001        delete _minOptionsForKey[aKey];
1002        delete _changesForKey[aKey];
1003    }
1004
1005    var count = observers ? observers.length : 0,
1006        changesCache = {};
1007
1008    while (count--)
1009    {
1010        var observerInfo = observers[count],
1011            options = observerInfo.options,
1012            onlyNewAndOldOptions = options & kvoNewAndOld,
1013            observerChanges = nil;
1014
1015        if (isBefore)
1016        {
1017            // Only send 'observeValueForKeyPath:' for '...Prior' option when handling 'willChangeValue...'
1018            if (options & CPKeyValueObservingOptionPrior)
1019            {
1020                observerChanges = changes;
1021                // The new values are not yet created in the change dictionary so remove ...New option to get a working cache below
1022                onlyNewAndOldOptions &= ~CPKeyValueObservingOptionNew;
1023            }
1024        }
1025        else
1026        {
1027            observerChanges = changes;
1028        }
1029
1030        if (observerChanges)
1031        {
1032            // Don't change the 'change' dictionary when the observer wants the minimum options.
1033            // The ...New option is remved above for the ...Prior case
1034            if (onlyNewAndOldOptions !== observersMinimumOptions)
1035            {
1036                // Use a subset of the 'change' dictionary. First try to find it in the cache
1037                observerChanges = changesCache[onlyNewAndOldOptions];
1038                if (!observerChanges)
1039                {
1040                    // Not in the cache. Build a new dictionary and store it in the cache
1041                    changesCache[onlyNewAndOldOptions] = observerChanges = [changes mutableCopy];
1042                    if (!(onlyNewAndOldOptions & CPKeyValueObservingOptionOld))
1043                        [observerChanges removeObjectForKey:CPKeyValueChangeOldKey];
1044                    if (!(onlyNewAndOldOptions & CPKeyValueObservingOptionNew))
1045                        [observerChanges removeObjectForKey:CPKeyValueChangeNewKey];
1046                }
1047            }
1048            [observerInfo.observer observeValueForKeyPath:aKey ofObject:_targetObject change:observerChanges context:observerInfo.context];
1049        }
1050    }
1051
1052    var dependentKeysMap = _nativeClass[DependentKeysKey];
1053
1054    if (!dependentKeysMap)
1055        return;
1056
1057    var dependentKeyPaths = [dependentKeysMap[aKey] allObjects];
1058
1059    if (!dependentKeyPaths)
1060        return;
1061
1062    var index = 0,
1063        count = [dependentKeyPaths count];
1064
1065    for (; index < count; ++index)
1066    {
1067        var keyPath = dependentKeyPaths[index];
1068
1069        [self _sendNotificationsForKey:keyPath
1070                         changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
1071                              isBefore:isBefore];
1072    }
1073}
1074
1075@end
1076
1077@implementation _CPKVOModelSubclass : CPObject
1078{
1079}
1080
1081- (void)willChangeValueForKey:(CPString)aKey
1082{
1083    var superClass = [self class],
1084        methodSelector = @selector(willChangeValueForKey:),
1085        methodImp = class_getMethodImplementation(superClass, methodSelector);
1086
1087    methodImp(self, methodSelector, aKey);
1088
1089    if (!aKey)
1090        return;
1091
1092    var changeOptions = @{ CPKeyValueChangeKindKey: CPKeyValueChangeSetting };
1093
1094    [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1095}
1096
1097- (void)didChangeValueForKey:(CPString)aKey
1098{
1099    var superClass = [self class],
1100        methodSelector = @selector(didChangeValueForKey:),
1101        methodImp = class_getMethodImplementation(superClass, methodSelector);
1102
1103    methodImp(self, methodSelector, aKey);
1104
1105    if (!aKey)
1106        return;
1107
1108    [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1109}
1110
1111- (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
1112{
1113    var superClass = [self class],
1114        methodSelector = @selector(willChange:valuesAtIndexes:forKey:),
1115        methodImp = class_getMethodImplementation(superClass, methodSelector);
1116
1117    methodImp(self, methodSelector, change, indexes, aKey);
1118
1119    if (!aKey)
1120        return;
1121
1122    var changeOptions = @{ CPKeyValueChangeKindKey: change, CPKeyValueChangeIndexesKey: indexes };
1123
1124    [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1125}
1126
1127- (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)aKey
1128{
1129    var superClass = [self class],
1130        methodSelector = @selector(didChange:valuesAtIndexes:forKey:),
1131        methodImp = class_getMethodImplementation(superClass, methodSelector);
1132
1133    methodImp(self, methodSelector, change, indexes, aKey);
1134
1135    if (!aKey)
1136        return;
1137
1138    [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1139}
1140
1141- (void)willChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
1142{
1143    var superClass = [self class],
1144        methodSelector = @selector(willChangeValueForKey:withSetMutation:usingObjects:),
1145        methodImp = class_getMethodImplementation(superClass, methodSelector);
1146
1147    methodImp(self, methodSelector, aKey, mutationKind, objects);
1148
1149    if (!aKey)
1150        return;
1151
1152    var changeKind = _changeKindForSetMutationKind(mutationKind),
1153        changeOptions = @{ CPKeyValueChangeKindKey: changeKind };
1154
1155    //set hidden change-dict ivars to support unordered to-many relationships
1156    changeOptions[_CPKeyValueChangeSetMutationObjectsKey] = objects;
1157    changeOptions[_CPKeyValueChangeSetMutationKindKey] = mutationKind;
1158
1159    [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:changeOptions isBefore:YES];
1160}
1161
1162- (void)didChangeValueForKey:(CPString)aKey withSetMutation:(CPKeyValueSetMutationKind)mutationKind usingObjects:(CPSet)objects
1163{
1164    var superClass = [self class],
1165        methodSelector = @selector(didChangeValueForKey:withSetMutation:usingObjects:),
1166        methodImp = class_getMethodImplementation(superClass, methodSelector);
1167
1168    methodImp(self, methodSelector, aKey, mutationKind, objects);
1169
1170    if (!aKey)
1171        return;
1172
1173    [[_CPKVOProxy proxyForObject:self] _sendNotificationsForKey:aKey changeOptions:nil isBefore:NO];
1174}
1175
1176- (Class)class
1177{
1178    return self[KVOProxyKey]._nativeClass;
1179}
1180
1181- (Class)superclass
1182{
1183    return [[self class] superclass];
1184}
1185
1186- (BOOL)isKindOfClass:(Class)aClass
1187{
1188    return [[self class] isSubclassOfClass:aClass];
1189}
1190
1191- (BOOL)isMemberOfClass:(Class)aClass
1192{
1193    return [self class] == aClass;
1194}
1195
1196- (CPString)className
1197{
1198    return [self class].name;
1199}
1200
1201@end
1202
1203@implementation _CPKVOModelDictionarySubclass : CPObject
1204{
1205}
1206
1207- (void)removeAllObjects
1208{
1209    var keys = [self allKeys],
1210        count = [keys count],
1211        i = 0;
1212
1213    for (; i < count; i++)
1214        [self willChangeValueForKey:keys[i]];
1215
1216    var superClass = [self class],
1217        methodSelector = @selector(removeAllObjects),
1218        methodImp = class_getMethodImplementation(superClass, methodSelector);
1219
1220    methodImp(self, methodSelector);
1221
1222    for (i = 0; i < count; i++)
1223        [self didChangeValueForKey:keys[i]];
1224}
1225
1226- (void)removeObjectForKey:(id)aKey
1227{
1228    [self willChangeValueForKey:aKey];
1229
1230    var superClass = [self class],
1231        methodSelector = @selector(removeObjectForKey:),
1232        methodImp = class_getMethodImplementation(superClass, methodSelector);
1233
1234    methodImp(self, methodSelector, aKey);
1235
1236    [self didChangeValueForKey:aKey];
1237}
1238
1239- (void)setObject:(id)anObject forKey:(id)aKey
1240{
1241    [self willChangeValueForKey:aKey];
1242
1243    var superClass = [self class],
1244        methodSelector = @selector(setObject:forKey:),
1245        methodImp = class_getMethodImplementation(superClass, methodSelector);
1246
1247    methodImp(self, methodSelector, anObject, aKey);
1248
1249    [self didChangeValueForKey:aKey];
1250}
1251
1252@end
1253
1254@implementation _CPKVOForwardingObserver : CPObject
1255{
1256    id          _object;
1257    id          _observer;
1258    id          _context;
1259    unsigned    _options;
1260                             //a.b
1261    CPString    _firstPart;  //a
1262    CPString    _secondPart; //b
1263
1264    id          _value;
1265}
1266
1267- (id)initWithKeyPath:(CPString)aKeyPath object:(id)anObject observer:(id)anObserver options:(unsigned)options context:(id)aContext
1268{
1269    self = [super init];
1270
1271    _context = aContext;
1272    _observer = anObserver;
1273    _object = anObject;
1274    _options = options;
1275
1276    var dotIndex = aKeyPath.indexOf('.');
1277
1278    if (dotIndex === CPNotFound)
1279        [CPException raise:CPInvalidArgumentException reason:"Created _CPKVOForwardingObserver without compound key path: " + aKeyPath];
1280
1281    _firstPart = aKeyPath.substring(0, dotIndex);
1282    _secondPart = aKeyPath.substring(dotIndex + 1);
1283
1284    //become an observer of the first part of our key (a)
1285    [_object addObserver:self forKeyPath:_firstPart options:_options context:nil];
1286
1287    //the current value of a (not the value of a.b)
1288    _value = [_object valueForKey:_firstPart];
1289
1290    if (_value)
1291        [_value addObserver:self forKeyPath:_secondPart options:_options context:nil]; //we're observing b on current a
1292
1293    return self;
1294}
1295
1296- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext
1297{
1298    if (aKeyPath === _firstPart)
1299    {
1300        var pathChanges = [CPMutableDictionary dictionaryWithObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey];
1301
1302        if (_options & CPKeyValueObservingOptionOld)
1303        {
1304            var oldValue = [_value valueForKeyPath:_secondPart];
1305
1306            [pathChanges setObject:oldValue != null ? oldValue : [CPNull null] forKey:CPKeyValueChangeOldKey];
1307        }
1308
1309        if (_options & CPKeyValueObservingOptionNew)
1310        {
1311            var newValue = [_object valueForKeyPath:_firstPart + "." + _secondPart];
1312
1313            [pathChanges setObject:newValue != null ? newValue : [CPNull null] forKey:CPKeyValueChangeNewKey];
1314        }
1315
1316        [_observer observeValueForKeyPath:_firstPart + "." + _secondPart ofObject:_object change:pathChanges context:_context];
1317
1318        //since a has changed, we should remove ourselves as an observer of the old a, and observe the new one
1319        if (_value)
1320            [_value removeObserver:self forKeyPath:_secondPart];
1321
1322        _value = [_object valueForKey:_firstPart];
1323
1324        if (_value)
1325            [_value addObserver:self forKeyPath:_secondPart options:_options context:nil];
1326    }
1327    else
1328    {
1329        //a is the same, but a.b has changed -- nothing to do but forward this message along
1330        [_observer observeValueForKeyPath:_firstPart + "." + aKeyPath ofObject:_object change:changes context:_context];
1331    }
1332}
1333
1334- (void)finalize
1335{
1336    if (_value)
1337        [_value removeObserver:self forKeyPath:_secondPart];
1338
1339    [_object removeObserver:self forKeyPath:_firstPart];
1340
1341    _object = nil;
1342    _observer = nil;
1343    _context = nil;
1344    _value = nil;
1345}
1346
1347@end
1348
1349var _CPKVOInfoMake = function(anObserver, theOptions, aContext, aForwarder)
1350{
1351    return {
1352        observer: anObserver,
1353        options: theOptions,
1354        context: aContext,
1355        forwarder: aForwarder
1356    };
1357};
1358
1359@import "CPArray+KVO.j"
1360@import "CPSet+KVO.j"