using System; using System.Collections.Generic; using System.Threading.Tasks; using JetBrains.Core; using JetBrains.Lifetimes; // ReSharper disable InconsistentNaming namespace JetBrains.Collections.Viewable { public static class ReactiveEx { public static void Fire(this ISignal me) { me.Fire(Unit.Instance); } public static void AddLifetimed(this ICollection me, Lifetime lifetime, T item) { lifetime.Bracket(() => me.Add(item), () => me.Remove(item)); } public static void Advise(this ISource me, Lifetime lifetime, Action handler) { me.Advise(lifetime, _ => { handler(); }); } public static void AdviseNotNull(this ISource me, Lifetime lifetime, Action handler) where T : class { me.Advise(lifetime, v => { if (v != null) handler(v); }); } public static void AdviseNotNull(this ISource me, Lifetime lifetime, Action handler) where T : struct { me.Advise(lifetime, v => { if (v != null) handler(v.Value); }); } public static void AdviseUntil(this ISource me, Lifetime lifetime, Func handler) { if (!lifetime.IsAlive) return; var definition = Lifetime.Define(lifetime); me.Advise(definition.Lifetime, v => { if (handler(v)) definition.Terminate(); }); } public static void AdviseOnce(this ISource me, Lifetime lifetime, Action handler) { if (lifetime.IsNotAlive) return; var definition = lifetime.CreateNested(); me.Advise(definition.Lifetime, v => { definition.Terminate(); handler(v); }); } public static void View(this IReadonlyProperty me, Lifetime lifetime, Action handler) { if (!lifetime.IsAlive) return; // nested lifetime is needed due to exception that could be thrown // while viewing a property change right at the moment of lifetime's termination // but before handler gets removed var lf = lifetime == Lifetime.Eternal ? lifetime : Lifetime.Define(lifetime).Lifetime; var seq = new SequentialLifetimes(lf); me.Advise(lf, v => handler(seq.Next(), v)); } public static void ViewNotNull(this IReadonlyProperty me, Lifetime lifetime, Action handler) where T: class { me.View(lifetime, (lf, v) => { if (v != null) handler(lf, v); }); } public static void ViewNotNull(this IReadonlyProperty me, Lifetime lifetime, Action handler) where T: struct { me.View(lifetime, (lf, v) => { if (v != null) handler(lf, v.Value); }); } public static void ViewNull(this IReadonlyProperty me, Lifetime lifetime, Action handler) where T: class { me.View(lifetime, (lf, v) => { if (v == null) handler(lf); }); } public static void ViewNull(this IReadonlyProperty me, Lifetime lifetime, Action handler) where T: struct { me.View(lifetime, (lf, v) => { if (v == null) handler(lf); }); } public static void View(this IViewableMap me, Lifetime lifetime, Action> handler) where K: notnull { View(me, lifetime, (lf, k, v) => handler(lf, JetKeyValuePair.Of(k, v))); } public static void FlowInto(this IViewableMap me, Lifetime lifetime, IDictionary storage) where K : notnull { me.Advise(lifetime, e => { if (e.IsAdd || e.IsUpdate) storage[e.Key] = e.NewValue; else if (e.IsRemove) storage.Remove(e.Key); else throw new ArgumentOutOfRangeException($"Unexpected kind: {e.Kind}"); }); } [Obsolete("This method has horrible performance when adding 100+ items")] public static void AddOrReplaceLifetimed(this IViewableMap me, Lifetime lifetime, K k, Func vfun) where K : notnull { var def = lifetime.CreateNested(); me[k] = vfun(def.Lifetime); me.Change.Advise(def.Lifetime, _event => { if (_event.Key.Equals(k)) def.Terminate(); }); } public static void AdviseAddRemove(this IViewableMap me, Lifetime lifetime, Action handler) where K : notnull { me.Advise(lifetime, e => { switch (e.Kind) { case AddUpdateRemove.Add: handler(AddRemove.Add, e.Key, e.NewValue); break; case AddUpdateRemove.Update: handler(AddRemove.Remove, e.Key, e.OldValue); handler(AddRemove.Add, e.Key, e.NewValue); break; case AddUpdateRemove.Remove: handler(AddRemove.Remove, e.Key, e.OldValue); break; default: throw new ArgumentOutOfRangeException("" + e.Kind); } }); } public static void Advise(this IViewableSet me, Lifetime lifetime, Action handler) where T: notnull { me.Advise(lifetime, e => handler(e.Kind, e.Value)); } public static void View(this IViewableSet me, Lifetime lifetime, Action handler) where T : notnull { var lifetimes = new Dictionary(me.Count); me.Advise(lifetime, (kind, value) => { switch (kind) { case AddRemove.Add: var def = Lifetime.Define(lifetime); lifetimes[value] = def; handler(def.Lifetime, value); break; case AddRemove.Remove: def = lifetimes[value]; lifetimes.Remove(value); def.Terminate(); break; default: throw new ArgumentOutOfRangeException($"illegal enum value: {kind}"); } }); } public static void View(this IViewableMap me, Lifetime lifetime, Action handler) where K : notnull { #nullable disable var lifetimes = new Dictionary, LifetimeDefinition>(); me.AdviseAddRemove(lifetime, (kind, key, value) => { var entry = JetKeyValuePair.Of(key, value); switch (kind) { case AddRemove.Add: var def = Lifetime.Define(lifetime); lifetimes.Add(entry, def); handler(def.Lifetime, entry.Key, entry.Value); break; case AddRemove.Remove: def = lifetimes[entry]; lifetimes.Remove(entry); def.Terminate(); break; default: throw new ArgumentOutOfRangeException($"Illegal enum value: {kind}"); } }); #nullable enable } /// /// Same as but all events with kind= are /// split into two sequential events with kinds and /// /// /// /// /// /// public static void AdviseAddRemove(this IViewableList me, Lifetime lifetime, Action handler) where V : notnull { #nullable disable me.Advise(lifetime, e => { switch (e.Kind) { case AddUpdateRemove.Add: handler(AddRemove.Add, e.Index, e.NewValue); break; case AddUpdateRemove.Update: handler(AddRemove.Remove, e.Index, e.OldValue); handler(AddRemove.Add, e.Index, e.NewValue); break; case AddUpdateRemove.Remove: handler(AddRemove.Remove, e.Index, e.OldValue); break; default: throw new ArgumentOutOfRangeException($"Illegal enum value: {e.Kind}"); } }); #nullable enable } /// /// Structured subscription for List. Behaves same as /// except handler receives parameter /// that terminates when someone removes or updates element on corresponding index. /// /// /// /// /// /// public static void View(this IViewableList me, Lifetime lifetime, Action handler) where V : notnull { var lifetimes = new List(me.Count); me.AdviseAddRemove(lifetime, (kind, index, value) => { switch (kind) { case AddRemove.Add: var def = Lifetime.Define(lifetime); lifetimes.Insert(index, def); handler(def.Lifetime, index, value); break; case AddRemove.Remove: def = lifetimes[index]; lifetimes.RemoveAt(index); def.Terminate(); break; default: throw new ArgumentOutOfRangeException($"Illegal enum value: {kind}"); } }); } public static bool HasValue(this IReadonlyProperty me) { return me.Maybe.HasValue; } public static bool HasTrueValue(this IReadonlyProperty me) { return me.Maybe.HasValue && me.Maybe.Value; } public static void Compose(this IReadonlyProperty first, Lifetime lifetime, IReadonlyProperty second, Action composer) { first.Advise(lifetime, v => { if (second.HasValue()) composer(v, second.Value); }); second.Advise(lifetime, v => { if (first.HasValue()) composer(first.Value, v); }); } public static IReadonlyProperty Compose(this IReadonlyProperty first, Lifetime lifetime, IReadonlyProperty second, Func composer) { var res = new ViewableProperty(); first.Advise(lifetime, v => { if (second.HasValue()) res.Value = composer(v, second.Value); }); second.Advise(lifetime, v => { if (first.HasValue()) res.Value = composer(first.Value, v); }); return res; } public static void WhenTrue(this IReadonlyProperty property, Lifetime lifetime, Action handler) { if (property == null) throw new ArgumentNullException(nameof(property)); if (handler == null) throw new ArgumentNullException(nameof(handler)); property.View(lifetime, (lf, value) => { if (value) handler(lf); }); } public static void WhenFalse(this IReadonlyProperty property, Lifetime lifetime, Action handler) { if (property == null) throw new ArgumentNullException(nameof(property)); if (handler == null) throw new ArgumentNullException(nameof(handler)); property.View(lifetime, (lf, value) => { if (!value) handler(lf); }); } private class MappedSink : ISource { private readonly ISource myOriginal; private readonly Func myMap; public MappedSink(ISource original, Func map) { myOriginal = original; myMap = map; } public void Advise(Lifetime lifetime, Action handler) { myOriginal.Advise(lifetime, x => handler(myMap(x))); } } private class MappedProperty : IReadonlyProperty { private readonly IViewableProperty mySource; private readonly Func myMap; public MappedProperty(IViewableProperty source, Func map) { mySource = source; myMap = map; Change = new MappedSink(source.Change, myMap); } public void Advise(Lifetime lifetime, Action handler) => mySource.Advise(lifetime, v => handler(myMap(v))); public ISource Change { get; } public Maybe Maybe => mySource.Maybe.Select(myMap); public R Value => Maybe.Value; } public static IReadonlyProperty Select(this IViewableProperty source, Func f) { return new MappedProperty(source, f); } public static Task NextNotNullValueAsync(this ISource source, Lifetime lifetime) { return source.NextValueAsync(lifetime, value => value != null); } public static Task NextTrueValueAsync(this ISource source, Lifetime lifetime) { return source.NextValueAsync(lifetime, value => value); } public static Task NextFalseValueAsync(this ISource source, Lifetime lifetime) { return source.NextValueAsync(lifetime, value => !value); } public static Task NextValueAsync(this ISource source, Lifetime lifetime) { return source.NextValueAsync(lifetime, _ => true); } public static Task NextValueAsync(this ISource source, Lifetime lifetime, Func condition) { var tcs = new TaskCompletionSource(); var definition = lifetime.CreateNested(); definition.SynchronizeWith(tcs); source.Advise(definition.Lifetime, v => { if (condition(v)) tcs.TrySetResult(v); }); return tcs.Task; } } }