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 }