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