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 }