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
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 }
|
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
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 }
|