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 }