1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework;
35
36
37 import java.io.Serializable;
38 import java.util.Objects;
39 import java.util.Optional;
40
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import io.reactivex.BackpressureStrategy;
45 import io.reactivex.disposables.Disposable;
46 import io.reactivex.functions.Consumer;
47 import io.reactivex.subjects.BehaviorSubject;
48 import io.reactivex.subjects.Subject;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public interface ContextProperty<T> extends Serializable {
64
65 Logger log = LoggerFactory.getLogger(ContextProperty.class);
66
67 Disposable observeNullable(Consumer<T> action);
68
69 Disposable observe(Consumer<Optional<T>> action);
70
71 Optional<T> value();
72
73 default void set(T value) {
74 set(value, false);
75 }
76
77 void set(T value, boolean shouldNotifyOnSameItem);
78
79 void mutate(Consumer<T> mutator);
80
81 default T nullableValue() {
82
83 return value().get();
84 }
85
86
87
88
89
90 class Wrapper<T> implements ContextProperty<T> {
91
92 private final ContextProperty<T> delegate;
93
94 public Wrapper(ContextProperty<T> delegate) {
95 this.delegate = delegate;
96 }
97
98 @Override
99 public Disposable observeNullable(Consumer<T> action) {
100 return this.delegate.observeNullable(action);
101 }
102
103 @Override
104 public Disposable observe(Consumer<Optional<T>> action) {
105 return this.delegate.observe(action);
106 }
107
108 @Override
109 public Optional<T> value() {
110 return this.delegate.value();
111 }
112
113 @Override
114 public void set(T value, boolean shouldNotifyOnSameItem) {
115 this.delegate.set(value, shouldNotifyOnSameItem);
116 }
117
118 @Override
119 public void mutate(Consumer<T> mutator) {
120 this.delegate.mutate(mutator);
121 }
122 }
123
124
125
126
127
128
129
130
131
132 class Impl<T> implements ContextProperty<T> {
133
134 private static final Logger log = LoggerFactory.getLogger(Impl.class);
135
136 private T lastValue = null;
137
138 private T pendingValueUpdate = null;
139
140 private Subject<Optional<T>> subject = BehaviorSubject.createDefault(Optional.empty());
141
142 private boolean updatesMuted;
143
144
145 Impl() {
146 subject.onNext(Optional.empty());
147 }
148
149 public Disposable observeNullable(Consumer<T> action) {
150 return subject
151 .toFlowable(BackpressureStrategy.LATEST)
152 .map(optional -> optional)
153 .subscribe(
154 optional -> {
155 muteUpdates();
156 try {
157 action.accept(optional.orElse(null));
158 } finally {
159 unmuteUpdates();
160 }
161 },
162 e -> log.error("Failed to dispatch context property change: {}", e.getMessage(), e));
163 }
164
165 @Override
166 public Disposable observe(Consumer<Optional<T>> action) {
167 return subject.subscribe(value -> {
168 muteUpdates();
169 try {
170 action.accept(value);
171 } finally {
172 unmuteUpdates();
173 }
174 }, e -> log.error("Failed to dispatch context property change: {}", e.getMessage(), e));
175
176 }
177
178 @Override
179 public void mutate(Consumer<T> mutator) {
180 value().ifPresent(value -> {
181 try {
182 mutator.accept(value);
183 } catch (Exception e) {
184 log.error("{}", e.getMessage(), e);
185 }
186 doSet(value, true);
187 });
188 }
189
190 @Override
191 public Optional<T> value() {
192 return Optional.ofNullable(this.lastValue);
193 }
194
195 @Override
196 public void set(T value, boolean shouldNotifyOnSameItem) {
197 doSet(value, shouldNotifyOnSameItem);
198 }
199
200 public void doSet(T value, boolean shouldNotifyOnSameItem) {
201 if (updatesMuted) {
202 pendingValueUpdate = value;
203 return;
204 }
205
206 if (!shouldNotifyOnSameItem && Objects.equals(value, this.lastValue)) {
207 return;
208 }
209
210 this.lastValue = value;
211 muteUpdates();
212 try {
213 this.subject.onNext(Optional.ofNullable(value));
214 } finally {
215 unmuteUpdates();
216 }
217 }
218
219 private void muteUpdates() {
220 this.updatesMuted = true;
221 }
222
223 private void unmuteUpdates() {
224 if (pendingValueUpdate != null) {
225 doSet(pendingValueUpdate, false);
226 pendingValueUpdate = null;
227 }
228
229 this.updatesMuted = false;
230 }
231 }
232 }