/* * CPKeyValueCoding.j * Foundation * * Created by Ross Boucher. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @import "CPArray.j" @import "CPDictionary.j" @import "CPException.j" @import "CPNull.j" @import "CPObject.j" @import "CPSet.j" @implementation CPObject (KeyValueObserving) - (void)willChangeValueForKey:(CPString)aKey { } - (void)didChangeValueForKey:(CPString)aKey { } - (void)willChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)key { } - (void)didChange:(CPKeyValueChange)change valuesAtIndexes:(CPIndexSet)indexes forKey:(CPString)key { } - (void)addObserver:(id)anObserver forKeyPath:(CPString)aPath options:(unsigned)options context:(id)aContext { if (!anObserver || !aPath) return; [[_CPKVOProxy proxyForObject:self] _addObserver:anObserver forKeyPath:aPath options:options context:aContext]; } - (void)removeObserver:(id)anObserver forKeyPath:(CPString)aPath { if (!anObserver || !aPath) return; [self[KVOProxyKey] _removeObserver:anObserver forKeyPath:aPath]; } + (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey { return YES; } + (CPSet)keyPathsForValuesAffectingValueForKey:(CPString)aKey { var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1); selector = "keyPathsForValuesAffecting" + capitalizedKey; if ([[self class] respondsToSelector:selector]) return objj_msgSend([self class], selector); return [CPSet set]; } - (void)applyChange:(CPDictionary)aChange toKeyPath:(CPString)aKeyPath { var changeKind = [aChange objectForKey:CPKeyValueChangeKindKey]; if (changeKind === CPKeyValueChangeSetting) { var value = [aChange objectForKey:CPKeyValueChangeNewKey]; [self setValue:value === [CPNull null] ? nil : value forKeyPath:aKeyPath]; } else if (changeKind === CPKeyValueChangeInsertion) [[self mutableArrayValueForKeyPath:aKeyPath] insertObjects:[aChange objectForKey:CPKeyValueChangeNewKey] atIndexes:[aChange objectForKey:CPKeyValueChangeIndexesKey]]; else if (changeKind === CPKeyValueChangeRemoval) [[self mutableArrayValueForKeyPath:aKeyPath] removeObjectsAtIndexes:[aChange objectForKey:CPKeyValueChangeIndexesKey]]; else if (changeKind === CPKeyValueChangeReplacement) [[self mutableArrayValueForKeyPath:aKeyPath] replaceObjectAtIndexes:[aChange objectForKey:CPKeyValueChangeIndexesKey] withObjects:[aChange objectForKey:CPKeyValueChangeNewKey]]; } @end @implementation CPDictionary (KeyValueObserving) - (CPDictionary)inverseChangeDictionary { var inverseChangeDictionary = [self mutableCopy], changeKind = [self objectForKey:CPKeyValueChangeKindKey]; if (changeKind === CPKeyValueChangeSetting || changeKind === CPKeyValueChangeReplacement) { [inverseChangeDictionary setObject:[self objectForKey:CPKeyValueChangeOldKey] forKey:CPKeyValueChangeNewKey]; [inverseChangeDictionary setObject:[self objectForKey:CPKeyValueChangeNewKey] forKey:CPKeyValueChangeOldKey]; } else if (changeKind === CPKeyValueChangeInsertion) { [inverseChangeDictionary setObject:CPKeyValueChangeRemoval forKey:CPKeyValueChangeKindKey]; [inverseChangeDictionary setObject:[self objectForKey:CPKeyValueChangeNewKey] forKey:CPKeyValueChangeOldKey]; [inverseChangeDictionary removeObjectForKey:CPKeyValueChangeNewKey]; } else if (changeKind === CPKeyValueChangeRemoval) { [inverseChangeDictionary setObject:CPKeyValueChangeInsertion forKey:CPKeyValueChangeKindKey]; [inverseChangeDictionary setObject:[self objectForKey:CPKeyValueChangeOldKey] forKey:CPKeyValueChangeNewKey]; [inverseChangeDictionary removeObjectForKey:CPKeyValueChangeOldKey]; } return inverseChangeDictionary; } @end // KVO Options CPKeyValueObservingOptionNew = 1 << 0; CPKeyValueObservingOptionOld = 1 << 1; CPKeyValueObservingOptionInitial = 1 << 2; CPKeyValueObservingOptionPrior = 1 << 3; // KVO Change Dictionary Keys CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey"; CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey"; CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey"; CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey"; CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey"; // KVO Change Types CPKeyValueChangeSetting = 1; CPKeyValueChangeInsertion = 2; CPKeyValueChangeRemoval = 3; CPKeyValueChangeReplacement = 4; var kvoNewAndOld = CPKeyValueObservingOptionNew|CPKeyValueObservingOptionOld, DependentKeysKey = "$KVODEPENDENT", KVOProxyKey = "$KVOPROXY"; //rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object) /* @ignore */ @implementation _CPKVOProxy : CPObject { id _targetObject; Class _nativeClass; CPDictionary _changesForKey; Object _observersForKey; int _observersForKeyLength; CPSet _replacedKeys; } + (id)proxyForObject:(CPObject)anObject { var proxy = anObject[KVOProxyKey]; if (proxy) return proxy; proxy = [[self alloc] initWithTarget:anObject]; [proxy _replaceClass]; anObject[KVOProxyKey] = proxy; return proxy; } - (id)initWithTarget:(id)aTarget { self = [super init]; _targetObject = aTarget; _nativeClass = [aTarget class]; _replacedKeys = [CPSet set]; _observersForKey = {}; _changesForKey = {}; _observersForKeyLength = 0; return self; } - (void)_replaceClass { var currentClass = _nativeClass, kvoClassName = "$KVO_"+class_getName(_nativeClass), existingKVOClass = objj_lookUpClass(kvoClassName); if (existingKVOClass) { _targetObject.isa = existingKVOClass; return; } var kvoClass = objj_allocateClassPair(currentClass, kvoClassName); objj_registerClassPair(kvoClass); _class_initialize(kvoClass); //copy in the methods from our model subclass var methodList = _CPKVOModelSubclass.method_list, count = methodList.length; for (var i=0; i