1 /*
2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.javafx.binding;
27
28 import java.util.LinkedHashMap;
29 import java.util.Map;
30 import java.util.Map.Entry;
31
32 import javafx.beans.InvalidationListener;
33 import javafx.beans.value.ChangeListener;
34 import javafx.beans.value.ObservableValue;
35
36 /**
37 * A convenience class for creating implementations of {@link javafx.beans.value.ObservableValue}.
38 * It contains all of the infrastructure support for value invalidation- and
39 * change event notification.
40 *
41 * This implementation can handle adding and removing listeners while the
42 * observers are being notified, but it is not thread-safe.
43 *
44 *
45 */
46 public abstract class ExpressionHelper<T> extends ExpressionHelperBase {
47
48 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
49 // Static methods
50
51 public static <T> ExpressionHelper<T> addListener(ExpressionHelper<T> helper, ObservableValue<T> observable, InvalidationListener listener) {
52 if ((observable == null) || (listener == null)) {
53 throw new NullPointerException();
54 }
55 observable.getValue(); // validate observable
56 return (helper == null)? new SingleInvalidation<T>(observable, listener) : helper.addListener(listener);
57 }
58
59 public static <T> ExpressionHelper<T> removeListener(ExpressionHelper<T> helper, InvalidationListener listener) {
60 if (listener == null) {
61 throw new NullPointerException();
62 }
63 return (helper == null)? null : helper.removeListener(listener);
64 }
65
66 public static <T> ExpressionHelper<T> addListener(ExpressionHelper<T> helper, ObservableValue<T> observable, ChangeListener<? super T> listener) {
67 if ((observable == null) || (listener == null)) {
68 throw new NullPointerException();
69 }
70 return (helper == null)? new SingleChange<T>(observable, listener) : helper.addListener(listener);
71 }
72
73 public static <T> ExpressionHelper<T> removeListener(ExpressionHelper<T> helper, ChangeListener<? super T> listener) {
74 if (listener == null) {
75 throw new NullPointerException();
76 }
77 return (helper == null)? null : helper.removeListener(listener);
78 }
79
80 public static <T> void fireValueChangedEvent(ExpressionHelper<T> helper) {
81 if (helper != null) {
82 helper.fireValueChangedEvent();
83 }
84 }
85
86 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
87 // Common implementations
88
89 protected final ObservableValue<T> observable;
90
91 private ExpressionHelper(ObservableValue<T> observable) {
92 this.observable = observable;
93 }
94
95 protected abstract ExpressionHelper<T> addListener(InvalidationListener listener);
96 protected abstract ExpressionHelper<T> removeListener(InvalidationListener listener);
97
98 protected abstract ExpressionHelper<T> addListener(ChangeListener<? super T> listener);
99 protected abstract ExpressionHelper<T> removeListener(ChangeListener<? super T> listener);
100
101 protected abstract void fireValueChangedEvent();
102
103 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
104 // Implementations
105
106 private static class SingleInvalidation<T> extends ExpressionHelper<T> {
107
108 private final InvalidationListener listener;
109
110 private SingleInvalidation(ObservableValue<T> expression, InvalidationListener listener) {
111 super(expression);
112 this.listener = listener;
113 }
114
115 @Override
116 protected ExpressionHelper<T> addListener(InvalidationListener listener) {
117 return new Generic<T>(observable, this.listener, listener);
118 }
119
120 @Override
121 protected ExpressionHelper<T> removeListener(InvalidationListener listener) {
122 return (listener.equals(this.listener))? null : this;
123 }
124
125 @Override
126 protected ExpressionHelper<T> addListener(ChangeListener<? super T> listener) {
127 return new Generic<T>(observable, this.listener, listener);
128 }
129
130 @Override
131 protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
132 return this;
133 }
134
135 @Override
136 protected void fireValueChangedEvent() {
137 try {
138 listener.invalidated(observable);
139 } catch (Exception e) {
140 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
141 }
142 }
143 }
144
145 private static class SingleChange<T> extends ExpressionHelper<T> {
146
147 private final ChangeListener<? super T> listener;
148 private T currentValue;
149
150 private SingleChange(ObservableValue<T> observable, ChangeListener<? super T> listener) {
151 super(observable);
152 this.listener = listener;
153 this.currentValue = observable.getValue();
154 }
155
156 @Override
157 protected ExpressionHelper<T> addListener(InvalidationListener listener) {
158 return new Generic<T>(observable, listener, this.listener);
159 }
160
161 @Override
162 protected ExpressionHelper<T> removeListener(InvalidationListener listener) {
163 return this;
164 }
165
166 @Override
167 protected ExpressionHelper<T> addListener(ChangeListener<? super T> listener) {
168 return new Generic<T>(observable, this.listener, listener);
169 }
170
171 @Override
172 protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
173 return (listener.equals(this.listener))? null : this;
174 }
175
176 @Override
177 protected void fireValueChangedEvent() {
178 final T oldValue = currentValue;
179 currentValue = observable.getValue();
180 final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
181 if (changed) {
182 try {
183 listener.changed(observable, oldValue, currentValue);
184 } catch (Exception e) {
185 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
186 }
187 }
188 }
189 }
190
191 private static class Generic<T> extends ExpressionHelper<T> {
192
193 private Map<InvalidationListener, Integer> invalidationListeners = new LinkedHashMap<>();
194 private Map<ChangeListener<? super T>, Integer> changeListeners = new LinkedHashMap<>();
195 private T currentValue;
196 private int weakChangeListenerGcCount = 2;
197 private int weakInvalidationListenerGcCount = 2;
198
199 private Generic(ObservableValue<T> observable, InvalidationListener listener0, InvalidationListener listener1) {
200 super(observable);
201 this.invalidationListeners.put(listener0, 1);
202 // use merge here in case listener1 == listener0
203 this.invalidationListeners.merge(listener1, 1, Integer::sum);
204 }
205
206 private Generic(ObservableValue<T> observable, ChangeListener<? super T> listener0, ChangeListener<? super T> listener1) {
207 super(observable);
208 this.changeListeners.put(listener0, 1);
209 // use merge here in case listener1 == listener0
210 this.changeListeners.merge(listener1, 1, Integer::sum);
211 this.currentValue = observable.getValue();
212 }
213
214 private Generic(ObservableValue<T> observable, InvalidationListener invalidationListener, ChangeListener<? super T> changeListener) {
215 super(observable);
216 this.invalidationListeners.put(invalidationListener, 1);
217 this.changeListeners.put(changeListener, 1);
218 this.currentValue = observable.getValue();
219 }
220
221 @Override
222 protected Generic<T> addListener(InvalidationListener listener) {
223 if (invalidationListeners.size() == weakInvalidationListenerGcCount) {
224 removeWeakListeners(invalidationListeners);
225 if (invalidationListeners.size() == weakInvalidationListenerGcCount) {
226 weakInvalidationListenerGcCount = (weakInvalidationListenerGcCount * 3)/2 + 1;
227 }
228 }
229 invalidationListeners.merge(listener, 1, Integer::sum);
230 return this;
231 }
232
233 @Override
234 protected ExpressionHelper<T> removeListener(InvalidationListener listener) {
235 if (invalidationListeners.containsKey(listener)) {
236 if (invalidationListeners.merge(listener, -1, Integer::sum) == 0) {
237 invalidationListeners.remove(listener);
238 if (invalidationListeners.isEmpty() && changeListeners.size() == 1) {
239 return new SingleChange<T>(observable, changeListeners.keySet().iterator().next());
240 } else if ((invalidationListeners.size() == 1) && changeListeners.isEmpty()) {
241 return new SingleInvalidation<T>(observable, invalidationListeners.keySet().iterator().next());
242 }
243 }
244 }
245 return this;
246 }
247
248 @Override
249 protected ExpressionHelper<T> addListener(ChangeListener<? super T> listener) {
250 if (changeListeners.size() == weakChangeListenerGcCount) {
251 removeWeakListeners(changeListeners);
252 if (changeListeners.size() == weakChangeListenerGcCount) {
253 weakChangeListenerGcCount = (weakChangeListenerGcCount * 3)/2 + 1;
254 }
255 }
256 changeListeners.merge(listener, 1, Integer::sum);
257 if (changeListeners.size() == 1) {
258 currentValue = observable.getValue();
259 }
260 return this;
261 }
262
263 @Override
264 protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
265 if (changeListeners.containsKey(listener)) {
266 if (changeListeners.merge(listener, -1, Integer::sum) == 0) {
267 changeListeners.remove(listener);
268 if (changeListeners.isEmpty() && invalidationListeners.size() == 1) {
269 return new SingleInvalidation<T>(observable, invalidationListeners.keySet().iterator().next());
270 } else if ((changeListeners.size() == 1) && invalidationListeners.isEmpty()) {
271 return new SingleChange<T>(observable, changeListeners.keySet().iterator().next());
272 }
273 }
274 }
275 return this;
276 }
277
278 @Override
279 protected void fireValueChangedEvent() {
280 // Take a copy of listeners to ensure adding and removing listeners
281 // while the observers are being notified is safe
282 final Map<InvalidationListener, Integer> curInvalidationList = new LinkedHashMap<>(invalidationListeners);
283 final Map<ChangeListener<? super T>, Integer> curChangeList = new LinkedHashMap<>(changeListeners);
284
285 curInvalidationList.entrySet().forEach(entry -> fireInvalidationListeners(entry));
286
287 if (!curChangeList.isEmpty()) {
288 final T oldValue = currentValue;
289 currentValue = observable.getValue();
290 final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
291 if (changed) {
292 curChangeList.entrySet().forEach(entry -> fireChangeListeners(oldValue, entry));
293 }
294 }
295 }
296
297 private void fireInvalidationListeners(Entry<InvalidationListener, Integer> entry) {
298 final InvalidationListener listener = entry.getKey();
299 final int registrationCount = entry.getValue();
300 try {
301 for (int i = 0; i < registrationCount; i++) {
302 listener.invalidated(observable);
303 }
304 } catch (Exception e) {
305 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(
306 Thread.currentThread(), e);
307 }
308 }
309
310 private void fireChangeListeners(final T oldValue, Entry<ChangeListener<? super T>, Integer> entry) {
311 final ChangeListener<? super T> listener = entry.getKey();
312 final int registrationCount = entry.getValue();
313 try {
314 for (int i = 0; i < registrationCount; i++) {
315 listener.changed(observable, oldValue, currentValue);
316 }
317 } catch (Exception e) {
318 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(
319 Thread.currentThread(), e);
320 }
321 }
322 }
323 }