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 void set(T value);
74
75 void mutate(Consumer<T> mutator);
76
77 default T nullableValue() {
78
79 return value().get();
80 }
81
82
83
84
85
86 class Wrapper<T> implements ContextProperty<T> {
87
88 private final ContextProperty<T> delegate;
89
90 public Wrapper(ContextProperty<T> delegate) {
91 this.delegate = delegate;
92 }
93
94 @Override
95 public Disposable observeNullable(Consumer<T> action) {
96 return this.delegate.observeNullable(action);
97 }
98
99 @Override
100 public Disposable observe(Consumer<Optional<T>> action) {
101 return this.delegate.observe(action);
102 }
103
104 @Override
105 public Optional<T> value() {
106 return this.delegate.value();
107 }
108
109 @Override
110 public void set(T value) {
111 this.delegate.set(value);
112 }
113
114 @Override
115 public void mutate(Consumer<T> mutator) {
116 this.delegate.mutate(mutator);
117 }
118 }
119
120
121
122
123
124
125
126
127
128 class Impl<T> implements ContextProperty<T> {
129
130 private static final Logger log = LoggerFactory.getLogger(Impl.class);
131
132 private T lastValue = null;
133
134 private Subject<Optional<T>> subject = BehaviorSubject.createDefault(Optional.empty());
135
136 Impl() {
137 subject.onNext(Optional.empty());
138 }
139
140 public Disposable observeNullable(Consumer<T> action) {
141 return subject
142 .toFlowable(BackpressureStrategy.LATEST)
143 .map(optional -> optional)
144 .subscribe(
145 optional -> action.accept(optional.orElse(null)),
146 e -> log.error("Failed to dispatch context property change: {}", e.getMessage(), e));
147 }
148
149 @Override
150 public Disposable observe(Consumer<Optional<T>> action) {
151 return subject.subscribe(action, e -> log.error("Failed to dispatch context property change: {}", e.getMessage(), e));
152 }
153
154 @Override
155 public void mutate(Consumer<T> mutator) {
156 value().ifPresent(value -> {
157 try {
158 mutator.accept(value);
159 } catch (Exception e) {
160 log.error("{}", e.getMessage(), e);
161 }
162 doSet(value, true);
163 });
164 }
165
166 @Override
167 public Optional<T> value() {
168 return Optional.ofNullable(this.lastValue);
169 }
170
171 @Override
172 public void set(T value) {
173 doSet(value, false);
174 }
175
176 private void doSet(T value, boolean shouldNotifyOnSameItem) {
177 if (!shouldNotifyOnSameItem && Objects.equals(value, this.lastValue)) {
178 return;
179 }
180
181 this.lastValue = value;
182 this.subject.onNext(Optional.ofNullable(value));
183 }
184
185
186
187
188
189 }
190 }