1 /* 2 * Copyright (c) 2010, 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 package test.com.sun.javafx.binding; 26 27 import com.sun.javafx.binding.Logging; 28 import java.beans.PropertyChangeListener; 29 import java.beans.PropertyChangeSupport; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.Random; 34 import test.javafx.beans.Person; 35 import javafx.beans.binding.Bindings; 36 import javafx.beans.binding.BooleanBinding; 37 import javafx.beans.binding.DoubleBinding; 38 import javafx.beans.binding.FloatBinding; 39 import javafx.beans.binding.IntegerBinding; 40 import javafx.beans.binding.LongBinding; 41 import javafx.beans.binding.ObjectBinding; 42 import javafx.beans.binding.StringBinding; 43 import test.javafx.binding.Variable; 44 import javafx.collections.ObservableList; 45 import sun.util.logging.PlatformLogger.Level; 46 import org.junit.AfterClass; 47 import org.junit.Before; 48 import org.junit.BeforeClass; 49 import org.junit.Test; 50 51 import static org.junit.Assert.*; 52 53 public class SelectBindingTest { 54 55 private static final double EPSILON_DOUBLE = 1e-12; 56 private static final float EPSILON_FLOAT = 1e-6f; 57 58 public static class POJOPerson { 59 60 private String name; 61 62 public String getName() { 63 return name; 64 } 65 66 public POJOPerson() { 67 } 68 69 public POJOPerson(String name) { 70 this.name = name; 71 } 72 73 } 74 75 public static class POJONext { 76 77 private Object next; 78 private PropertyChangeSupport pcs = new PropertyChangeSupport(this); 79 80 public Object getNext() { 81 return next; 82 } 83 84 public void setNext(Object next) { 85 Object old = this.next; 86 this.next = next; 87 pcs.firePropertyChange("next", old, next); 88 } 89 90 public void addPropertyChangeListener(String property, PropertyChangeListener pcl) { 91 pcs.addPropertyChangeListener(property, pcl); 92 } 93 94 public void removePropertyChangeListener(String property, PropertyChangeListener pcl) { 95 pcs.removePropertyChangeListener(property, pcl); 96 } 97 98 } 99 100 private Variable a; 101 private Variable b; 102 private Variable c; 103 private Variable d; 104 private StringBinding select; 105 private ObservableList<?> dependencies; 106 107 private static final ErrorLoggingUtiltity log = new ErrorLoggingUtiltity(); 108 109 @BeforeClass 110 public static void setUpClass() { 111 log.start(); 112 } 113 114 @AfterClass 115 public static void tearDownClass() { 116 log.stop(); 117 } 118 119 @Before 120 public void setUp() throws Exception { 121 a = new Variable("a"); 122 b = new Variable("b"); 123 c = new Variable("c"); 124 d = new Variable("d"); 125 a.setNext(b); 126 b.setNext(c); 127 select = Bindings.selectString(a.nextProperty(), "next", "name"); 128 dependencies = select.getDependencies(); 129 } 130 131 @Test 132 public void testObject() { 133 final Person person1 = new Person(); 134 final Person person2 = new Person(); 135 c.setNext(person1); 136 final ObjectBinding<Object> objectBinding = Bindings.select(a.nextProperty(), "next", "next"); 137 assertEquals(person1, objectBinding.get()); 138 c.setNext(person2); 139 assertEquals(person2, objectBinding.get()); 140 b.setNext(null); 141 assertEquals(null, objectBinding.get()); 142 log.checkFine(NullPointerException.class); 143 } 144 145 @Test 146 public void testPOJOObject() { 147 POJONext pojoA = new POJONext(); 148 pojoA.setNext(b); 149 final Person person1 = new Person(); 150 final Person person2 = new Person(); 151 c.setNext(person1); 152 final ObjectBinding<Object> objectBinding = Bindings.select(pojoA, "next", "next", "next"); 153 assertEquals(person1, objectBinding.get()); 154 c.setNext(person2); 155 assertEquals(person2, objectBinding.get()); 156 } 157 158 @Test 159 public void testPOJOObject_2() { 160 POJONext pojoC = new POJONext(); 161 b.setNext(pojoC); 162 final Person person1 = new Person(); 163 final Person person2 = new Person(); 164 pojoC.setNext(person1); 165 final ObjectBinding<Object> objectBinding = Bindings.select(a.nextProperty(), "next", "next"); 166 assertEquals(person1, objectBinding.get()); 167 pojoC.setNext(person2); 168 assertEquals(person2, objectBinding.get()); 169 } 170 171 @Test 172 public void testPOJOObject_3() { 173 final POJOPerson person1 = new POJOPerson("P1"); 174 final POJOPerson person2 = new POJOPerson("P2"); 175 c.setNext(person1); 176 final ObjectBinding<Object> objectBinding = Bindings.select(a.nextProperty(), "next", "next", "name"); 177 assertEquals("P1", objectBinding.get()); 178 c.setNext(person2); 179 assertEquals("P2", objectBinding.get()); 180 b.setNext(null); 181 assertEquals(null, objectBinding.get()); 182 log.checkFine(NullPointerException.class); 183 } 184 185 @Test 186 public void testPOJOBoolean() { 187 188 POJONext pojoA = new POJONext(); 189 pojoA.setNext(b); 190 191 final Person person = new Person(); 192 b.setNext(person); 193 final BooleanBinding binding1 = Bindings.selectBoolean(pojoA, "next", "next", "retired"); 194 assertEquals(false, binding1.get()); 195 person.setRetired(true); 196 assertEquals(true, binding1.get()); 197 } 198 199 @Test 200 public void testBoolean() { 201 202 final Person person = new Person(); 203 b.setNext(person); 204 final BooleanBinding binding1 = Bindings.selectBoolean(a.nextProperty(), "next", "retired"); 205 assertEquals(false, binding1.get()); 206 person.setRetired(true); 207 assertEquals(true, binding1.get()); 208 b.setNext(null); 209 assertEquals(false, binding1.get()); 210 log.checkFine(NullPointerException.class); 211 212 person.setData(false); 213 b.setNext(person); 214 final BooleanBinding binding2 = Bindings.selectBoolean(a.nextProperty(), "next", "data"); 215 assertEquals(false, binding2.get()); 216 person.setData(true); 217 assertEquals(true, binding2.get()); 218 person.setData(null); 219 assertEquals(false, binding2.get()); 220 log.checkFine(NullPointerException.class); 221 b.setNext(null); 222 assertEquals(false, binding2.get()); 223 log.checkFine(NullPointerException.class); 224 } 225 226 @Test 227 public void testPOJODouble() { 228 POJONext pojoA = new POJONext(); 229 pojoA.setNext(b); 230 231 final Person person = new Person(); 232 person.setSomething(-Math.E); 233 b.setNext(person); 234 final DoubleBinding binding1 = Bindings.selectDouble(pojoA, "next", "next", "something"); 235 assertEquals(-Math.E, binding1.get(), EPSILON_DOUBLE); 236 person.setSomething(Math.PI); 237 assertEquals(Math.PI, binding1.get(), EPSILON_DOUBLE); 238 } 239 240 @Test 241 public void testDouble() { 242 243 final Person person = new Person(); 244 person.setSomething(-Math.E); 245 b.setNext(person); 246 final DoubleBinding binding1 = Bindings.selectDouble(a.nextProperty(), "next", "something"); 247 assertEquals(-Math.E, binding1.get(), EPSILON_DOUBLE); 248 person.setSomething(Math.PI); 249 assertEquals(Math.PI, binding1.get(), EPSILON_DOUBLE); 250 b.setNext(null); 251 assertEquals(0.0, binding1.get(), EPSILON_DOUBLE); 252 log.checkFine(NullPointerException.class); 253 254 person.setData(-Math.E); 255 b.setNext(person); 256 final DoubleBinding binding2 = Bindings.selectDouble(a.nextProperty(), "next", "data"); 257 assertEquals(-Math.E, binding2.get(), EPSILON_DOUBLE); 258 person.setData(Math.PI); 259 assertEquals(Math.PI, binding2.get(), EPSILON_DOUBLE); 260 person.setData(null); 261 assertEquals(0.0, binding2.get(), EPSILON_DOUBLE); 262 log.checkFine(NullPointerException.class); 263 b.setNext(null); 264 assertEquals(0.0, binding2.get(), EPSILON_DOUBLE); 265 log.checkFine(NullPointerException.class); 266 } 267 268 @Test 269 public void testPOJOFloat() { 270 POJONext pojoA = new POJONext(); 271 pojoA.setNext(b); 272 273 final Person person = new Person(); 274 person.setMiles((float) -Math.E); 275 b.setNext(person); 276 final FloatBinding binding1 = Bindings.selectFloat(pojoA, "next", "next", "miles"); 277 assertEquals((float) -Math.E, binding1.get(), EPSILON_FLOAT); 278 person.setMiles((float) Math.PI); 279 assertEquals((float) Math.PI, binding1.get(), EPSILON_FLOAT); 280 } 281 282 @Test 283 public void testFloat() { 284 285 final Person person = new Person(); 286 person.setMiles((float) -Math.E); 287 b.setNext(person); 288 final FloatBinding binding1 = Bindings.selectFloat(a.nextProperty(), "next", "miles"); 289 assertEquals((float) -Math.E, binding1.get(), EPSILON_FLOAT); 290 person.setMiles((float) Math.PI); 291 assertEquals((float) Math.PI, binding1.get(), EPSILON_FLOAT); 292 b.setNext(null); 293 assertEquals(0.0f, binding1.get(), EPSILON_FLOAT); 294 log.checkFine(NullPointerException.class); 295 296 person.setData((float) -Math.E); 297 b.setNext(person); 298 final FloatBinding binding2 = Bindings.selectFloat(a.nextProperty(), "next", "data"); 299 assertEquals((float) -Math.E, binding2.get(), EPSILON_FLOAT); 300 person.setData((float) Math.PI); 301 assertEquals((float) Math.PI, binding2.get(), EPSILON_FLOAT); 302 person.setData(null); 303 assertEquals(0.0f, binding2.get(), EPSILON_FLOAT); 304 log.checkFine(NullPointerException.class); 305 b.setNext(null); 306 assertEquals(0.0f, binding2.get(), EPSILON_FLOAT); 307 log.checkFine(NullPointerException.class); 308 } 309 310 @Test 311 public void testPOJOInteger() { 312 313 POJONext pojoA = new POJONext(); 314 pojoA.setNext(b); 315 316 final Person person = new Person(); 317 person.setAge(42); 318 b.setNext(person); 319 final IntegerBinding binding1 = Bindings.selectInteger(pojoA, "next", "next", "age"); 320 assertEquals(42, binding1.get()); 321 person.setAge(-18); 322 assertEquals(-18, binding1.get()); 323 } 324 325 @Test 326 public void testInteger() { 327 328 final Person person = new Person(); 329 person.setAge(42); 330 b.setNext(person); 331 final IntegerBinding binding1 = Bindings.selectInteger(a.nextProperty(), "next", "age"); 332 assertEquals(42, binding1.get()); 333 person.setAge(-18); 334 assertEquals(-18, binding1.get()); 335 b.setNext(null); 336 assertEquals(0, binding1.get()); 337 log.checkFine(NullPointerException.class); 338 339 person.setData(42); 340 b.setNext(person); 341 final IntegerBinding binding2 = Bindings.selectInteger(a.nextProperty(), "next", "data"); 342 assertEquals(42, binding2.get()); 343 person.setData(-18); 344 assertEquals(-18, binding2.get()); 345 person.setData(null); 346 assertEquals(0, binding2.get()); 347 log.checkFine(NullPointerException.class); 348 b.setNext(null); 349 assertEquals(0, binding2.get()); 350 log.checkFine(NullPointerException.class); 351 } 352 353 @Test 354 public void testPOJOLong() { 355 POJONext pojoA = new POJONext(); 356 pojoA.setNext(b); 357 final Person person = new Person(); 358 person.setIncome(1234567890987654321L); 359 b.setNext(person); 360 final LongBinding binding1 = Bindings.selectLong(pojoA, "next", "next", "income"); 361 assertEquals(1234567890987654321L, binding1.get()); 362 person.setIncome(-987654321234567890L); 363 assertEquals(-987654321234567890L, binding1.get()); 364 } 365 366 @Test 367 public void testLong() { 368 final Person person = new Person(); 369 person.setIncome(1234567890987654321L); 370 b.setNext(person); 371 final LongBinding binding1 = Bindings.selectLong(a.nextProperty(), "next", "income"); 372 assertEquals(1234567890987654321L, binding1.get()); 373 person.setIncome(-987654321234567890L); 374 assertEquals(-987654321234567890L, binding1.get()); 375 b.setNext(null); 376 assertEquals(0L, binding1.get()); 377 log.checkFine(NullPointerException.class); 378 379 person.setData(1234567890987654321L); 380 b.setNext(person); 381 final LongBinding binding2 = Bindings.selectLong(a.nextProperty(), "next", "data"); 382 assertEquals(1234567890987654321L, binding2.get()); 383 person.setData(-987654321234567890L); 384 assertEquals(-987654321234567890L, binding2.get()); 385 person.setData(null); 386 assertEquals(0L, binding2.get()); 387 log.checkFine(NullPointerException.class); 388 b.setNext(null); 389 assertEquals(0L, binding2.get()); 390 log.checkFine(NullPointerException.class); 391 } 392 393 @Test(expected = NullPointerException.class) 394 public void createWithRootNull() { 395 select = Bindings.selectString(null, "next", "name"); 396 } 397 398 @Test 399 public void createWithNoSteps() { 400 select = Bindings.selectString(a.nameProperty()); 401 assertEquals("a", select.get()); 402 a.setName("b"); 403 assertEquals("b", select.get()); 404 a.setName(null); 405 assertNull(select.get()); 406 log.check(java.util.logging.Level.WARNING, NullPointerException.class); 407 } 408 409 @Test(expected = NullPointerException.class) 410 public void createWithOneStepIsNull() { 411 select = Bindings.selectString(a.nextProperty(), null, "name"); 412 } 413 414 @Test 415 public void testNullIsReturnedFromAChainWithAPropertyThatIsNotOnTheAvailableObject() { 416 select = Bindings.selectString(a.nextProperty(), "dummy", "name"); 417 assertNull(select.get()); 418 log.check(java.util.logging.Level.WARNING, NoSuchMethodException.class); 419 } 420 421 @SuppressWarnings("unchecked") 422 @Test 423 public void testAllMembersHaveListeners() { 424 // As yet, there should still be no listeners registered 425 assertEquals(1, a.numChangedListenersForNext); 426 assertEquals(0, a.numChangedListenersForName); 427 assertEquals(0, b.numChangedListenersForNext); 428 assertEquals(0, b.numChangedListenersForName); 429 assertEquals(0, c.numChangedListenersForNext); 430 assertEquals(0, c.numChangedListenersForName); 431 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 432 // Read the value of the select. It should be == "c" 433 assertEquals("c", select.get()); 434 // Now there should be changed listeners for a.value and 435 // b.value and c.name 436 assertEquals(1, a.numChangedListenersForNext); 437 assertEquals(0, a.numChangedListenersForName); 438 assertEquals(1, b.numChangedListenersForNext); 439 assertEquals(0, b.numChangedListenersForName); 440 assertEquals(0, c.numChangedListenersForNext); 441 assertEquals(1, c.numChangedListenersForName); 442 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty(), c.nameProperty()), dependencies); 443 } 444 445 @SuppressWarnings("unchecked") 446 @Test 447 public void testWhenAValidChangeIsBrokenBySettingTheRootToNullThenTheValueIsNull() { 448 449 assertEquals("c", select.get()); 450 a.setNext(null); 451 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 452 assertNull(select.get()); 453 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 454 log.checkFine(NullPointerException.class); 455 } 456 457 @SuppressWarnings("unchecked") 458 @Test 459 public void testWhenAnIncompleteChainIsMadeCompleteThatTheValueIsComputedCorrectly() { 460 461 a.setNext(null); 462 select.get(); // force it to be validated, for fun 463 log.reset(); 464 a.setNext(b); 465 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 466 assertEquals("c", select.get()); 467 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty(), c.nameProperty()), dependencies); 468 assertTrue(log.isEmpty()); 469 } 470 471 @SuppressWarnings("unchecked") 472 @Test 473 public void testWhenAValidChangeIsBrokenBySettingTheMiddleToNullThenTheValueIsNull() { 474 475 assertEquals("c", select.get()); 476 b.setNext(null); 477 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 478 assertNull(select.get()); 479 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty()), dependencies); 480 log.checkFine(NullPointerException.class); 481 } 482 483 @SuppressWarnings("unchecked") 484 @Test 485 public void testWhenAnIncompleteChainIsMadeCompleteInTheMiddleThatTheValueIsComputedCorrectly() { 486 487 b.setNext(null); 488 select.get(); 489 log.reset(); 490 b.setNext(c); 491 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 492 assertEquals("c", select.get()); 493 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty(), c.nameProperty()), dependencies); 494 assertTrue(log.isEmpty()); 495 } 496 497 @SuppressWarnings("unchecked") 498 @Test 499 public void testWhenAValidChangeIsBrokenBySettingTheLastLinkToNullThenTheValueIsNull() { 500 log.reset(); 501 assertEquals("c", select.get()); 502 c.setName("d"); 503 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 504 assertEquals("d", select.get()); 505 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty(), c.nameProperty()), dependencies); 506 assertTrue(log.isEmpty()); 507 } 508 509 @SuppressWarnings("unchecked") 510 @Test 511 public void testWhenAnIncompleteChainIsMadeCompleteAtTheEndThatTheValueIsComputedCorrectly() { 512 log.reset(); 513 c.setName("d"); 514 select.get(); 515 c.setName("c"); 516 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 517 assertEquals("c", select.get()); 518 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty(), c.nameProperty()), dependencies); 519 assertTrue(log.isEmpty()); 520 } 521 522 @SuppressWarnings("unchecked") 523 @Test 524 public void testSettingTheRootValueToNullInAChainShouldUnhookAllListeners() { 525 select.get(); 526 a.setNext(null); 527 528 // All of the listeners should have been uninstalled 529 assertEquals(0, a.numChangedListenersForName); 530 assertEquals(1, a.numChangedListenersForNext); 531 532 assertEquals(0, b.numChangedListenersForName); 533 assertEquals(0, b.numChangedListenersForNext); 534 535 assertEquals(0, c.numChangedListenersForName); 536 assertEquals(0, c.numChangedListenersForNext); 537 538 assertEquals(0, d.numChangedListenersForName); 539 assertEquals(0, d.numChangedListenersForNext); 540 541 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 542 } 543 544 /** 545 * This test performs 10,000 random operations on the chain of a.b.c 546 * (setting different values for each step, sometimes doing multiple 547 * assignments between get() calls, random invalidate() calls). After each 548 * random iteration, we check to see if the listeners installed on the a, b, 549 * c objects are still correct. The goal is to catch any freak situations 550 * where we might have installed two listeners on the same property, or 551 * failed to remove a listener for a property under some circumstance. 552 * 553 * Do note that the only listeners that are installed in this method are 554 * done by the select binding. 555 */ 556 @SuppressWarnings("unchecked") 557 @Test 558 public void stressTestRandomOperationsResultInCorrectListenersInstalled() { 559 560 final Level logLevel = Logging.getLogger().level(); 561 Logging.getLogger().setLevel(Level.SEVERE); 562 List<String> steps = new ArrayList<String>(); 563 564 Random rand = new Random(System.currentTimeMillis()); 565 for (int i = 0; i < 10000; i++) { 566 switch (rand.nextInt(20)) { 567 case 0: 568 a.setNext(null); 569 steps.add("Assign a.value to null"); 570 break; 571 case 1: 572 a.setNext(b); 573 steps.add("Assign a.value to b"); 574 break; 575 case 2: 576 b.setNext(null); 577 steps.add("Assign b.value to null"); 578 break; 579 case 3: 580 b.setNext(c); 581 steps.add("Assign b.value to c"); 582 break; 583 case 4: 584 c.setNext(null); 585 steps.add("Assign c.value to null"); 586 break; 587 case 5: 588 c.setNext(d); 589 steps.add("Assign c.value to d"); 590 break; 591 case 6: 592 c.setName(null); 593 steps.add("Assign c.name to null"); 594 break; 595 case 7: 596 c.setName("c"); 597 steps.add("Assign c.name to 'c'"); 598 break; 599 default: 600 select.get(); 601 steps.add("Call select.get()"); 602 } 603 604 // Now validate that the listeners are as we expected 605 int expected = 1; 606 int depsCount = expected; 607 assertEquals(0, a.numChangedListenersForName); 608 if (expected != a.numChangedListenersForNext) { 609 printSteps(i, steps); 610 } 611 assertEquals(expected, a.numChangedListenersForNext); 612 613 expected = select.isValid() && a.getNext() == b ? 1 : 0; 614 depsCount += expected; 615 assertEquals(0, b.numChangedListenersForName); 616 if (expected != b.numChangedListenersForNext) { 617 printSteps(i, steps); 618 } 619 assertEquals(expected, b.numChangedListenersForNext); 620 621 expected = select.isValid() && a.getNext() == b && b.getNext() == c ? 1 : 0; 622 depsCount += expected; 623 assertEquals(0, c.numChangedListenersForNext); 624 if (expected != c.numChangedListenersForName) { 625 printSteps(i, steps); 626 } 627 assertEquals(expected, c.numChangedListenersForName); 628 629 assertEquals(0, d.numChangedListenersForName); 630 assertEquals(0, d.numChangedListenersForNext); 631 632 switch (depsCount) { 633 case 0: 634 case 1: 635 assertEquals(Arrays.asList(a.nextProperty()), dependencies); 636 break; 637 case 2: 638 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty()), dependencies); 639 break; 640 case 3: 641 assertEquals(Arrays.asList(a.nextProperty(), b.nextProperty(), c.nameProperty()), dependencies); 642 break; 643 default: 644 fail("Should not reach here"); 645 } 646 } 647 Logging.getLogger().setLevel(logLevel); 648 } 649 650 private void printSteps(int iteration, List<String> steps) { 651 System.err.println("Failed on iteration " + iteration + " for the following observableArrayList of changes"); 652 for (String s : steps) { 653 System.err.println("\t" + s); 654 } 655 } 656 }