public void NotificationPocoSuppressErrorOnBind()
        {
            RxApp.EnsureInitialized();
            using (var testLoggerRegistration = new TestLoggerRegistration())
            {
                var instance = new POCOObservableForProperty();

                var testLogger = testLoggerRegistration.Logger;

                var testClass = new PocoType();

                Expression <Func <PocoType, string> > expr = x => x.Property1 !;
                var exp = Reflection.Rewrite(expr.Body);

                var propertyName = exp.GetMemberInfo()?.Name;

                if (propertyName is null)
                {
                    throw new InvalidOperationException("propertyName should not be null");
                }

                instance.GetNotificationForProperty(testClass, exp, propertyName, false, true).Subscribe(_ => { });

                testLogger.LastMessages.Should().NotContain(m => m.Contains(nameof(POCOObservableForProperty)));
            }
        }
        public void CheckGetAffinityForObjectValues()
        {
            var instance = new POCOObservableForProperty();

            Assert.Equal(1, instance.GetAffinityForObject(typeof(PocoType), null, false));
            Assert.Equal(1, instance.GetAffinityForObject(typeof(INPCClass), null, false));
        }
        public IObservable <IObservedChange <object, object> > GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
        {
            Contract.Requires(sender != null && sender is DependencyObject);
            var type = sender.GetType();

            if (beforeChanged == true)
            {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object",
                                type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged));
            }

            var dpFetcher = getDependencyPropertyFetcher(type, propertyName);

            if (dpFetcher == null)
            {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                                type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged));
            }

#if !WINRT && !SILVERLIGHT
            return(Observable.Create <IObservedChange <object, object> >(subj => {
                var dp = dpFetcher();
                var dpd = DependencyPropertyDescriptor.FromProperty(dp, type);
                var ev = new EventHandler((o, e) => subj.OnNext(new ObservedChange <object, object>()
                {
                    Sender = sender, PropertyName = propertyName,
                }));
                dpd.AddValueChanged(sender, ev);

                return Disposable.Create(() => dpd.RemoveValueChanged(sender, ev));
            }));
#else
            var dpAndSubj = createAttachedProperty(type, propertyName);

            BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1,
                                         new Binding()
            {
                Source = sender as DependencyObject, Path = new PropertyPath(propertyName)
            });

            return(dpAndSubj.Item2
                   .Where(x => x == sender)
                   .Select(x => (IObservedChange <object, object>) new ObservedChange <object, object>()
            {
                Sender = x, PropertyName = propertyName
            }));
#endif
        }
        /// <inheritdoc/>
        public IObservable <IObservedChange <object, object> >?GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false)
        {
            if (sender == null)
            {
                throw new ArgumentNullException(nameof(sender));
            }

            var depSender = sender as DependencyObject;

            if (depSender == null)
            {
                throw new ArgumentException("The sender must be a DependencyObject", nameof(sender));
            }

            var type = sender.GetType();

            if (beforeChanged)
            {
                this.Log().Warn(
                    CultureInfo.InvariantCulture,
                    "Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object",
                    type.FullName,
                    propertyName);

                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, expression, propertyName, beforeChanged));
            }

            var dpFetcher = GetDependencyPropertyFetcher(type, propertyName);

            if (dpFetcher == null)
            {
                this.Log().Warn(
                    CultureInfo.InvariantCulture,
                    "Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                    type.FullName,
                    propertyName);

                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, expression, propertyName, beforeChanged));
            }

            return(Observable.Create <IObservedChange <object, object> >(subj =>
            {
                var handler = new DependencyPropertyChangedCallback((o, e) =>
                {
                    subj.OnNext(new ObservedChange <object, object>(sender, expression));
                });
                var dependencyProperty = dpFetcher();
                var token = depSender.RegisterPropertyChangedCallback(dependencyProperty, handler);
                return Disposable.Create(() => depSender.UnregisterPropertyChangedCallback(dependencyProperty, token));
            }));
        }
        public void NotificationPocoErrorOnBind()
        {
            RxApp.EnsureInitialized();

            // Use same logger, when the test is executed multiple times in the same AndroidRunner/AppDomain/AssemblyLoadContext
            if (_testLoggerForNotificationPocoErrorOnBind is null)
            {
                _testLoggerForNotificationPocoErrorOnBind = new TestLogger();
            }

            // Run test twice and verify that POCO message is logged only once.
            for (var i = 0; i < 2; i++)
            {
                using (var testLoggerRegistration = new TestLoggerRegistration(_testLoggerForNotificationPocoErrorOnBind))
                {
                    var instance = new POCOObservableForProperty();

                    var testLogger = testLoggerRegistration.Logger;

                    var testClass = new PocoType();

                    Expression <Func <PocoType, string> > expr = x => x.Property1 !;
                    var exp = Reflection.Rewrite(expr.Body);

                    var propertyName = exp.GetMemberInfo()?.Name;

                    if (propertyName is null)
                    {
                        throw new InvalidOperationException("propertyName should not be null");
                    }

                    instance.GetNotificationForProperty(testClass, exp, propertyName, false).Subscribe(_ => { });

                    Assert.True(testLogger.LastMessages.Count > 0);

                    var expectedMessage = $"{nameof(POCOObservableForProperty)}: The class {typeof(PocoType).FullName} property {nameof(PocoType.Property1)} is a POCO type and won't send change notifications, WhenAny will only return a single value!";
                    Assert.Equal(expectedMessage, testLogger.LastMessages[0]);

                    // Verify that the message is logged only once
                    foreach (var logMessage in testLogger.LastMessages.Skip(1))
                    {
                        Assert.NotEqual(expectedMessage, logMessage);
                    }
                }
            }
        }
        public void NotificationPocoSuppressErrorOnBind()
        {
            var instance = new POCOObservableForProperty();

            var testLogger = new TestLogger();

            Locator.CurrentMutable.RegisterConstant <ILogger>(testLogger);

            var testClass = new PocoType();

            Expression <Func <PocoType, string> > expr = x => x.Property1;
            var exp = Reflection.Rewrite(expr.Body);

            instance.GetNotificationForProperty(testClass, exp, exp.GetMemberInfo().Name, false, true).Subscribe(_ => { });

            testLogger.LastMessages.ShouldNotContain(m => m.Contains(nameof(POCOObservableForProperty)));
        }
        public IObservable <IObservedChange <object, object> > GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
        {
            Contract.Requires(sender != null && sender is DependencyObject);
            var type = sender.GetType();

            if (beforeChanged == true)
            {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object",
                                type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged));
            }

            var dpFetcher = getDependencyPropertyFetcher(type, propertyName);

            if (dpFetcher == null)
            {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                                type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged));
            }

            var dpAndSubj = createAttachedProperty(type, propertyName);

            return(Observable.Create <IObservedChange <object, object> >(obs => {
                BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1,
                                             new Binding()
                {
                    Source = sender as DependencyObject, Path = new PropertyPath(propertyName)
                });

                var disp = dpAndSubj.Item2
                           .Where(x => x == sender)
                           .Select(x => new ObservedChange <object, object>()
                {
                    Sender = x, PropertyName = propertyName
                })
                           .Subscribe(obs);
                // ClearBinding calls ClearValue http://stackoverflow.com/questions/1639219/clear-binding-in-silverlight-remove-data-binding-from-setbinding
                return new CompositeDisposable(Disposable.Create(() => (sender as DependencyObject).ClearValue(dpAndSubj.Item1)), disp);
            }));
        }
        public void NotificationPocoErrorOnBind()
        {
            var instance = new POCOObservableForProperty();

            var testLogger = new TestLogger();

            Locator.CurrentMutable.RegisterConstant <ILogger>(testLogger);

            var testClass = new PocoType();

            Expression <Func <PocoType, string> > expr = x => x.Property1;
            var exp = Reflection.Rewrite(expr.Body);

            instance.GetNotificationForProperty(testClass, exp, exp.GetMemberInfo().Name, false).Subscribe(_ => { });

            Assert.True(testLogger.LastMessages.Count > 0);
            Assert.Equal(testLogger.LastMessages[0], $"{nameof(POCOObservableForProperty)}: The class {typeof(PocoType).FullName} property {nameof(PocoType.Property1)} is a POCO type and won't send change notifications, WhenAny will only return a single value!");
        }
        public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
        {
            Contract.Requires(sender != null && sender is DependencyObject);
            var type = sender.GetType();

            if (beforeChanged == true) {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object",
                    type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return ret.GetNotificationForProperty(sender, propertyName, beforeChanged);
            }

            var dpFetcher = getDependencyPropertyFetcher(type, propertyName);
            if (dpFetcher == null) {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                    type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return ret.GetNotificationForProperty(sender, propertyName, beforeChanged);
            }

#if !WINRT && !SILVERLIGHT
            return Observable.Create<IObservedChange<object, object>>(subj => {
                var dp = dpFetcher();
                var dpd = DependencyPropertyDescriptor.FromProperty(dp, type);
                var ev = new EventHandler((o, e) => subj.OnNext(new ObservedChange<object, object>() {Sender = sender, PropertyName = propertyName,}));
                dpd.AddValueChanged(sender, ev);

                return Disposable.Create(() => dpd.RemoveValueChanged(sender, ev));
            });
#else
            var dpAndSubj = createAttachedProperty(type, propertyName);

            BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1,
                new Binding() { Source = sender as DependencyObject, Path = new PropertyPath(propertyName) });

            return dpAndSubj.Item2
                .Where(x => x == sender)
                .Select(x => (IObservedChange<object, object>) new ObservedChange<object, object>() { Sender = x, PropertyName = propertyName });
#endif
        }
        public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
        {
            Contract.Requires(sender != null && sender is DependencyObject);
            var type = sender.GetType();

            if (beforeChanged == true) {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object",
                    type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return ret.GetNotificationForProperty(sender, propertyName, beforeChanged);
            }

            var dpFetcher = getDependencyPropertyFetcher(type, propertyName);
            if (dpFetcher == null) {
                this.Log().Warn("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                    type.FullName, propertyName);

                var ret = new POCOObservableForProperty();
                return ret.GetNotificationForProperty(sender, propertyName, beforeChanged);
            }

            var dpAndSubj = createAttachedProperty(type, propertyName);

            return Observable.Create<IObservedChange<object, object>>(obs => {
                BindingOperations.SetBinding(sender as DependencyObject, dpAndSubj.Item1,
                    new Binding() { Source = sender as DependencyObject, Path = new PropertyPath(propertyName) });

                var disp = dpAndSubj.Item2
                    .Where(x => x == sender)
                    .Select(x => new ObservedChange<object, object>() { Sender = x, PropertyName = propertyName })
                    .Subscribe(obs);
                // ClearBinding calls ClearValue http://stackoverflow.com/questions/1639219/clear-binding-in-silverlight-remove-data-binding-from-setbinding
                return new CompositeDisposable(Disposable.Create(() => (sender as DependencyObject).ClearValue(dpAndSubj.Item1)), disp);
            });
        }
        public IObservable <IObservedChange <object, object> > GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
        {
            Contract.Requires(sender != null && sender is DependencyObject);

            if (beforeChanged == true)
            {
                return(null);
            }

            var dobj = sender as DependencyObject;
            var type = dobj.GetType();

            // Look for the DependencyProperty attached to this property name
#if WINRT
            var pi = type.GetProperty(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            if (pi != null)
            {
                goto itWorks;
            }
#endif

            var fi = type.GetField(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            if (fi == null)
            {
                this.Log().Debug("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                                 type.FullName, propertyName);
                var ret = new POCOObservableForProperty();
                return(ret.GetNotificationForProperty(sender, propertyName, beforeChanged));
            }

#if !WINRT && !SILVERLIGHT
            return(Observable.Create <IObservedChange <object, object> >(subj =>
            {
                var dp = (DependencyProperty)fi.GetValue(null);
                var dpd = DependencyPropertyDescriptor.FromProperty(dp, type);
                var ev = new EventHandler((o, e) => subj.OnNext(new ObservedChange <object, object>()
                {
                    Sender = sender, PropertyName = propertyName,
                }));
                dpd.AddValueChanged(sender, ev);

                return Disposable.Create(() => dpd.RemoveValueChanged(sender, ev));
            }));
#else
itWorks:
            return(Observable.Create <IObservedChange <object, object> >(subj => {
                DependencyProperty attachedProp;

                if (!attachedProperties.ContainsKey(type))
                {
                    // NB: There is no way to unregister an attached property,
                    // we just have to leak it. Luckily it's per-type, so it's
                    // not *that* bad.
                    attachedProp = DependencyProperty.RegisterAttached(
                        "ListenAttached" + propertyName + this.GetHashCode().ToString("{0:x}"),
                        typeof(object), type,
                        new PropertyMetadata(null, (o, e) => subjects[o].Item1.OnNext(o)));
                    attachedProperties[type] = attachedProp;
                }
                else
                {
                    attachedProp = attachedProperties[type];
                }

                // Here's the idea for this cracked-out code:
                //
                // The reason we're doing all of this is that we can only
                // create a single binding between a DependencyObject and its
                // attached property, yet we could have multiple people
                // interested in this property. We should only drop the actual
                // Binding once nobody is listening anymore.
                if (!subjects.ContainsKey(sender))
                {
                    var disposer = new RefcountDisposeWrapper(
                        Disposable.Create(() => {
#if !SILVERLIGHT && !WINRT
                        // XXX: Apparently it's simply impossible to unset a binding in SL :-/
                        BindingOperations.ClearBinding(dobj, attachedProp);
#endif
                        subjects.Remove(dobj);
                    }));

                    subjects[sender] = Tuple.Create(new Subject <object>(), disposer);

                    var b = new Binding()
                    {
                        Source = dobj, Path = new PropertyPath(propertyName)
                    };
                    BindingOperations.SetBinding(dobj, attachedProp, b);
                }
                else
                {
                    subjects[sender].Item2.AddRef();
                }

                var disp = subjects[sender].Item1
                           .Select(x => (IObservedChange <object, object>) new ObservedChange <object, object>()
                {
                    Sender = x, PropertyName = propertyName
                })
                           .Subscribe(subj);

                return Disposable.Create(() => {
                    disp.Dispose();
                    subjects[sender].Item2.Release();
                });
            }));
#endif
        }
        public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
        {
            Contract.Requires(sender != null && sender is DependencyObject);

            var dobj = sender as DependencyObject;
            var type = dobj.GetType();

            // Look for the DependencyProperty attached to this property name
            #if WINRT
            var pi = type.GetProperty(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            if (pi != null) {
                goto itWorks;
            }
            #endif

            var fi = type.GetField(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            if (fi == null) {
                this.Log().Debug("Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
                    type.FullName, propertyName);
                var ret = new POCOObservableForProperty();
                return ret.GetNotificationForProperty(sender, propertyName, beforeChanged);
            }

            itWorks:
            return Observable.Create<IObservedChange<object, object>>(subj => {
                DependencyProperty attachedProp;

                if (!attachedProperties.ContainsKey(type)) {
                    // NB: There is no way to unregister an attached property,
                    // we just have to leak it. Luckily it's per-type, so it's
                    // not *that* bad.
                    attachedProp = DependencyProperty.RegisterAttached(
                        "ListenAttached" + propertyName + this.GetHashCode().ToString("{0:x}"),
                        typeof(object), type,
                        new PropertyMetadata(null, (o,e) => subjects[o].Item1.OnNext(o)));
                    attachedProperties[type] = attachedProp;
                } else {
                    attachedProp = attachedProperties[type];
                }

                // Here's the idea for this cracked-out code:
                //
                // The reason we're doing all of this is that we can only
                // create a single binding between a DependencyObject and its
                // attached property, yet we could have multiple people
                // interested in this property. We should only drop the actual
                // Binding once nobody is listening anymore.
                if (!subjects.ContainsKey(sender)) {
                    var disposer = new RefcountDisposeWrapper(
                        Disposable.Create(() => {
            #if !SILVERLIGHT && !WINRT
                            // XXX: Apparently it's simply impossible to unset a binding in SL :-/
                            BindingOperations.ClearBinding(dobj, attachedProp);
            #endif
                            subjects.Remove(dobj);
                        }));

                    subjects[sender] = Tuple.Create(new Subject<object>(), disposer);

                    var b = new Binding() { Source = dobj, Path = new PropertyPath(propertyName) };
                    BindingOperations.SetBinding(dobj, attachedProp, b);
                } else {
                    subjects[sender].Item2.AddRef();
                }

                var disp = subjects[sender].Item1
                    .Select(x => (IObservedChange<object, object>) new ObservedChange<object, object>() { Sender = x, PropertyName = propertyName })
                    .Subscribe(subj);

                return Disposable.Create(() => {
                    disp.Dispose();
                    subjects[sender].Item2.Release();
                });
            });
        }