1 /* 2 * Copyright (c) 2010, 2019, 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.Observable; 30 import javafx.beans.WeakInvalidationListener; 31 import javafx.beans.binding.Binding; 32 import javafx.beans.binding.BooleanBinding; 33 import javafx.beans.binding.DoubleBinding; 34 import javafx.beans.binding.FloatBinding; 35 import javafx.beans.binding.IntegerBinding; 36 import javafx.beans.binding.LongBinding; 37 import javafx.beans.binding.ObjectBinding; 38 import javafx.beans.binding.StringBinding; 39 import javafx.beans.value.ObservableBooleanValue; 40 import javafx.beans.value.ObservableNumberValue; 41 import javafx.beans.value.ObservableValue; 42 import javafx.collections.FXCollections; 43 import javafx.collections.ObservableList; 44 45 import com.sun.javafx.property.JavaBeanAccessHelper; 46 import com.sun.javafx.logging.PlatformLogger; 47 import com.sun.javafx.logging.PlatformLogger.Level; 48 import com.sun.javafx.property.PropertyReference; 49 import java.util.Arrays; 50 51 /** 52 * A binding used to get a member, such as <code>a.b.c</code>. The value of the 53 * binding will be "c", or null if c could not be reached (due to "b" not having 54 * a "c" property, or "b" being null). "a" must be passed to the constructor of 55 * the SelectBinding and may be any dependency. All subsequent links are simply 56 * PropertyReferences. 57 * <p> 58 * With a SelectBinding, "a" must always exist. Usually "a" will refer to 59 * "this", or some concrete object. "b"* will be some intermediate step in the 60 * select binding. 61 */ 62 public class SelectBinding { 63 64 private SelectBinding() {} 65 66 public static class AsObject<T> extends ObjectBinding<T> { 67 68 private final SelectBindingHelper helper; 69 70 public AsObject(ObservableValue<?> root, String... steps) { 71 helper = new SelectBindingHelper(this, root, steps); 72 } 73 74 public AsObject(Object root, String... steps) { 75 helper = new SelectBindingHelper(this, root, steps); 76 } 77 78 @Override 79 public void dispose() { 80 helper.unregisterListener(); 81 } 82 83 @Override 84 protected void onInvalidating() { 85 helper.unregisterListener(); 86 } 87 88 @SuppressWarnings("unchecked") 89 @Override 90 protected T computeValue() { 91 final ObservableValue<?> observable = helper.getObservableValue(); 92 if (observable == null) { 93 return null; 94 } 95 try { 96 return (T)observable.getValue(); 97 } catch (ClassCastException ex) { 98 Logging.getLogger().warning("Value of select-binding has wrong type, returning null.", ex); 99 } 100 return null; 101 } 102 103 104 @Override 105 public ObservableList<ObservableValue<?>> getDependencies() { 106 return helper.getDependencies(); 107 } 108 109 } 110 111 public static class AsBoolean extends BooleanBinding { 112 113 private static final boolean DEFAULT_VALUE = false; 114 115 private final SelectBindingHelper helper; 116 117 public AsBoolean(ObservableValue<?> root, String... steps) { 118 helper = new SelectBindingHelper(this, root, steps); 119 } 120 121 public AsBoolean(Object root, String... steps) { 122 helper = new SelectBindingHelper(this, root, steps); 123 } 124 125 @Override 126 public void dispose() { 127 helper.unregisterListener(); 128 } 129 130 @Override 131 protected void onInvalidating() { 132 helper.unregisterListener(); 133 } 134 135 @Override 136 protected boolean computeValue() { 137 final ObservableValue<?> observable = helper.getObservableValue(); 138 if (observable == null) { 139 return DEFAULT_VALUE; 140 } 141 if (observable instanceof ObservableBooleanValue) { 142 return ((ObservableBooleanValue)observable).get(); 143 } 144 try { 145 return (Boolean)observable.getValue(); 146 } catch (NullPointerException ex) { 147 Logging.getLogger().fine("Value of select binding is null, returning default value", ex); 148 } catch (ClassCastException ex) { 149 Logging.getLogger().warning("Value of select-binding has wrong type, returning default value.", ex); 150 } 151 return DEFAULT_VALUE; 152 } 153 154 @Override 155 public ObservableList<ObservableValue<?>> getDependencies() { 156 return helper.getDependencies(); 157 } 158 159 } 160 161 public static class AsDouble extends DoubleBinding { 162 163 private static final double DEFAULT_VALUE = 0.0; 164 165 private final SelectBindingHelper helper; 166 167 public AsDouble(ObservableValue<?> root, String... steps) { 168 helper = new SelectBindingHelper(this, root, steps); 169 } 170 171 public AsDouble(Object root, String... steps) { 172 helper = new SelectBindingHelper(this, root, steps); 173 } 174 175 @Override 176 public void dispose() { 177 helper.unregisterListener(); 178 } 179 180 @Override 181 protected void onInvalidating() { 182 helper.unregisterListener(); 183 } 184 185 @Override 186 protected double computeValue() { 187 final ObservableValue<?> observable = helper.getObservableValue(); 188 if (observable == null) { 189 return DEFAULT_VALUE; 190 } 191 if (observable instanceof ObservableNumberValue) { 192 return ((ObservableNumberValue)observable).doubleValue(); 193 } 194 try { 195 return ((Number)observable.getValue()).doubleValue(); 196 } catch (NullPointerException ex) { 197 Logging.getLogger().fine("Value of select binding is null, returning default value", ex); 198 } catch (ClassCastException ex) { 199 Logging.getLogger().warning("Exception while evaluating select-binding", ex); 200 } 201 return DEFAULT_VALUE; 202 } 203 204 @Override 205 public ObservableList<ObservableValue<?>> getDependencies() { 206 return helper.getDependencies(); 207 } 208 209 } 210 211 public static class AsFloat extends FloatBinding { 212 213 private static final float DEFAULT_VALUE = 0.0f; 214 215 private final SelectBindingHelper helper; 216 217 public AsFloat(ObservableValue<?> root, String... steps) { 218 helper = new SelectBindingHelper(this, root, steps); 219 } 220 221 public AsFloat(Object root, String... steps) { 222 helper = new SelectBindingHelper(this, root, steps); 223 } 224 225 @Override 226 public void dispose() { 227 helper.unregisterListener(); 228 } 229 230 @Override 231 protected void onInvalidating() { 232 helper.unregisterListener(); 233 } 234 235 @Override 236 protected float computeValue() { 237 final ObservableValue<?> observable = helper.getObservableValue(); 238 if (observable == null) { 239 return DEFAULT_VALUE; 240 } 241 if (observable instanceof ObservableNumberValue) { 242 return ((ObservableNumberValue)observable).floatValue(); 243 } 244 try { 245 return ((Number)observable.getValue()).floatValue(); 246 } catch (NullPointerException ex) { 247 Logging.getLogger().fine("Value of select binding is null, returning default value", ex); 248 } catch (ClassCastException ex) { 249 Logging.getLogger().warning("Exception while evaluating select-binding", ex); 250 } 251 return DEFAULT_VALUE; 252 } 253 254 @Override 255 public ObservableList<ObservableValue<?>> getDependencies() { 256 return helper.getDependencies(); 257 } 258 259 } 260 261 public static class AsInteger extends IntegerBinding { 262 263 private static final int DEFAULT_VALUE = 0; 264 265 private final SelectBindingHelper helper; 266 267 public AsInteger(ObservableValue<?> root, String... steps) { 268 helper = new SelectBindingHelper(this, root, steps); 269 } 270 271 public AsInteger(Object root, String... steps) { 272 helper = new SelectBindingHelper(this, root, steps); 273 } 274 275 @Override 276 public void dispose() { 277 helper.unregisterListener(); 278 } 279 280 @Override 281 protected void onInvalidating() { 282 helper.unregisterListener(); 283 } 284 285 @Override 286 protected int computeValue() { 287 final ObservableValue<?> observable = helper.getObservableValue(); 288 if (observable == null) { 289 return DEFAULT_VALUE; 290 } 291 if (observable instanceof ObservableNumberValue) { 292 return ((ObservableNumberValue)observable).intValue(); 293 } 294 try { 295 return ((Number)observable.getValue()).intValue(); 296 } catch (NullPointerException ex) { 297 Logging.getLogger().fine("Value of select binding is null, returning default value", ex); 298 } catch (ClassCastException ex) { 299 Logging.getLogger().warning("Exception while evaluating select-binding", ex); 300 } 301 return DEFAULT_VALUE; 302 } 303 304 @Override 305 public ObservableList<ObservableValue<?>> getDependencies() { 306 return helper.getDependencies(); 307 } 308 309 } 310 311 public static class AsLong extends LongBinding { 312 313 private static final long DEFAULT_VALUE = 0L; 314 315 private final SelectBindingHelper helper; 316 317 public AsLong(ObservableValue<?> root, String... steps) { 318 helper = new SelectBindingHelper(this, root, steps); 319 } 320 321 public AsLong(Object root, String... steps) { 322 helper = new SelectBindingHelper(this, root, steps); 323 } 324 325 @Override 326 public void dispose() { 327 helper.unregisterListener(); 328 } 329 330 @Override 331 protected void onInvalidating() { 332 helper.unregisterListener(); 333 } 334 335 @Override 336 protected long computeValue() { 337 final ObservableValue<?> observable = helper.getObservableValue(); 338 if (observable == null) { 339 return DEFAULT_VALUE; 340 } 341 if (observable instanceof ObservableNumberValue) { 342 return ((ObservableNumberValue)observable).longValue(); 343 } 344 try { 345 return ((Number)observable.getValue()).longValue(); 346 } catch (NullPointerException ex) { 347 Logging.getLogger().fine("Value of select binding is null, returning default value", ex); 348 } catch (ClassCastException ex) { 349 Logging.getLogger().warning("Exception while evaluating select-binding", ex); 350 } 351 return DEFAULT_VALUE; 352 } 353 354 @Override 355 public ObservableList<ObservableValue<?>> getDependencies() { 356 return helper.getDependencies(); 357 } 358 359 } 360 361 public static class AsString extends StringBinding { 362 363 private static final String DEFAULT_VALUE = null; 364 365 private final SelectBindingHelper helper; 366 367 public AsString(ObservableValue<?> root, String... steps) { 368 helper = new SelectBindingHelper(this, root, steps); 369 } 370 371 public AsString(Object root, String... steps) { 372 helper = new SelectBindingHelper(this, root, steps); 373 } 374 375 @Override 376 public void dispose() { 377 helper.unregisterListener(); 378 } 379 380 @Override 381 protected void onInvalidating() { 382 helper.unregisterListener(); 383 } 384 385 @Override 386 protected String computeValue() { 387 final ObservableValue<?> observable = helper.getObservableValue(); 388 if (observable == null) { 389 return DEFAULT_VALUE; 390 } 391 try { 392 return observable.getValue().toString(); 393 } catch (RuntimeException ex) { 394 Logging.getLogger().warning("Exception while evaluating select-binding", ex); 395 // return default 396 return DEFAULT_VALUE; 397 } 398 } 399 400 @Override 401 public ObservableList<ObservableValue<?>> getDependencies() { 402 return helper.getDependencies(); 403 } 404 405 } 406 407 private static class SelectBindingHelper implements InvalidationListener { 408 409 private final Binding<?> binding; 410 private final String[] propertyNames; 411 private final ObservableValue<?>[] properties; 412 private final PropertyReference<?>[] propRefs; 413 private final WeakInvalidationListener observer; 414 415 private ObservableList<ObservableValue<?>> dependencies; 416 417 private SelectBindingHelper(Binding<?> binding, ObservableValue<?> firstProperty, String... steps) { 418 if (firstProperty == null) { 419 throw new NullPointerException("Must specify the root"); 420 } 421 if (steps == null) { 422 steps = new String[0]; 423 } 424 425 this.binding = binding; 426 427 final int n = steps.length; 428 for (int i = 0; i < n; i++) { 429 if (steps[i] == null) { 430 throw new NullPointerException("all steps must be specified"); 431 } 432 } 433 434 observer = new WeakInvalidationListener(this); 435 propertyNames = new String[n]; 436 System.arraycopy(steps, 0, propertyNames, 0, n); 437 propRefs = new PropertyReference<?>[n]; 438 properties = new ObservableValue<?>[n + 1]; 439 properties[0] = firstProperty; 440 properties[0].addListener(observer); 441 } 442 443 private static ObservableValue<?> checkAndCreateFirstStep(Object root, String[] steps) { 444 if (root == null || steps == null || steps[0] == null) { 445 throw new NullPointerException("Must specify the root and the first property"); 446 } 447 try { 448 return JavaBeanAccessHelper.createReadOnlyJavaBeanProperty(root, steps[0]); 449 } catch (NoSuchMethodException ex) { 450 throw new IllegalArgumentException("The first property '" + steps[0] + "' doesn't exist"); 451 } 452 } 453 454 private SelectBindingHelper(Binding<?> binding, Object root, String... steps) { 455 this(binding, checkAndCreateFirstStep(root, steps), Arrays.copyOfRange(steps, 1, steps.length)); 456 } 457 458 @Override 459 public void invalidated(Observable observable) { 460 binding.invalidate(); 461 } 462 463 public ObservableValue<?> getObservableValue() { 464 // Step through each of the steps, and at each step add a listener as 465 // appropriate, accumulating the result. 466 final int n = properties.length; 467 for (int i = 0; i < n - 1; i++) { 468 final Object obj = properties[i].getValue(); 469 try { 470 if ((propRefs[i] == null) 471 || (!obj.getClass().equals( 472 propRefs[i].getContainingClass()))) { 473 propRefs[i] = new PropertyReference<Object>(obj.getClass(), 474 propertyNames[i]); 475 } 476 if (propRefs[i].hasProperty()) { 477 properties[i + 1] = propRefs[i].getProperty(obj); 478 } else { 479 properties[i + 1] = JavaBeanAccessHelper.createReadOnlyJavaBeanProperty(obj, propRefs[i].getName()); 480 } 481 } catch (NoSuchMethodException ex) { 482 Logging.getLogger().warning("Exception while evaluating select-binding " + stepsToString(), ex); 483 // return default 484 updateDependencies(); 485 return null; 486 } catch (RuntimeException ex) { 487 final PlatformLogger logger = Logging.getLogger(); 488 if (logger.isLoggable(Level.WARNING)) { 489 String msg = "Exception while evaluating select-binding " + stepsToString(); 490 if (ex instanceof IllegalStateException) { 491 logger.warning(msg); 492 logger.warning("Property '" + propertyNames[i] + "' does not exist in " + obj.getClass(), ex); 493 } else if (ex instanceof NullPointerException) { 494 logger.fine(msg); 495 logger.fine("Property '" + propertyNames[i] + "' in " + properties[i] + " is null", ex); 496 } else { 497 logger.warning(msg, ex); 498 } 499 } 500 // return default 501 updateDependencies(); 502 return null; 503 } 504 properties[i + 1].addListener(observer); 505 } 506 updateDependencies(); 507 final ObservableValue<?> result = properties[n-1]; 508 if (result == null) { 509 Logging.getLogger().fine("Property '" + propertyNames[n-1] + "' in " + properties[n-1] + " is null", new NullPointerException()); 510 } 511 return result; 512 } 513 514 private String stepsToString() { 515 return Arrays.toString(propertyNames); 516 } 517 518 private void unregisterListener() { 519 final int n = properties.length; 520 for (int i = 1; i < n; i++) { 521 if (properties[i] == null) { 522 break; 523 } 524 properties[i].removeListener(observer); 525 properties[i] = null; 526 } 527 updateDependencies(); 528 } 529 530 private void updateDependencies() { 531 if (dependencies != null) { 532 dependencies.clear(); 533 final int n = properties.length; 534 for (int i = 0; i < n; i++) { 535 if (properties[i] == null) { 536 break; 537 } 538 dependencies.add(properties[i]); 539 } 540 } 541 } 542 543 public ObservableList<ObservableValue<?>> getDependencies() { 544 if (dependencies == null) { 545 dependencies = FXCollections.observableArrayList(); 546 updateDependencies(); 547 } 548 549 return FXCollections.unmodifiableObservableList(dependencies); 550 } 551 552 } 553 554 }