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 }