Пример #1
0
        /// <summary>
        /// Merges an observable sequence of observable sequences into an observable sequence.
        /// </summary>
        public static IObservable <TSource> Merge <TSource>(this IObservable <IObservable <TSource> > source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var gate = new object();
                var isStopped = false;
                var group = new CompositeDisposable();
                var outerSubscription = new MutableDisposable();

                group.Add(outerSubscription);

                outerSubscription.Disposable = source.Subscribe(
                    innerSource =>
                {
                    var innerSubscription = new MutableDisposable();
                    group.Add(innerSubscription);
                    innerSubscription.Disposable = innerSource.Subscribe(
                        x =>
                    {
                        lock (gate)
                            observer.OnNext(x);
                    },
                        exception =>
                    {
                        lock (gate)
                            observer.OnError(exception);
                    },
                        () =>
                    {
                        group.Remove(innerSubscription);           // modification MUST occur before subsequent check
                        if (isStopped && group.Count == 1)         // isStopped must be checked before group Count to ensure outer is not creating more groups
                        {
                            lock (gate)
                                observer.OnCompleted();
                        }
                    });
                },
                    exception =>
                {
                    lock (gate)
                        observer.OnError(exception);
                },
                    () =>
                {
                    isStopped = true;         // modification MUST occur before subsequent check
                    if (group.Count == 1)
                    {
                        lock (gate)
                            observer.OnCompleted();
                    }
                });

                return group;
            }));
        }
Пример #2
0
 public JoinObserver(IObservable <T> source, Action <Exception> onError)
 {
     this.source  = source;
     this.onError = onError;
     Queue        = new Queue <Notification <T> >();
     subscription = new MutableDisposable();
     activePlans  = new List <ActivePlan>();
 }
Пример #3
0
        /// <summary>
        /// Asynchronously notify observers using the scheduler.
        /// </summary>
        public static IObservable <TSource> ObserveOn <TSource>(this IObservable <TSource> source, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var q = new Queue <Notification <TSource> >();
                var active = false;
                var gate = new object();
                var cancelable = new MutableDisposable();
                var subscription = source.Materialize().Subscribe(n =>
                {
                    var shouldStart = false;
                    lock (gate)
                    {
                        shouldStart = !active;
                        active = true;
                        q.Enqueue(n);
                    }

                    if (shouldStart)
                    {
                        cancelable.Disposable = scheduler.Schedule(self =>
                        {
                            var notification = default(Notification <TSource>);
                            lock (gate)
                            {
                                notification = q.Dequeue();
                            }

                            notification.Accept(observer);

                            var shouldRecurse = false;
                            lock (gate)
                            {
                                shouldRecurse = active = q.Count > 0;
                            }

                            if (shouldRecurse)
                            {
                                self();
                            }
                        });
                    }
                });

                return new CompositeDisposable(subscription, cancelable);
            }));
        }
Пример #4
0
        /// <summary>
        /// Continues an observable sequence that is terminated normally or by an exception with the next observable sequence.
        /// </summary>
        public static IObservable <TSource> OnErrorResumeNext <TSource>(this IEnumerable <IObservable <TSource> > sources, IScheduler scheduler)
        {
            if (sources == null)
            {
                throw new ArgumentNullException("sources");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var e = sources.GetEnumerator();
                var subscription = new MutableDisposable();

                var cancelable = scheduler.Schedule(self =>
                {
                    var hasNext = false;
                    var current = default(IObservable <TSource>);
                    try
                    {
                        hasNext = e.MoveNext();
                        if (hasNext)
                        {
                            current = e.Current;
                        }
                        else
                        {
                            e.Dispose();
                        }
                    }
                    catch (Exception exception)
                    {
                        observer.OnError(exception);
                        e.Dispose();
                        return;
                    }

                    if (!hasNext)
                    {
                        observer.OnCompleted();
                        return;
                    }

                    var d = new MutableDisposable();
                    subscription.Disposable = d;
                    d.Disposable = current.Subscribe(observer.OnNext, exception => self(), self);
                });

                return new CompositeDisposable(subscription, cancelable);
            }));
        }
Пример #5
0
        public void MutableDisposable_Dispose()
        {
            var disp = false;

            var m = new MutableDisposable();
            var d = Disposable.Create(() => { disp = true; });

            m.Disposable = d;
            Assert.AreSame(d, m.Disposable);
            Assert.IsFalse(disp);

            m.Dispose();
            Assert.IsTrue(disp);
            Assert.IsNull(m.Disposable);
        }
Пример #6
0
        internal static IObservable <TResult> Combine <TLeft, TRight, TResult>(this IObservable <TLeft> leftSource, IObservable <TRight> rightSource, Func <IObserver <TResult>, IDisposable, IDisposable, IObserver <Either <Notification <TLeft>, Notification <TRight> > > > combinerSelector)
        {
            return(new AnonymousObservable <TResult>(observer =>
            {
                var leftSubscription = new MutableDisposable();
                var rightSubscription = new MutableDisposable();

                var combiner = combinerSelector(observer, leftSubscription, rightSubscription);
                var gate = new object();

                leftSubscription.Disposable = leftSource.Materialize().Select(x => Either <Notification <TLeft>, Notification <TRight> > .CreateLeft(x)).Synchronize(gate).Subscribe(combiner);
                rightSubscription.Disposable = rightSource.Materialize().Select(x => Either <Notification <TLeft>, Notification <TRight> > .CreateRight(x)).Synchronize(gate).Subscribe(combiner);

                return new CompositeDisposable(leftSubscription, rightSubscription);
            }));
        }
Пример #7
0
        public void FutureDisposable_DisposeAfterSet()
        {
            var disposed = false;

            var d  = new MutableDisposable();
            var dd = Disposable.Create(() => { disposed = true; });

            d.Disposable = dd;

            Assert.AreSame(dd, d.Disposable);

            Assert.IsFalse(disposed);
            d.Dispose();
            Assert.IsTrue(disposed);
            d.Dispose();
            Assert.IsTrue(disposed);
        }
Пример #8
0
        /// <summary>
        /// Continues an observable sequence that is terminated by an exception of the specified type with the observable sequence
        /// produced by the handler.
        /// </summary>
        public static IObservable <TSource> Catch <TSource, TException>(this IObservable <TSource> source, Func <TException, IObservable <TSource> > handler) where TException : Exception
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (handler == null)
            {
                throw new ArgumentNullException("handler");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var subscription = new MutableDisposable();

                subscription.Disposable = source.Subscribe(observer.OnNext,
                                                           exception =>
                {
                    var e = exception as TException;
                    if (e != null)
                    {
                        IObservable <TSource> result;
                        try
                        {
                            result = handler(e);
                        }
                        catch (Exception ex)
                        {
                            observer.OnError(ex);
                            return;
                        }

                        var d = new MutableDisposable();
                        subscription.Disposable = d;
                        d.Disposable = result.Subscribe(observer);
                    }
                    else
                    {
                        observer.OnError(exception);
                    }
                }, observer.OnCompleted);

                return subscription;
            }));
        }
Пример #9
0
        /// <summary>
        /// Asynchronously subscribes and unsubscribes observers on the synchronization context.
        /// </summary>
        public static IObservable <TSource> SubscribeOn <TSource>(this IObservable <TSource> source, SynchronizationContext context)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var subscription = new MutableDisposable();
                context.Post(_ => subscription.Disposable = new ContextDisposable(context, source.Subscribe(observer)), null);
                return subscription;
            }));
        }
Пример #10
0
        public void FutureDisposable_DisposeBeforeSet()
        {
            var disposed = false;

            var d  = new MutableDisposable();
            var dd = Disposable.Create(() => { disposed = true; });

            Assert.IsFalse(disposed);
            d.Dispose();
            Assert.IsFalse(disposed);

            d.Disposable = dd;
            Assert.IsNull(d.Disposable); // CHECK!
            Assert.IsTrue(disposed);

            d.Dispose();
            Assert.IsTrue(disposed);
        }
Пример #11
0
        public void MutableDisposable_ReplaceBeforeDispose()
        {
            var disp1 = false;
            var disp2 = false;

            var m  = new MutableDisposable();
            var d1 = Disposable.Create(() => { disp1 = true; });

            m.Disposable = d1;
            Assert.AreSame(d1, m.Disposable);
            Assert.IsFalse(disp1);

            var d2 = Disposable.Create(() => { disp2 = true; });

            m.Disposable = d2;
            Assert.AreSame(d2, m.Disposable);
            Assert.IsTrue(disp1);
            Assert.IsFalse(disp2);
        }
Пример #12
0
        /// <summary>
        /// Asynchronously subscribes and unsubscribes observers using scheduler.
        /// </summary>
        public static IObservable <TSource> SubscribeOn <TSource>(this IObservable <TSource> source, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var d = new MutableDisposable();

                scheduler.Schedule(() => d.Disposable = new ScheduledDisposable(scheduler, source.Subscribe(observer)));

                return d;
            }));
        }
Пример #13
0
        public void MutableDisposable_ReplaceAfterDispose()
        {
            var disp1 = false;
            var disp2 = false;

            var m = new MutableDisposable();

            m.Dispose();

            var d1 = Disposable.Create(() => { disp1 = true; });

            m.Disposable = d1;
            Assert.IsNull(m.Disposable); // CHECK
            Assert.IsTrue(disp1);

            var d2 = Disposable.Create(() => { disp2 = true; });

            m.Disposable = d2;
            Assert.IsNull(m.Disposable); // CHECK
            Assert.IsTrue(disp2);
        }
Пример #14
0
        /// <summary>
        /// Ignores values from an observable sequence which are followed by another value before dueTime.
        /// </summary>
        public static IObservable <TSource> Throttle <TSource>(this IObservable <TSource> source, TimeSpan dueTime, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var gate = new object();
                var value = default(TSource);
                var hasValue = false;
                var cancelable = new MutableDisposable();
                var id = 0UL;

                var subscription = source.Subscribe(x =>
                {
                    ulong currentid;
                    lock (gate)
                    {
                        hasValue = true;
                        value = x;
                        id = unchecked (id + 1);
                        currentid = id;
                    }
                    var d = new MutableDisposable();
                    cancelable.Disposable = d;
                    d.Disposable = scheduler.Schedule(() =>
                    {
                        lock (gate)
                        {
                            if (hasValue && id == currentid)
                            {
                                observer.OnNext(value);
                            }
                            hasValue = false;
                        }
                    }, dueTime);
                },
                                                    exception =>
                {
                    cancelable.Dispose();

                    lock (gate)
                    {
                        observer.OnError(exception);
                        hasValue = false;
                        id = unchecked (id + 1);
                    }
                },
                                                    () =>
                {
                    cancelable.Dispose();

                    lock (gate)
                    {
                        if (hasValue)
                        {
                            observer.OnNext(value);
                        }
                        observer.OnCompleted();
                        hasValue = false;
                        id = unchecked (id + 1);
                    }
                });

                return new CompositeDisposable(subscription, cancelable);
            }));
        }
Пример #15
0
        /// <summary>
        /// Returns the source observable sequence or the other observable sequence if dueTime elapses.
        /// </summary>
        public static IObservable <TSource> Timeout <TSource>(this IObservable <TSource> source, TimeSpan dueTime, IObservable <TSource> other, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }
            if (other == null)
            {
                throw new ArgumentNullException("other");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var subscription = new MutableDisposable();
                var timer = new MutableDisposable();
                var original = new MutableDisposable();

                subscription.Disposable = original;

                var gate = new object();
                var id = 0UL;
                var initial = id;
                var switched = false;

                timer.Disposable = scheduler.Schedule(() =>
                {
                    var timerWins = false;

                    lock (gate)
                    {
                        switched = id == initial;
                        timerWins = switched;
                    }

                    if (timerWins)
                    {
                        subscription.Disposable = other.Subscribe(observer);
                    }
                }, dueTime);

                original.Disposable = source.Subscribe(
                    x =>
                {
                    var onNextWins = false;
                    var value = 0UL;

                    lock (gate)
                    {
                        onNextWins = !switched;
                        if (onNextWins)
                        {
                            id = unchecked (id + 1);
                            value = id;
                        }
                    }

                    if (onNextWins)
                    {
                        observer.OnNext(x);
                        timer.Disposable = scheduler.Schedule(() =>
                        {
                            var timerWins = false;

                            lock (gate)
                            {
                                switched = id == value;
                                timerWins = switched;
                            }

                            if (timerWins)
                            {
                                subscription.Disposable = other.Subscribe(observer);
                            }
                        }, dueTime);
                    }
                },
                    exception =>
                {
                    var onErrorWins = false;

                    lock (gate)
                    {
                        onErrorWins = !switched;
                        if (onErrorWins)
                        {
                            id = unchecked (id + 1);
                        }
                    }

                    if (onErrorWins)
                    {
                        observer.OnError(exception);
                    }
                },
                    () =>
                {
                    var onCompletedWins = false;

                    lock (gate)
                    {
                        onCompletedWins = !switched;
                        if (onCompletedWins)
                        {
                            id = unchecked (id + 1);
                        }
                    }

                    if (onCompletedWins)
                    {
                        observer.OnCompleted();
                    }
                });

                return new CompositeDisposable(subscription, timer);
            }));
        }
Пример #16
0
        public void FutureDisposable_SetNull()
        {
            var d = new MutableDisposable();

            d.Disposable = null;
        }
Пример #17
0
        /// <summary>
        /// Returns the source observable sequence or the other observable sequence if dueTime elapses.
        /// </summary>
        public static IObservable <TSource> Timeout <TSource>(this IObservable <TSource> source, DateTimeOffset dueTime, IObservable <TSource> other, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }
            if (other == null)
            {
                throw new ArgumentNullException("other");
            }


            return(new AnonymousObservable <TSource>(observer =>
            {
                var subscription = new MutableDisposable();
                var original = new MutableDisposable();

                subscription.Disposable = original;

                var gate = new object();
                var switched = false;

                var timer = scheduler.Schedule(() =>
                {
                    var timerWins = false;

                    lock (gate)
                    {
                        timerWins = !switched;
                        switched = true;
                    }

                    if (timerWins)
                    {
                        subscription.Disposable = other.Subscribe(observer);
                    }
                }, dueTime);

                original.Disposable = source.Subscribe(
                    x =>
                {
                    lock (gate)
                    {
                        if (!switched)
                        {
                            observer.OnNext(x);
                        }
                    }
                },
                    exception =>
                {
                    var onErrorWins = false;

                    lock (gate)
                    {
                        onErrorWins = !switched;
                        switched = true;
                    }

                    if (onErrorWins)
                    {
                        observer.OnError(exception);
                    }
                },
                    () =>
                {
                    var onCompletedWins = false;

                    lock (gate)
                    {
                        onCompletedWins = !switched;
                        switched = true;
                    }

                    if (onCompletedWins)
                    {
                        observer.OnCompleted();
                    }
                });

                return new CompositeDisposable(subscription, timer);
            }));
        }
Пример #18
0
        public void MutableDisposable_Ctor_Prop()
        {
            var m = new MutableDisposable();

            Assert.IsNull(m.Disposable);
        }
        /// <summary>
        /// Groups the elements of an observable sequence according to a specified key selector function and comparer and selects the resulting elements by using a specified function.
        /// </summary>
        public static IObservable <IGroupedObservable <TKey, TElement> > GroupBy <TSource, TKey, TElement>(this IObservable <TSource> source, Func <TSource, TKey> keySelector, Func <TSource, TElement> elementSelector, IEqualityComparer <TKey> comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            if (elementSelector == null)
            {
                throw new ArgumentNullException("elementSelector");
            }
            if (comparer == null)
            {
                throw new ArgumentNullException("comparer");
            }

            return(new AnonymousObservable <IGroupedObservable <TKey, TElement> >(observer =>
            {
                var map = new Dictionary <TKey, Subject <TElement> >(comparer);

                var subscription = new MutableDisposable();
                var refCountDisposable = new RefCountDisposable(subscription);

                subscription.Disposable = source.Subscribe(x =>
                {
                    var key = default(TKey);
                    try
                    {
                        key = keySelector(x);
                    }
                    catch (Exception exception)
                    {
                        foreach (var w in map.Values)
                        {
                            w.OnError(exception);
                        }
                        observer.OnError(exception);
                        return;
                    }

                    var fireNewMapEntry = false;
                    var writer = default(Subject <TElement>);
                    try
                    {
                        if (!map.TryGetValue(key, out writer))
                        {
                            writer = new Subject <TElement>();
                            map.Add(key, writer);
                            fireNewMapEntry = true;
                        }
                    }
                    catch (Exception exception)
                    {
                        foreach (var w in map.Values)
                        {
                            w.OnError(exception);
                        }
                        observer.OnError(exception);
                        return;
                    }

                    if (fireNewMapEntry)
                    {
                        observer.OnNext(new GroupedObservable <TKey, TElement>(key, writer, refCountDisposable));
                    }

                    var element = default(TElement);
                    try
                    {
                        element = elementSelector(x);
                    }
                    catch (Exception exception)
                    {
                        foreach (var w in map.Values)
                        {
                            w.OnError(exception);
                        }
                        observer.OnError(exception);
                        return;
                    }

                    writer.OnNext(element);
                },
                                                           e =>
                {
                    foreach (var w in map.Values)
                    {
                        w.OnError(e);
                    }
                    observer.OnError(e);
                },
                                                           () =>
                {
                    foreach (var w in map.Values)
                    {
                        w.OnCompleted();
                    }
                    observer.OnCompleted();
                });

                return refCountDisposable;
            }));
        }
Пример #20
0
        public static IObservable <TSource[]> BufferWithTimeout <TSource>(
            this IObservable <TSource> source, TimeSpan timeout, IScheduler scheduler, bool flushFirst = false)
        {
            return(Observable.CreateWithDisposable <TSource[]>(
                       observer =>
            {
                object lockObject = new object();
                List <TSource> buffer = new List <TSource>();
                bool fistToBuffer = true;

                MutableDisposable timeoutDisposable = new MutableDisposable();

                Action flushBuffer = () =>
                {
                    TSource[] values;

                    lock (lockObject)
                    {
                        values = buffer.ToArray();
                        buffer.Clear();
                        fistToBuffer = true;
                    }

                    observer.OnNext(values);
                };

                var sourceSubscription = source.Subscribe(
                    value =>
                {
                    if (fistToBuffer && flushFirst)
                    {
                        bool isFirst = false;
                        lock (lockObject)
                        {
                            if (fistToBuffer)
                            {
                                fistToBuffer = false;
                                isFirst = true;
                            }
                        }
                        if (isFirst)
                        {
                            observer.OnNext(new[] { value });
                            return;
                        }
                    }

                    lock (lockObject)
                    {
                        buffer.Add(value);
                    }

                    timeoutDisposable.Disposable = scheduler.Schedule(flushBuffer, timeout);
                },
                    observer.OnError,
                    () =>
                {
                    flushBuffer();
                    observer.OnCompleted();
                });

                return new CompositeDisposable(sourceSubscription, timeoutDisposable);
            }));
        }
Пример #21
0
        /// <summary>
        /// Projects each value of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed.
        /// </summary>
        public static IObservable <IList <TSource> > BufferWithTimeOrCount <TSource>(this IObservable <TSource> source, TimeSpan timeSpan, int count, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (count <= 0)
            {
                throw new ArgumentOutOfRangeException("count");
            }
            if (scheduler == null)
            {
                throw new ArgumentNullException("scheduler");
            }

            return(new AnonymousObservable <IList <TSource> >(observer =>
            {
                var bufferId = 0UL;
                var gate = new object();
                var data = new List <TSource>();

                var flushBuffer = new Action(() =>
                {
                    observer.OnNext(data);
                    data = new List <TSource>();
                    bufferId = unchecked (bufferId + 1);
                });

                var timer = new MutableDisposable();

                var startTimer = default(Action <ulong>);
                startTimer = myId =>
                {
                    var workItem = scheduler.Schedule(() =>
                    {
                        var shouldRecurse = false;
                        var nextId = 0UL;

                        lock (gate)
                        {
                            if (myId == bufferId)
                            {
                                flushBuffer();
                                nextId = bufferId;
                                shouldRecurse = true;
                            }
                        }

                        if (shouldRecurse)
                        {
                            startTimer(nextId);
                        }
                    }, timeSpan);

                    timer.Disposable = workItem;
                };

                startTimer(bufferId);

                var subscription = source.Subscribe(
                    x =>
                {
                    var shouldStartNewTimer = false;
                    var nextId = 0UL;

                    lock (gate)
                    {
                        data.Add(x);
                        if (data.Count == count)
                        {
                            flushBuffer();
                            nextId = bufferId;
                            shouldStartNewTimer = true;
                        }
                    }

                    if (shouldStartNewTimer)
                    {
                        startTimer(nextId);
                    }
                },
                    exception =>
                {
                    lock (gate)
                    {
                        observer.OnNext(data);
                        bufferId = unchecked (bufferId + 1);
                        observer.OnError(exception);
                    }
                },
                    () =>
                {
                    lock (gate)
                    {
                        observer.OnNext(data);
                        bufferId = unchecked (bufferId + 1);
                        observer.OnCompleted();
                    }
                }
                    );

                return new CompositeDisposable(subscription, timer);
            }));
        }
Пример #22
0
        /// <summary>
        /// Transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
        /// </summary>
        public static IObservable <TSource> Switch <TSource>(this IObservable <IObservable <TSource> > source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            return(new AnonymousObservable <TSource>(observer =>
            {
                var gate = new object();
                var innerSubscription = new MutableDisposable();
                var subscription = new MutableDisposable();
                var isStopped = false;

                subscription.Disposable = source.Subscribe(
                    innerSource =>
                {
                    var d = new MutableDisposable();
                    innerSubscription.Disposable = d;
                    d.Disposable = innerSource.Subscribe(
                        x =>
                    {
                        lock (gate)
                            observer.OnNext(x);
                    },
                        exception =>
                    {
                        subscription.Dispose();
                        innerSubscription.Dispose();
                        lock (gate)
                            observer.OnError(exception);
                    },
                        () =>
                    {
                        innerSubscription.Disposable = null;
                        if (isStopped)
                        {
                            lock (gate)
                                observer.OnCompleted();
                        }
                    });
                },
                    exception =>
                {
                    innerSubscription.Dispose();
                    lock (gate)
                        observer.OnError(exception);
                },
                    () =>
                {
                    isStopped = true;
                    if (innerSubscription.Disposable == null)
                    {
                        lock (gate)
                            observer.OnCompleted();
                    }
                });

                return new CompositeDisposable(subscription, innerSubscription);
            }));
        }