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 }