On Tue, 17 Nov 2020 12:38:32 GMT, Jeanette Winzenburg <faste...@openjdk.org> 
wrote:

>> Fixing IndexOutOfBoundsException in the MultipleSelectionModelBase and added 
>> a unit-test for it.
>> ticket: https://bugs.openjdk.java.net/browse/JDK-8256397
>> run test: `./gradlew --continue -PFULL_TEST=true controls:test --tests 
>> "*MultipleSelectionModelImplTest*"`
>
> good catch :)
> 
> No review, just a couple of comments:
> 
> - other subclasses of MultipleSelectionModelBase might have similar issues 
> - the fix gets rid of the error (good!) but might fire incorrect changes (see 
> below)
> - there are quite a lot of open issues related to change notification, some 
> might profit from a fix of this
> - tests, tests, tests .. :)
> 
> selectIndices might involve disjoint intervals in the change (even for 
> addition which is unusual in the "normal" implementations of observableLists)
> 
>        // initial selection, starting from empty
>        selectionModel.selectIndices(0, /*1, IMPORTANT: remove some index */ 
> 2,  3);
>        System.out.println(change);
>       // expected and actual change 
>       { [0, 2, 3] added at 0 }
> 
>       // add selection of disjoint indices (assuming a longer items list)
>        selectionModel.selectIndices(1, 5, 7);
>        System.out.println(change);
>       // actual
>        { [1, 2, 3] added at 1 }
>       // expected
>      { [1] added at 1, [5, 7] added at 4 }

I don't understand how the original code, as well as the proposed solution, can 
work at all. Let's take @kleopatra's example:

selectionModel.selectIndices(0, 2, 3);
// ---> expected: { [0, 2, 3] added at 0 }
selectionModel.selectIndices(1, 5, 7);
// ---> expected: { [1] added at 1, [5, 7] added at 4 }

In order to find the two sub-ranges `[1]` and `[5, 7]`, we need to compare the 
_newly added_ indices to the _entire_ list of selected indices. Without doing 
that, we can't just "know" that there are two contiguous sub-ranges in `[1, 5, 
7]`.

However, the algorithm that supposedly tries to do that, doesn't even look at 
the current list of selected indices (except for a single `indexOf` call, which 
doesn't do the trick):

int pos = 0;
int start = 0;
int end = 0;

// starting from pos, we keep going until the value is
// not the next value
int startValue = sortedNewIndices.get(pos++);
start = indexOf(startValue);
end = start + 1;
int endValue = startValue;
while (pos < size) {
    int previousEndValue = endValue;
    endValue = sortedNewIndices.get(pos++);
    ++end;
    if (previousEndValue != (endValue - 1)) {
        _nextAdd(start, end);
        start = end;
        continue;
    }
    
    // special case for when we get to the point where the loop is about to end
    // and we have uncommitted changes to fire.
    if (pos == size) {
        _nextAdd(start, end);
    }
}

I think this algorithm mistakenly finds contiguous ranges within the list of 
_newly added_ indices, which is not what we want.

Here is an algorithm that works:

int startIndex = indexOf(sortedNewIndices.get(0));
int endIndex = startIndex + 1;

for (int i = 1; i < sortedNewIndices.size(); ++i) {
    int currentValue = get(endIndex);
    int currentNewValue = sortedNewIndices.get(i);
    if (currentValue != currentNewValue) {
        _nextAdd(startIndex, endIndex);
        while (get(endIndex) != currentNewValue) ++endIndex;
        startIndex = endIndex++;
    } else {
        ++endIndex;
    }

    if (i == sortedNewIndices.size() - 1) {
        _nextAdd(startIndex, endIndex);
    }
}


Unfortunately, even with the working algorithm, the list change notifications 
are still not correct in all cases due to another (probably unrelated) bug in 
`SelectedIndicesList.get(int)`: If I remove the optimization that is 
implemented in that method, and replace it with a simple lookup, the 
notifications are always correct in my tests:

@Override public Integer get(int index) {
    for (int lastGetIndex = 0, lastGetValue = bitset.nextSetBit(0);
         lastGetValue >= 0 || lastGetIndex == index;
         lastGetIndex++, lastGetValue = bitset.nextSetBit(lastGetValue + 1)) {
        if (lastGetIndex == index) {
            return lastGetValue;
        }
    }
    return -1;
}


Test 1:

selectionModel.selectIndices(0, 2, 3);
// ---> { [0, 2, 3] added at 0 }
selectionModel.selectIndices(1, 5, 7);
// ---> { [1] added at 1, [5, 7] added at 4 }

Test 2:

selectionModel.selectIndices(1, 3, 4);
// ---> { [1, 3, 4] added at 0 }
selectionModel.selectIndices(0, 5, 7);
// ---> { [0] added at 0, [5, 7] added at 4 }
selectionModel.selectIndices(6, 8, 9);
// ---> { [6] added at 5, [8, 9] added at 7 }

-------------

PR: https://git.openjdk.org/jfx/pull/353

Reply via email to