Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8301761

The sorting of the SortedList can become invalid

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P3 P3
    • tbd
    • jfx11, jfx20, 8, jfx17, jfx19
    • javafx
    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      I run on microsoft windows & Java 17 / JavaFX17.
      But the error is also in older version comibinations like Java 11 / JavaFX 16.


      A DESCRIPTION OF THE PROBLEM :
      The error can be located in a wrong implementation of javafx.collections.transformation.SortedList.sourceChanged() under certain conditions.
      If the element in the source list is replaced by set(index, element), the standard ObservableList creates a ListChangeEvent with 1 Remove and 1 Add. The SortedList does not replace the element at the same position if there is another element which compares to 0, as it is first replaced and then added after the other element.

      This can be very problematic e.g. in a table, if the column based comparator compares different records to 0, and we update a record by using set(index, element) .

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      JUnit test below:
      * Create an ObservableList
      * Create a SortedList on that
      * Bind a comparator to the sorted list, that two elements have a 0 comparison result
      * Change the first of the two elements by an exact copy of itself

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The sorted list should not have a different sorting.
      If the comparator returns 0, the order in the original list should then be the comparison result.
      ACTUAL -
      The sort order changes

      ---------- BEGIN SOURCE ----------
      package de.heitec.empic.centralsuite.gui.javafx;

      import java.util.Comparator;
      import java.util.List;
      import java.util.Objects;
      import java.util.stream.Collectors;

      import org.junit.Assert;
      import org.junit.Test;

      import javafx.collections.FXCollections;
      import javafx.collections.ObservableList;
      import javafx.collections.transformation.SortedList;

      public class SortedListTest
      {
         @Test
         public void testSorting()
         {
            // Create a list.
            ObservableList<TestDto> originalList = FXCollections.observableArrayList(List.of(
                  new TestDto('C', 2),
                  new TestDto('C', 1),
                  new TestDto('A', 1)));
            // Create a sorted list. The comparator cannot distinguish between the first and the second element
            SortedList<TestDto> sortedList = new SortedList<>(originalList, Comparator.comparing(TestDto::getValue));

            Assert.assertEquals("C2;C1;A1", toDebugString(originalList));
            // The comparator result of C1 and C2 is the same, but C2 is in front of C1 in the origin list
            Assert.assertEquals("A1;C2;C1", toDebugString(sortedList));

            // We update the value of the second element of the original list with a copy of the same value
            originalList.set(1, new TestDto('C', 1));
            Assert.assertEquals("C2;C1;A1", toDebugString(originalList));

            // HERE COMES THE ERROR:
            // We expect the sorted list to be identical to the status above
            // but instead of that the two results C2 and C1 are mixed
            Assert.assertEquals("A1;C2;C1", toDebugString(sortedList)); // In JavaFX 16 & 17.0.6 "A1;C1;C2" is the result
         }

         private String toDebugString(List<TestDto> testDtos)
         {
            return testDtos.stream()
                           .map(TestDto::toString)
                           .collect(Collectors.joining(";"));
         }

         private static class TestDto
         {
            private final Character value;
            private final int otherIdentifier;

            TestDto(Character value, int otherIdentifier)
            {
               this.value = value;
               this.otherIdentifier = otherIdentifier;
            }

            public Character getValue()
            {
               return value;
            }

            public int getOtherIdentifier()
            {
               return otherIdentifier;
            }

            @Override
            public boolean equals(Object o)
            {
               if (this == o)
               {
                  return true;
               }
               if (o == null || getClass() != o.getClass())
               {
                  return false;
               }
               TestDto testDto = (TestDto) o;
               return value.equals(testDto.value);
            }

            @Override
            public int hashCode()
            {
               return Objects.hash(value);
            }

            @Override
            public String toString()
            {
               return value.toString() + otherIdentifier;
            }
         }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      No usable workaround,
      Maybe copy the whole base list, change it on the copy and use setAll() would help. But using that on tables would have enormous consequences.


      FREQUENCY : always


        1. SortedListTest.java
          3 kB
          Praveen Narayanaswamy

            kcr Kevin Rushforth
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: