package ns.foundation;
import java.util.EnumSet;
public interface NSKeyValueObserving extends NSKeyValueCodingAdditions {
public enum Options {
New,
Old,
Initial,
Prior;
public static final EnumSet<Options> NewAndOld = EnumSet.of(New, Old);
}
public enum Changes {
Setting,
Insertion,
Removal,
Replacement
}
public enum SetMutation {
Union,
Minus,
Intersect,
Set
}
public class KeyValueChange {
public EnumSet<Changes> kind;
public Object newValue;
public Object oldValue;
public NSSet<Integer> indexes;
public Boolean isPrior;
public KeyValueChange() {
}
public KeyValueChange(Changes... changes) {
kind = EnumSet.of(changes[0], changes);
}
public KeyValueChange(KeyValueChange changes) {
this.kind = changes.kind;
this.newValue = changes.newValue;
this.oldValue = changes.oldValue;
this.indexes = changes.indexes;
}
public KeyValueChange(EnumSet<Changes> change, NSSet<Integer> indexes) {
this.kind = change;
this.indexes = indexes;
}
}
public static class DefaultImplementation {
public static boolean automaticallyNotifiesObserversForKey(NSObservable targetObject, String key) {
return true;
}
public static void addObserverForKeyPath(NSObservable targetObject, NSObserver observer, String keyPath, EnumSet<Options> options, Object context) {
if (observer == null || keyPath == null || keyPath.length() == 0) {
return;
}
KeyValueObservingProxy.proxyForObject(targetObject).addObserverForKeyPath(observer, keyPath, options, context);
}
public static void didChangeValueForKey(NSObservable targetObject, String key) {
if (key == null || key.length() == 0) {
return;
}
KeyValueObservingProxy.proxyForObject(targetObject).sendNotificationsForKey(key, null, false);
}
public static void didChangeValuesAtIndexForKey(NSObservable targetObject, EnumSet<Changes> change, NSSet<Integer> indexes, String key) {
if (key == null) {
return;
}
KeyValueObservingProxy.proxyForObject(targetObject).sendNotificationsForKey(key, null, false);
}
public static NSSet<String> keyPathsForValuesAffectingValueForKey(NSObservable targetObject, String key) {
return NSSet.emptySet();
}
public static void removeObserverForKeyPath(NSObservable targetObject, NSObserver observer, String keyPath) {
if (observer == null || keyPath == null || keyPath.length() == 0) {
return;
}
KeyValueObservingProxy.proxyForObject(targetObject).removeObserverForKeyPath(observer, keyPath);
}
public static void willChangeValueForKey(NSObservable targetObject, String key) {
if (key == null || key.length() == 0)
return;
KeyValueChange changeOptions = new KeyValueChange(Changes.Setting);
KeyValueObservingProxy.proxyForObject(targetObject).sendNotificationsForKey(key, changeOptions, true);
}
public static void willChangeValuesAtIndexForKey(final NSObservable targetObject, final EnumSet<Changes> change, final NSSet<Integer> indexes, final String key) {
if (key == null || key.length() == 0)
return;
KeyValueChange changeOptions = new KeyValueChange(change, indexes);
KeyValueObservingProxy.proxyForObject(targetObject).sendNotificationsForKey(key, changeOptions, true);
}
public static void observeValueForKeyPath(NSObserver observer, String keyPath, NSObservable targetObject, KeyValueChange changes, Object context) {
}
}
public static class Utility {
public static NSObservable observable(Object object) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
if (object instanceof NSObservable) {
return (NSObservable)object;
}
return KeyValueObservingProxy.proxyForObject(object);
}
public static void addObserverForKeyPath(Object object, NSObserver observer, String keyPath, EnumSet<Options> options, Object context) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
observable(object).addObserverForKeyPath(observer, keyPath, options, context);
}
public static boolean automaticallyNotifiesObserversForKey(Object object, String key) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
return observable(object).automaticallyNotifiesObserversForKey(key);
}
public static void didChangeValueForKey(Object object, String key) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
observable(object).didChangeValueForKey(key);
}
public static void didChangeValuesAtIndexForKey(Object object, EnumSet<Changes> change, NSSet<Integer> indexes, String key) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
observable(object).didChangeValuesAtIndexForKey(change, indexes, key);
}
public static NSSet<String> keyPathsForValuesAffectingValueForKey(Object object, String key) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
return observable(object).keyPathsForValuesAffectingValueForKey(key);
}
public static void removeObserverForKeyPath(Object object, NSObserver observer, String keyPath) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
observable(object).removeObserverForKeyPath(observer, keyPath);
}
public static void willChangeValueForKey(Object object, String key) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
observable(object).willChangeValueForKey(key);
}
public static void willChangeValuesAtIndexForKey(Object object, EnumSet<Changes> change, NSSet<Integer> indexes, String key) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
observable(object).willChangeValuesAtIndexForKey(change, indexes, key);
}
public static void observeValueForKeyPath(Object object, String keyPath, NSObservable targetObject, KeyValueChange changes, Object context) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
if (object instanceof NSObserver) {
((NSObserver)object).observeValueForKeyPath(keyPath, targetObject, changes, context);
}
throw new IllegalArgumentException("Object must implement NSObserver");
}
}
public static class KeyValueObservingProxy implements NSObservable {
private static final NSMutableDictionary<Object, KeyValueObservingProxy> _proxyCache = new NSMutableDictionary<Object, KeyValueObservingProxy>();
private static final NSMutableDictionary<Class<? extends Object>, NSMutableDictionary<String, NSMutableSet<String>>> _dependentKeys = new NSMutableDictionary<Class<? extends Object>, NSMutableDictionary<String, NSMutableSet<String>>>();
private final Object _targetObject;
private NSMutableDictionary<String, KeyValueChange> _changesForKey = new NSMutableDictionary<String, KeyValueChange>();
private NSMutableDictionary<String, NSMutableDictionary<NSObserver, ObserverInfo>> _observersForKey = new NSMutableDictionary<String, NSMutableDictionary<NSObserver,ObserverInfo>>();
int _changeCount = 0;
public static KeyValueObservingProxy proxyForObject(Object object) {
KeyValueObservingProxy proxy = _proxyCache.objectForKey(object);
if (proxy != null) {
return proxy;
}
proxy = new KeyValueObservingProxy(object);
_proxyCache.setObjectForKey(proxy, object);
return proxy;
}
private KeyValueObservingProxy(Object object) {
_targetObject = object;
}
private NSObservable observable() {
if (_targetObject instanceof NSObservable) {
return (NSObservable)_targetObject;
}
return this;
}
@Override
public void addObserverForKeyPath(NSObserver observer, String keyPath, EnumSet<Options> options, Object context) {
if (observer == null)
return;
KeyValueForwardingObserver forwarder = null;
if (keyPath.contains(".")) {
forwarder = new KeyValueForwardingObserver(keyPath, observable(), observer, options, context);
} else {
addDependentKeysForKey(keyPath);
}
NSMutableDictionary<NSObserver, ObserverInfo> observers = _observersForKey.objectForKey(keyPath);
if (observers == null) {
observers = new NSMutableDictionary<NSObserver, ObserverInfo>();
_observersForKey.setObjectForKey(observers, keyPath);
NSKeyValueCoding.DefaultImplementation._addKVOAdditionsForKey(_targetObject, keyPath);
}
observers.setObjectForKey(new ObserverInfo(observer, options, context, forwarder), observer);
if (options.contains(Options.Initial)) {
Object _newValue = NSKeyValueCodingAdditions.Utility.valueForKeyPath(_targetObject, keyPath);
if (_newValue == null)
_newValue = NSKeyValueCoding.NullValue;
final Object value = _newValue;
KeyValueChange changes = new KeyValueChange() {{ this.newValue = value; }};
observer.observeValueForKeyPath(keyPath, observable(), changes, context);
}
}
private void addDependentKeysForKey(String key) {
NSSet<String> composedOfKeys = NSKeyValueObserving.Utility.keyPathsForValuesAffectingValueForKey(_targetObject,key);
NSMutableDictionary<String, NSMutableSet<String>> dependentKeysForClass = _dependentKeys.objectForKey(_targetObject.getClass());
if (dependentKeysForClass == null) {
dependentKeysForClass = new NSMutableDictionary<String, NSMutableSet<String>>();
_dependentKeys.setObjectForKey(dependentKeysForClass, _targetObject.getClass());
}
for (String componentKey : composedOfKeys) {
NSMutableSet<String> keysComposedOfKey = dependentKeysForClass.objectForKey(componentKey);
if (keysComposedOfKey == null) {
keysComposedOfKey = new NSMutableSet<String>();
dependentKeysForClass.setObjectForKey(keysComposedOfKey, componentKey);
}
keysComposedOfKey.addObject(key);
addDependentKeysForKey(componentKey);
}
}
@Override
public void removeObserverForKeyPath(NSObserver observer, String keyPath) {
NSMutableDictionary<NSObserver, ObserverInfo> observers = _observersForKey.objectForKey(keyPath);
if (keyPath.contains(".")) {
KeyValueForwardingObserver forwarder = observers.objectForKey(observer).forwarder;
forwarder.destroy();
}
observers.removeObjectForKey(observer);
if (observers.isEmpty()) {
_observersForKey.removeObjectForKey(keyPath);
NSKeyValueCoding.DefaultImplementation._removeKVOAdditionsForKey(_targetObject, keyPath);
}
if (_observersForKey.isEmpty())
_proxyCache.removeObjectForKey(_targetObject);
}
@SuppressWarnings("unchecked")
public void sendNotificationsForKey(String key, KeyValueChange changeOptions, boolean isBefore) {
KeyValueChange changes = _changesForKey.objectForKey(key);
if (isBefore) {
changes = new KeyValueChange(changeOptions);
NSSet<Integer> indexes = changes.indexes;
if (indexes != null) {
EnumSet<Changes> type = changes.kind;
if (type.contains(Changes.Replacement) || type.contains(Changes.Removal)) {
NSMutableArray<Object> oldValues = new NSMutableArray<Object>((NSArray<Object>)NSKeyValueCodingAdditions.Utility.valueForKeyPath(_targetObject, key));
changes.oldValue = oldValues;
}
} else {
Object oldValue = NSKeyValueCoding.Utility.valueForKey(_targetObject, key);
if (oldValue == null)
oldValue = NSKeyValueCoding.NullValue;
changes.oldValue = oldValue;
}
changes.isPrior = true;
_changesForKey.setObjectForKey(changes, key);
} else {
changes.isPrior = null;
NSSet<Integer> indexes = changes.indexes;
if (indexes != null) {
EnumSet<Changes> type = changes.kind;
if (type.contains(Changes.Replacement) || type.contains(Changes.Insertion)) {
NSMutableArray<Object> newValues = new NSMutableArray<Object>((NSArray<Object>)NSKeyValueCodingAdditions.Utility.valueForKeyPath(_targetObject, key));
changes.newValue = newValues;
}
} else {
Object newValue = NSKeyValueCoding.Utility.valueForKey(_targetObject, key);
if (newValue == null)
newValue = NSKeyValueCoding.NullValue;
changes.newValue = newValue;
}
//FIXME - Is this safe? Sink the change notification if nothing actually changed.
if (changes.oldValue == changes.newValue)
return;
}
NSArray<ObserverInfo> observers;
if (_observersForKey.containsKey(key)) {
observers = _observersForKey.objectForKey(key).allValues();
} else {
observers = new NSArray<ObserverInfo>();
}
int count = observers.count();
while (count-- > 0) {
ObserverInfo observerInfo = observers.objectAtIndex(count);
if (isBefore && (observerInfo.options.contains(Options.Prior))) {
observerInfo.observer.observeValueForKeyPath(key, observable(), changes, observerInfo.context);
} else if (!isBefore) {
observerInfo.observer.observeValueForKeyPath(key, observable(), changes, observerInfo.context);
}
}
NSSet<String> keysComposedOfKey = null;
if (_dependentKeys.containsKey(_targetObject.getClass()))
keysComposedOfKey = _dependentKeys.objectForKey(_targetObject.getClass()).objectForKey(key);
if (keysComposedOfKey == null || keysComposedOfKey.isEmpty())
return;
for (String dependentKey : keysComposedOfKey) {
sendNotificationsForKey(dependentKey, changeOptions, isBefore);
}
}
@Override
public boolean automaticallyNotifiesObserversForKey(String key) {
return true;
}
@Override
public void didChangeValueForKey(String key) {
if (--_changeCount == 0)
NSKeyValueObserving.DefaultImplementation.didChangeValueForKey(this, key);
}
@Override
public void didChangeValuesAtIndexForKey(EnumSet<Changes> change, NSSet<Integer> indexes, String key) {
NSKeyValueObserving.DefaultImplementation.didChangeValuesAtIndexForKey(this, change, indexes, key);
}
@Override
public NSSet<String> keyPathsForValuesAffectingValueForKey(String key) {
return NSSet.emptySet();
}
@Override
public void willChangeValueForKey(String key) {
_changeCount++;
NSKeyValueObserving.DefaultImplementation.willChangeValueForKey(this, key);
}
@Override
public void willChangeValuesAtIndexForKey(EnumSet<Changes> change, NSSet<Integer> indexes, String key) {
NSKeyValueObserving.DefaultImplementation.willChangeValuesAtIndexForKey(this, change, indexes, key);
}
@Override
public void takeValueForKeyPath(Object value, String keyPath) {
NSKeyValueCodingAdditions.Utility.takeValueForKeyPath(_targetObject, value, keyPath);
}
@Override
public Object valueForKeyPath(String keyPath) {
return NSKeyValueCodingAdditions.Utility.valueForKeyPath(_targetObject, keyPath);
}
@Override
public void takeValueForKey(Object value, String key) {
NSKeyValueCoding.Utility.takeValueForKey(_targetObject, value, key);
}
@Override
public Object valueForKey(String key) {
return NSKeyValueCoding.Utility.valueForKey(_targetObject, key);
}
}
public static class ObserverInfo {
public final NSObserver observer;
public final EnumSet<Options> options;
public final Object context;
public final KeyValueForwardingObserver forwarder;
public ObserverInfo(NSObserver observer, EnumSet<Options> options, Object context, KeyValueForwardingObserver forwarder) {
this.observer = observer;
this.options = options;
this.context = context;
this.forwarder = forwarder;
}
}
public static class KeyValueForwardingObserver implements NSObserver {
private final NSObservable _targetObject;
private final NSObserver _observer;
private final EnumSet<Options> _options;
private final String _firstPart;
private final String _secondPart;
private NSObservable _value;
public KeyValueForwardingObserver(String keyPath, NSObservable object, NSObserver observer, EnumSet<Options> options, Object context) {
_observer = observer;
_targetObject = object;
_options = options;
if (!keyPath.contains("."))
throw new IllegalArgumentException("Created KeyValueForwardingObserver without a compound key path: " + keyPath);
int index = keyPath.indexOf('.');
_firstPart = keyPath.substring(0, index);
_secondPart = keyPath.substring(index+1);
_targetObject.addObserverForKeyPath(this, keyPath, options, context);
_value = (NSObservable) _targetObject.valueForKey(_firstPart);
if (_value != null) {
_value.addObserverForKeyPath(this, keyPath, options, context);
}
}
@Override
public void observeValueForKeyPath(String keyPath, NSObservable targetObject, KeyValueChange changes, Object context) {
if (targetObject == _targetObject) {
_observer.observeValueForKeyPath(_firstPart, targetObject, changes, context);
Object newValue = _targetObject.valueForKeyPath(_secondPart);
if (_value != null && _value != newValue)
_value.removeObserverForKeyPath(this, _secondPart);
_value = (NSObservable) newValue;
if (_value != null)
_value.addObserverForKeyPath(this, _secondPart, _options, context);
} else {
_observer.observeValueForKeyPath(_firstPart+"."+keyPath, targetObject, changes, context);
}
}
private void destroy() {
if (_value != null)
_value.removeObserverForKeyPath(this, _secondPart);
_targetObject.removeObserverForKeyPath(this, _firstPart);
_value = null;
}
}
public interface _NSKeyValueObserving {
}
}