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 26 package test.javafx.collections; 27 28 import com.sun.javafx.collections.NonIterableChange.SimplePermutationChange; 29 import com.sun.javafx.collections.ObservableListWrapper; 30 import java.util.ArrayList; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Collections; 34 import java.util.Arrays; 35 import java.util.Comparator; 36 import java.util.Map; 37 38 import javafx.beans.Observable; 39 import javafx.beans.property.SimpleObjectProperty; 40 import javafx.collections.FXCollections; 41 import javafx.collections.ListChangeListener; 42 import javafx.collections.ObservableList; 43 import javafx.collections.ObservableListWrapperShim; 44 import javafx.collections.transformation.FilteredList; 45 import javafx.collections.transformation.SortedList; 46 import org.junit.Before; 47 import org.junit.Test; 48 import static org.junit.Assert.* ; 49 import static org.junit.Assert.assertEquals; 50 51 public class SortedListTest { 52 53 private ObservableList<String> list; 54 private MockListObserver<String> mockListObserver; 55 private SortedList<String> sortedList; 56 57 @Before 58 public void setUp() { 59 list = FXCollections.observableArrayList(); 60 list.addAll("a", "c", "d", "c"); 61 sortedList = list.sorted(); 62 mockListObserver = new MockListObserver<String>(); 63 sortedList.addListener(mockListObserver); 64 } 65 66 @Test 67 public void testNoChange() { 68 assertEquals(Arrays.asList("a", "c", "c", "d"), sortedList); 69 mockListObserver.check0(); 70 71 compareIndices(); 72 } 73 74 @Test 75 public void testAdd() { 76 list.clear(); 77 mockListObserver.clear(); 78 assertEquals(Collections.emptyList(), sortedList); 79 list.addAll("a", "c", "d", "c"); 80 assertEquals(Arrays.asList("a", "c", "c", "d"), sortedList); 81 mockListObserver.check1AddRemove(sortedList, Collections.<String>emptyList(), 0, 4); 82 assertEquals(0, sortedList.getSourceIndex(0)); 83 assertEquals(2, sortedList.getSourceIndex(3)); 84 85 compareIndices(); 86 } 87 88 private <E> void compareIndices(SortedList<E> sorted) { 89 ObservableList<? extends E> source = sorted.getSource(); 90 for (int i = 0; i < sorted.size(); i++) { 91 // i as a view index 92 int sourceIndex = sorted.getSourceIndex(i); 93 assertEquals(i, sorted.getViewIndex(sourceIndex)); 94 assertSame(sorted.get(i), source.get(sourceIndex)); 95 96 // i as a source index 97 int viewIndex = sorted.getViewIndex(i); 98 assertEquals(i, sorted.getSourceIndex(viewIndex)); 99 assertSame(source.get(i), sorted.get(viewIndex)); 100 } 101 } 102 103 private void compareIndices() { 104 compareIndices(sortedList); 105 } 106 107 @Test 108 public void testAddSingle() { 109 list.add("b"); 110 assertEquals(Arrays.asList("a", "b", "c", "c", "d"), sortedList); 111 mockListObserver.check1AddRemove(sortedList, Collections.<String>emptyList(), 1, 2); 112 assertEquals(0, sortedList.getSourceIndex(0)); 113 assertEquals(4, sortedList.getSourceIndex(1)); 114 assertEquals(1, sortedList.getSourceIndex(2)); 115 assertEquals(3, sortedList.getSourceIndex(3)); 116 assertEquals(2, sortedList.getSourceIndex(4)); 117 118 compareIndices(); 119 } 120 121 @Test 122 public void testRemove() { 123 list.removeAll(Arrays.asList("c")); // removes "c", "d", "c", adds "d" 124 assertEquals(Arrays.asList("a", "d"), sortedList); 125 mockListObserver.check1AddRemove(sortedList, Arrays.asList("c", "c"), 1, 1); 126 assertEquals(0, sortedList.getSourceIndex(0)); 127 assertEquals(1, sortedList.getSourceIndex(1)); 128 mockListObserver.clear(); 129 list.removeAll(Arrays.asList("a", "d")); 130 mockListObserver.check1AddRemove(sortedList, Arrays.asList("a", "d"), 0, 0); 131 132 compareIndices(); 133 } 134 135 @Test 136 public void testRemoveSingle() { 137 list.remove("a"); 138 assertEquals(Arrays.asList("c", "c", "d"), sortedList); 139 mockListObserver.check1AddRemove(sortedList, Arrays.asList("a"), 0, 0); 140 assertEquals(0, sortedList.getSourceIndex(0)); 141 assertEquals(2, sortedList.getSourceIndex(1)); 142 assertEquals(1, sortedList.getSourceIndex(2)); 143 144 compareIndices(); 145 } 146 147 @Test 148 public void testMultipleOperations() { 149 list.remove(2); 150 assertEquals(Arrays.asList("a", "c", "c"), sortedList); 151 mockListObserver.check1AddRemove(sortedList, Arrays.asList("d"), 3, 3); 152 mockListObserver.clear(); 153 list.add("b"); 154 assertEquals(Arrays.asList("a", "b", "c", "c"), sortedList); 155 mockListObserver.check1AddRemove(sortedList, Collections.<String>emptyList(), 1, 2); 156 157 compareIndices(); 158 } 159 160 @Test 161 public void testPureRemove() { 162 list.removeAll(Arrays.asList("c", "d")); 163 mockListObserver.check1AddRemove(sortedList, Arrays.asList("c", "c", "d"), 1, 1); 164 assertEquals(0, sortedList.getSourceIndex(0)); 165 166 compareIndices(); 167 } 168 169 @Test 170 public void testChangeComparator() { 171 SimpleObjectProperty<Comparator<String>> op = 172 new SimpleObjectProperty<>(Comparator.naturalOrder()); 173 174 sortedList = new SortedList<>(list); 175 assertEquals(Arrays.asList("a", "c", "d", "c"), sortedList); 176 compareIndices(); 177 178 sortedList.comparatorProperty().bind(op); 179 assertEquals(Arrays.asList("a", "c", "c", "d"), sortedList); 180 compareIndices(); 181 182 sortedList.addListener(mockListObserver); 183 184 op.set((Comparator<String>) (String o1, String o2) -> -o1.compareTo(o2)); 185 assertEquals(Arrays.asList("d", "c", "c", "a"), sortedList); 186 mockListObserver.check1Permutation(sortedList, new int[] {3, 1, 2, 0}); // could be also 3, 2, 1, 0, but the algorithm goes this way 187 compareIndices(); 188 189 mockListObserver.clear(); 190 op.set(null); 191 assertEquals(Arrays.asList("a", "c", "d", "c"), sortedList); 192 mockListObserver.check1Permutation(sortedList, new int[] {2, 1, 3, 0}); 193 compareIndices(); 194 } 195 196 197 /** 198 * A slightly updated test provided by "Kleopatra" (http://javafx-jira.kenai.com/browse/RT-14400) 199 */ 200 @Test 201 public void testSourceIndex() { 202 final ObservableList<Double> sourceList = FXCollections.observableArrayList( 203 1300., 400., 600. 204 ); 205 // the list to be removed again, note that its highest value is greater 206 // then the highest in the base list before adding 207 List<Double> other = Arrays.asList( 208 50., -300., 4000. 209 ); 210 sourceList.addAll(other); 211 // wrap into a sorted list and add a listener to the sorted 212 final SortedList<Double> sorted = sourceList.sorted(); 213 ListChangeListener<Double> listener = c -> { 214 assertEquals(Arrays.<Double>asList(400.0, 600.0, 1300.0), c.getList()); 215 216 c.next(); 217 assertEquals(Arrays.<Double>asList(-300.0, 50.0), c.getRemoved()); 218 assertEquals(0, c.getFrom()); 219 assertEquals(0, c.getTo()); 220 assertTrue(c.next()); 221 assertEquals(Arrays.<Double>asList(4000.), c.getRemoved()); 222 assertEquals(3, c.getFrom()); 223 assertEquals(3, c.getTo()); 224 assertFalse(c.next()); 225 226 227 // grab sourceIndex of last (aka: highest) value in sorted list 228 int sourceIndex = sorted.getSourceIndex(sorted.size() - 1); 229 assertEquals(0, sourceIndex); 230 }; 231 sorted.addListener(listener); 232 sourceList.removeAll(other); 233 234 compareIndices(sorted); 235 } 236 237 @Test 238 public void testMutableElement() { 239 ObservableList<Person> list = createPersonsList(); 240 241 SortedList<Person> sorted = list.sorted(); 242 assertEquals(Arrays.asList( 243 new Person("five"), new Person("four"), new Person("one"), 244 new Person("three"), new Person("two")), 245 sorted); 246 MockListObserver<Person> listener = new MockListObserver<>(); 247 sorted.addListener(listener); 248 list.get(3).name.set("zero"); // four -> zero 249 ObservableList<Person> expected = FXCollections.observableArrayList( 250 new Person("five"), new Person("one"), new Person("three"), 251 new Person("two"), new Person("zero")); 252 listener.checkPermutation(0, expected, 0, list.size(), new int[]{0, 4, 1, 2, 3}); 253 listener.checkUpdate(1, expected, 4, 5); 254 assertEquals(expected, sorted); 255 256 compareIndices(sorted); 257 } 258 259 @Test 260 public void testMutableElementUnsorted_rt39541() { 261 ObservableList<Person> list = createPersonsList(); 262 SortedList<Person> unsorted = new SortedList<>(list); 263 MockListObserver<Person> listener = new MockListObserver<>(); 264 unsorted.addListener(listener); 265 list.get(3).name.set("zero"); // four -> zero 266 ObservableList<Person> expected = FXCollections.observableArrayList( 267 new Person("one"), new Person("two"), new Person("three"), 268 new Person("zero"), new Person("five")); 269 listener.check1Update(expected, 3, 4); 270 271 compareIndices(unsorted); 272 } 273 274 @Test 275 public void testMutableElementUnsortedChain_rt39541() { 276 ObservableList<Person> items = createPersonsList(); 277 278 SortedList<Person> sorted = items.sorted(); 279 SortedList<Person> unsorted = new SortedList<>(sorted); 280 281 assertEquals(sorted, unsorted); 282 283 MockListObserver<Person> listener = new MockListObserver<>(); 284 unsorted.addListener(listener); 285 items.get(3).name.set("zero"); // "four" -> "zero" 286 ObservableList<Person> expected = FXCollections.observableArrayList( 287 new Person("five"), new Person("one"), new Person("three"), 288 new Person("two"), new Person("zero")); 289 listener.checkPermutation(0, expected, 0, expected.size(), new int[] {0, 4, 1, 2, 3}); 290 listener.checkUpdate(1, expected, 4, 5); 291 assertEquals(expected, sorted); 292 assertEquals(expected, unsorted); 293 294 compareIndices(sorted); 295 compareIndices(unsorted); 296 } 297 298 @Test 299 public void testMutableElementSortedFilteredChain() { 300 ObservableList<Person> items = FXCollections.observableArrayList( 301 (Person p) -> new Observable[]{p.name}); 302 items.addAll( 303 new Person("b"), new Person("c"), new Person("a"), 304 new Person("f"), new Person("e"), new Person("d")); 305 306 FilteredList<Person> filtered = items.filtered(e -> !e.name.get().startsWith("z")); 307 MockListObserver<Person> filterListener = new MockListObserver<>(); 308 filtered.addListener(filterListener); 309 310 SortedList<Person> sorted = filtered.sorted((x, y) -> x.name.get().compareTo(y.name.get())); 311 MockListObserver<Person> sortListener = new MockListObserver<>(); 312 sorted.addListener(sortListener); 313 items.get(2).name.set("z"); // "a" -> "z" 314 filterListener.check1AddRemove(filtered, Arrays.asList(new Person("z")), 2, 2); 315 sortListener.check1AddRemove(sorted, Arrays.asList(new Person("z")), 0, 0); 316 ObservableList<Person> expected = FXCollections.observableArrayList( 317 new Person("b"), new Person("c"), new Person("d"), 318 new Person("e"), new Person("f")); 319 assertEquals(expected, sorted); 320 321 compareIndices(sorted); 322 } 323 324 private ObservableList<Person> createPersonsList() { 325 ObservableList<Person> list = FXCollections.observableArrayList( 326 (Person p) -> new Observable[]{p.name}); 327 list.addAll( 328 new Person("one"), new Person("two"), new Person("three"), 329 new Person("four"), new Person("five")); 330 return list; 331 } 332 333 @Test 334 public void testNotComparable() { 335 final Object o1 = new Object() { 336 337 @Override 338 public String toString() { 339 return "c"; 340 } 341 }; 342 final Object o2 = new Object() { 343 344 @Override 345 public String toString() { 346 return "a"; 347 } 348 }; 349 final Object o3 = new Object() { 350 351 @Override 352 public String toString() { 353 return "d"; 354 } 355 }; 356 ObservableList<Object> list = FXCollections.observableArrayList(o1, o2, o3); 357 358 SortedList<Object> sorted = list.sorted(); 359 assertEquals(Arrays.asList(o2, o1, o3), sorted); 360 361 compareIndices(sorted); 362 } 363 364 @Test 365 public void testCompareNulls() { 366 ObservableList<String> list = FXCollections.observableArrayList( "g", "a", null, "z"); 367 368 SortedList<String> sorted = list.sorted(); 369 assertEquals(Arrays.asList(null, "a", "g", "z"), sorted); 370 371 compareIndices(sorted); 372 } 373 374 375 private static class Permutator<E> extends ObservableListWrapper<E> { 376 private List<E> backingList; 377 public Permutator(List<E> list) { 378 super(list); 379 this.backingList = list; 380 } 381 382 public void swap() { 383 E first = get(0); 384 backingList.set(0, get(size() - 1)); 385 backingList.set(size() -1, first); 386 ObservableListWrapperShim.fireChange(this, 387 new SimplePermutationChange(0, size(), new int[] {2, 1, 0}, this)); 388 } 389 390 } 391 /** 392 * SortedList cant cope with permutations. 393 */ 394 @Test 395 public void testPermutate() { 396 List<Integer> list = new ArrayList<Integer>(); 397 for (int i = 0; i < 3; i++) { 398 list.add(i); 399 } 400 Permutator<Integer> permutator = new Permutator<Integer>(list); 401 SortedList<Integer> sorted = new SortedList<Integer>(permutator); 402 permutator.swap(); 403 404 compareIndices(sorted); 405 } 406 407 @Test 408 public void testUnsorted() { 409 SortedList<String> sorted = new SortedList<>(list); 410 assertEquals(sorted, list); 411 assertEquals(list, sorted); 412 413 list.removeAll("a", "d"); 414 415 assertEquals(sorted, list); 416 417 list.addAll(0, Arrays.asList("a", "b", "c")); 418 419 assertEquals(sorted, list); 420 421 FXCollections.sort(list); 422 423 assertEquals(sorted, list); 424 425 compareIndices(sorted); 426 } 427 428 @Test 429 public void testUnsorted2() { 430 list.setAll("a", "b", "c", "d", "e", "f"); 431 SortedList<String> sorted = new SortedList<>(list); 432 assertEquals(sorted, list); 433 434 list.removeAll("b", "c", "d"); 435 436 assertEquals(sorted, list); 437 438 compareIndices(sorted); 439 } 440 441 @Test 442 public void testSortedNaturalOrder() { 443 assertEquals(Arrays.asList("a", "c", "c", "d"), list.sorted()); 444 } 445 446 @Test 447 public void testRemoveFromDuplicates() { 448 String toRemove = new String("A"); 449 String other = new String("A"); 450 list = FXCollections.observableArrayList(other, toRemove); 451 Comparator<String> c = Comparator.naturalOrder(); 452 SortedList<String> sorted = list.sorted(c); 453 454 list.remove(1); 455 456 assertEquals(1, sorted.size()); 457 assertTrue(sorted.get(0) == other); 458 459 compareIndices(sorted); 460 } 461 462 @Test 463 public void testAddAllOnEmpty() { 464 list = FXCollections.observableArrayList(); 465 SortedList<String> sl = list.sorted(String.CASE_INSENSITIVE_ORDER); 466 list.addAll("B", "A"); 467 468 assertEquals(Arrays.asList("A", "B"), sl); 469 470 compareIndices(sl); 471 } 472 473 @Test 474 public void test_rt36353_sortedList() { 475 ObservableList<String> data = FXCollections.observableArrayList("2", "1", "3"); 476 SortedList<String> sortedList = new SortedList<String>(data); 477 478 HashMap<Integer, Integer> pMap = new HashMap<>(); 479 sortedList.addListener((ListChangeListener<String>) c -> { 480 while (c.next()) { 481 if (c.wasPermutated()) { 482 for (int i = c.getFrom(); i < c.getTo(); i++) { 483 pMap.put(i, c.getPermutation(i)); 484 } 485 } 486 } 487 }); 488 489 Map<Integer, Integer> expected = new HashMap<>(); 490 491 // comparator that will create list of [1,2,3]. Sort indices based on 492 // previous order [2,1,3]. 493 sortedList.setComparator((s1,s2) -> s1.compareTo(s2)); 494 assertEquals(FXCollections.observableArrayList("1","2","3"), sortedList); 495 expected.put(0, 1); // item "2" has moved from index 0 to index 1 496 expected.put(1, 0); // item "1" has moved from index 1 to index 0 497 expected.put(2, 2); // item "3" has remained in index 2 498 assertEquals(expected, pMap); 499 compareIndices(sortedList); 500 501 // comparator that will create list of [3,2,1]. Sort indices based on 502 // previous order [1,2,3]. 503 sortedList.setComparator((s1,s2) -> s2.compareTo(s1)); 504 assertEquals(FXCollections.observableArrayList("3","2","1"), sortedList); 505 expected.clear(); 506 expected.put(0, 2); // item "1" has moved from index 0 to index 2 507 expected.put(1, 1); // item "2" has remained in index 1 508 expected.put(2, 0); // item "3" has moved from index 2 to index 0 509 assertEquals(expected, pMap); 510 compareIndices(sortedList); 511 512 // null comparator so sort order should return to [2,1,3]. Sort indices based on 513 // previous order [3,2,1]. 514 sortedList.setComparator(null); 515 assertEquals(FXCollections.observableArrayList("2","1","3"), sortedList); 516 expected.clear(); 517 expected.put(0, 2); // item "3" has moved from index 0 to index 2 518 expected.put(1, 0); // item "2" has moved from index 1 to index 0 519 expected.put(2, 1); // item "1" has moved from index 2 to index 1 520 assertEquals(expected, pMap); 521 compareIndices(sortedList); 522 } 523 524 @Test 525 public void testAddWhenUnsorted() { 526 sortedList.setComparator(null); 527 mockListObserver.clear(); 528 list.add(2, "b"); 529 assertEquals(5, sortedList.size()); 530 assertEquals(Arrays.asList("a", "c", "b", "d", "c"), sortedList); 531 mockListObserver.check1AddRemove(sortedList, Collections.emptyList(), 2, 3); 532 compareIndices(); 533 534 mockListObserver.clear(); 535 sortedList.setComparator(Comparator.<String>naturalOrder()); 536 mockListObserver.check1Permutation(sortedList, new int[] {0, 2, 1, 4, 3}); 537 assertEquals(5, sortedList.size()); 538 assertEquals(Arrays.asList("a", "b", "c", "c", "d"), sortedList); 539 compareIndices(); 540 541 mockListObserver.clear(); 542 sortedList.setComparator(null); 543 assertEquals(5, sortedList.size()); 544 assertEquals(Arrays.asList("a", "c", "b", "d", "c"), sortedList); 545 mockListObserver.check1Permutation(sortedList, new int[] {0, 2, 1, 4, 3}); 546 compareIndices(); 547 } 548 549 @Test 550 public void testRemoveWhenUnsorted() { 551 sortedList.setComparator(null); 552 mockListObserver.clear(); 553 list.remove(1); 554 assertEquals(3, sortedList.size()); 555 assertEquals(Arrays.asList("a", "d", "c"), sortedList); 556 mockListObserver.check1AddRemove(sortedList, Arrays.asList("c"), 1, 1); 557 compareIndices(); 558 559 mockListObserver.clear(); 560 sortedList.setComparator(Comparator.<String>naturalOrder()); 561 mockListObserver.check1Permutation(sortedList, new int[] {0, 2, 1}); 562 assertEquals(3, sortedList.size()); 563 assertEquals(Arrays.asList("a", "c", "d"), sortedList); 564 compareIndices(); 565 566 mockListObserver.clear(); 567 sortedList.setComparator(null); 568 assertEquals(3, sortedList.size()); 569 assertEquals(Arrays.asList("a", "d", "c"), sortedList); 570 mockListObserver.check1Permutation(sortedList, new int[] {0, 2, 1}); 571 compareIndices(); 572 } 573 574 @Test 575 public void testSetWhenUnsorted() { 576 sortedList.setComparator(null); 577 mockListObserver.clear(); 578 list.set(1, "e"); 579 assertEquals(4, sortedList.size()); 580 assertEquals(Arrays.asList("a", "e", "d", "c"), sortedList); 581 mockListObserver.check1AddRemove(sortedList, Arrays.asList("c"), 1, 2); 582 compareIndices(); 583 584 mockListObserver.clear(); 585 sortedList.setComparator(Comparator.<String>naturalOrder()); 586 mockListObserver.check1Permutation(sortedList, new int[] {0, 3, 2, 1}); 587 assertEquals(4, sortedList.size()); 588 assertEquals(Arrays.asList("a", "c", "d", "e"), sortedList); 589 compareIndices(); 590 591 mockListObserver.clear(); 592 sortedList.setComparator(null); 593 assertEquals(4, sortedList.size()); 594 assertEquals(Arrays.asList("a", "e", "d", "c"), sortedList); 595 mockListObserver.check1Permutation(sortedList, new int[] {0, 3, 2, 1}); 596 compareIndices(); 597 } 598 }