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 javafx.beans.InvalidationListener;
29 import javafx.beans.value.ChangeListener;
30 import javafx.beans.value.ObservableValue;
31
32 import java.util.Arrays;
33
34 /**
35 * A convenience class for creating implementations of {@link javafx.beans.value.ObservableValue}.
36 * It contains all of the infrastructure support for value invalidation- and
37 * change event notification.
38 *
39 * This implementation can handle adding and removing listeners while the
40 * observers are being notified, but it is not thread-safe.
41 *
42 *
43 */
44 public abstract class ExpressionHelper<T> extends ExpressionHelperBase {
45
46 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
47 // Static methods
48
49 public static <T> ExpressionHelper<T> addListener(ExpressionHelper<T> helper, ObservableValue<T> observable, InvalidationListener listener) {
50 if ((observable == null) || (listener == null)) {
51 throw new NullPointerException();
52 }
53 observable.getValue(); // validate observable
54 return (helper == null)? new SingleInvalidation<T>(observable, listener) : helper.addListener(listener);
55 }
56
57 public static <T> ExpressionHelper<T> removeListener(ExpressionHelper<T> helper, InvalidationListener listener) {
58 if (listener == null) {
59 throw new NullPointerException();
60 }
61 return (helper == null)? null : helper.removeListener(listener);
62 }
63
64 public static <T> ExpressionHelper<T> addListener(ExpressionHelper<T> helper, ObservableValue<T> observable, ChangeListener<? super T> listener) {
65 if ((observable == null) || (listener == null)) {
66 throw new NullPointerException();
67 }
68 return (helper == null)? new SingleChange<T>(observable, listener) : helper.addListener(listener);
69 }
70
71 public static <T> ExpressionHelper<T> removeListener(ExpressionHelper<T> helper, ChangeListener<? super T> listener) {
72 if (listener == null) {
73 throw new NullPointerException();
74 }
75 return (helper == null)? null : helper.removeListener(listener);
76 }
77
78 public static <T> void fireValueChangedEvent(ExpressionHelper<T> helper) {
79 if (helper != null) {
80 helper.fireValueChangedEvent();
81 }
82 }
83
84 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
85 // Common implementations
86
87 protected final ObservableValue<T> observable;
88
89 private ExpressionHelper(ObservableValue<T> observable) {
90 this.observable = observable;
91 }
92
93 protected abstract ExpressionHelper<T> addListener(InvalidationListener listener);
94 protected abstract ExpressionHelper<T> removeListener(InvalidationListener listener);
95
96 protected abstract ExpressionHelper<T> addListener(ChangeListener<? super T> listener);
97 protected abstract ExpressionHelper<T> removeListener(ChangeListener<? super T> listener);
98
99 protected abstract void fireValueChangedEvent();
100
101 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
102 // Implementations
103
104 private static class SingleInvalidation<T> extends ExpressionHelper<T> {
105
106 private final InvalidationListener listener;
107
108 private SingleInvalidation(ObservableValue<T> expression, InvalidationListener listener) {
109 super(expression);
110 this.listener = listener;
111 }
112
113 @Override
114 protected ExpressionHelper<T> addListener(InvalidationListener listener) {
115 return new Generic<T>(observable, this.listener, listener);
116 }
117
118 @Override
119 protected ExpressionHelper<T> removeListener(InvalidationListener listener) {
120 return (listener.equals(this.listener))? null : this;
121 }
122
123 @Override
124 protected ExpressionHelper<T> addListener(ChangeListener<? super T> listener) {
125 return new Generic<T>(observable, this.listener, listener);
126 }
127
128 @Override
129 protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
130 return this;
131 }
132
133 @Override
134 protected void fireValueChangedEvent() {
135 try {
136 listener.invalidated(observable);
137 } catch (Exception e) {
138 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
139 }
140 }
141 }
142
143 private static class SingleChange<T> extends ExpressionHelper<T> {
144
145 private final ChangeListener<? super T> listener;
146 private T currentValue;
147
148 private SingleChange(ObservableValue<T> observable, ChangeListener<? super T> listener) {
149 super(observable);
150 this.listener = listener;
151 this.currentValue = observable.getValue();
152 }
153
154 @Override
155 protected ExpressionHelper<T> addListener(InvalidationListener listener) {
156 return new Generic<T>(observable, listener, this.listener);
157 }
158
159 @Override
160 protected ExpressionHelper<T> removeListener(InvalidationListener listener) {
161 return this;
162 }
163
164 @Override
165 protected ExpressionHelper<T> addListener(ChangeListener<? super T> listener) {
166 return new Generic<T>(observable, this.listener, listener);
167 }
168
169 @Override
170 protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
171 return (listener.equals(this.listener))? null : this;
172 }
173
174 @Override
175 protected void fireValueChangedEvent() {
176 final T oldValue = currentValue;
177 currentValue = observable.getValue();
178 final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
179 if (changed) {
180 try {
181 listener.changed(observable, oldValue, currentValue);
182 } catch (Exception e) {
183 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
184 }
185 }
186 }
187 }
188
189 private static class Generic<T> extends ExpressionHelper<T> {
190
191 private InvalidationListener[] invalidationListeners;
192 private ChangeListener<? super T>[] changeListeners;
193 private int invalidationSize;
194 private int changeSize;
195 private boolean locked;
196 private T currentValue;
197
198 private Generic(ObservableValue<T> observable, InvalidationListener listener0, InvalidationListener listener1) {
199 super(observable);
200 this.invalidationListeners = new InvalidationListener[] {listener0, listener1};
201 this.invalidationSize = 2;
202 }
203
204 private Generic(ObservableValue<T> observable, ChangeListener<? super T> listener0, ChangeListener<? super T> listener1) {
205 super(observable);
206 this.changeListeners = new ChangeListener[] {listener0, listener1};
207 this.changeSize = 2;
208 this.currentValue = observable.getValue();
209 }
210
211 private Generic(ObservableValue<T> observable, InvalidationListener invalidationListener, ChangeListener<? super T> changeListener) {
212 super(observable);
213 this.invalidationListeners = new InvalidationListener[] {invalidationListener};
214 this.invalidationSize = 1;
215 this.changeListeners = new ChangeListener[] {changeListener};
216 this.changeSize = 1;
217 this.currentValue = observable.getValue();
218 }
219
220 @Override
221 protected Generic<T> addListener(InvalidationListener listener) {
222 if (invalidationListeners == null) {
223 invalidationListeners = new InvalidationListener[] {listener};
224 invalidationSize = 1;
225 } else {
226 final int oldCapacity = invalidationListeners.length;
227 if (locked) {
228 final int newCapacity = (invalidationSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1;
229 invalidationListeners = Arrays.copyOf(invalidationListeners, newCapacity);
230 } else if (invalidationSize == oldCapacity) {
231 invalidationSize = trim(invalidationSize, invalidationListeners);
232 if (invalidationSize == oldCapacity) {
233 final int newCapacity = (oldCapacity * 3)/2 + 1;
234 invalidationListeners = Arrays.copyOf(invalidationListeners, newCapacity);
235 }
236 }
237 invalidationListeners[invalidationSize++] = listener;
238 }
239 return this;
240 }
241
242 @Override
243 protected ExpressionHelper<T> removeListener(InvalidationListener listener) {
244 if (invalidationListeners != null) {
245 for (int index = 0; index < invalidationSize; index++) {
246 if (listener.equals(invalidationListeners[index])) {
247 if (invalidationSize == 1) {
248 if (changeSize == 1) {
249 return new SingleChange<T>(observable, changeListeners[0]);
250 }
251 invalidationListeners = null;
252 invalidationSize = 0;
253 } else if ((invalidationSize == 2) && (changeSize == 0)) {
254 return new SingleInvalidation<T>(observable, invalidationListeners[1-index]);
255 } else {
256 final int numMoved = invalidationSize - index - 1;
257 final InvalidationListener[] oldListeners = invalidationListeners;
258 if (locked) {
259 invalidationListeners = new InvalidationListener[invalidationListeners.length];
260 System.arraycopy(oldListeners, 0, invalidationListeners, 0, index);
261 }
262 if (numMoved > 0) {
263 System.arraycopy(oldListeners, index+1, invalidationListeners, index, numMoved);
264 }
265 invalidationSize--;
266 if (!locked) {
267 invalidationListeners[invalidationSize] = null; // Let gc do its work
268 }
269 }
270 break;
271 }
272 }
273 }
274 return this;
275 }
276
277 @Override
278 protected ExpressionHelper<T> addListener(ChangeListener<? super T> listener) {
279 if (changeListeners == null) {
280 changeListeners = new ChangeListener[] {listener};
281 changeSize = 1;
282 } else {
283 final int oldCapacity = changeListeners.length;
284 if (locked) {
285 final int newCapacity = (changeSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1;
286 changeListeners = Arrays.copyOf(changeListeners, newCapacity);
287 } else if (changeSize == oldCapacity) {
288 changeSize = trim(changeSize, changeListeners);
289 if (changeSize == oldCapacity) {
290 final int newCapacity = (oldCapacity * 3)/2 + 1;
291 changeListeners = Arrays.copyOf(changeListeners, newCapacity);
292 }
293 }
294 changeListeners[changeSize++] = listener;
295 }
296 if (changeSize == 1) {
297 currentValue = observable.getValue();
298 }
299 return this;
300 }
301
302 @Override
303 protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
304 if (changeListeners != null) {
305 for (int index = 0; index < changeSize; index++) {
306 if (listener.equals(changeListeners[index])) {
307 if (changeSize == 1) {
308 if (invalidationSize == 1) {
309 return new SingleInvalidation<T>(observable, invalidationListeners[0]);
310 }
311 changeListeners = null;
312 changeSize = 0;
313 } else if ((changeSize == 2) && (invalidationSize == 0)) {
314 return new SingleChange<T>(observable, changeListeners[1-index]);
315 } else {
316 final int numMoved = changeSize - index - 1;
317 final ChangeListener<? super T>[] oldListeners = changeListeners;
318 if (locked) {
319 changeListeners = new ChangeListener[changeListeners.length];
320 System.arraycopy(oldListeners, 0, changeListeners, 0, index);
321 }
322 if (numMoved > 0) {
323 System.arraycopy(oldListeners, index+1, changeListeners, index, numMoved);
324 }
325 changeSize--;
326 if (!locked) {
327 changeListeners[changeSize] = null; // Let gc do its work
328 }
329 }
330 break;
331 }
332 }
333 }
334 return this;
335 }
336
337 @Override
338 protected void fireValueChangedEvent() {
339 final InvalidationListener[] curInvalidationList = invalidationListeners;
340 final int curInvalidationSize = invalidationSize;
341 final ChangeListener<? super T>[] curChangeList = changeListeners;
342 final int curChangeSize = changeSize;
343
344 try {
345 locked = true;
346 for (int i = 0; i < curInvalidationSize; i++) {
347 try {
348 curInvalidationList[i].invalidated(observable);
349 } catch (Exception e) {
350 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
351 }
352 }
353 if (curChangeSize > 0) {
354 final T oldValue = currentValue;
355 currentValue = observable.getValue();
356 final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
357 if (changed) {
358 for (int i = 0; i < curChangeSize; i++) {
359 try {
360 curChangeList[i].changed(observable, oldValue, currentValue);
361 } catch (Exception e) {
362 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
363 }
364 }
365 }
366 }
367 } finally {
368 locked = false;
369 }
370 }
371 }
372
373 }