1 /*
   2  * Copyright (c) 2010, 2016, 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 javafx.scene.control;
  27 
  28 import com.sun.javafx.collections.NonIterableChange;
  29 import static javafx.scene.control.SelectionMode.SINGLE;
  30 
  31 import java.util.*;
  32 import java.util.stream.Collectors;
  33 import java.util.stream.IntStream;
  34 
  35 import com.sun.javafx.scene.control.MultipleAdditionAndRemovedChange;
  36 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
  37 import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList;
  38 import javafx.collections.ListChangeListener;
  39 import javafx.collections.ObservableList;
  40 import javafx.collections.ObservableListBase;
  41 import javafx.util.Callback;
  42 
  43 import javafx.util.Pair;
  44 
  45 
  46 /**
  47  * An abstract class that implements more of the abstract MultipleSelectionModel
  48  * abstract class. However, this class is package-protected and not intended
  49  * for public use.
  50  *
  51  * @param <T> The type of the underlying data model for the UI control.
  52  */
  53 abstract class MultipleSelectionModelBase<T> extends MultipleSelectionModel<T> {
  54 
  55     /***********************************************************************
  56      *                                                                     *
  57      * Constructors                                                        *
  58      *                                                                     *
  59      **********************************************************************/
  60 
  61     public MultipleSelectionModelBase() {
  62         selectedIndexProperty().addListener(valueModel -> {
  63             // we used to lazily retrieve the selected item, but now we just
  64             // do it when the selection changes. This is hardly likely to be
  65             // expensive, and we still lazily handle the multiple selection
  66             // cases over in MultipleSelectionModel.
  67             setSelectedItem(getModelItem(getSelectedIndex()));
  68         });
  69 
  70         selectedIndices = new SelectedIndicesList();
  71 
  72         selectedItems = new SelectedItemsReadOnlyObservableList<T>(selectedIndices, () -> getItemCount()) {
  73             @Override protected T getModelItem(int index) {
  74                 return MultipleSelectionModelBase.this.getModelItem(index);
  75             }
  76         };
  77     }
  78 
  79 
  80 
  81     /***********************************************************************
  82      *                                                                     *
  83      * Observable properties                                               *
  84      *                                                                     *
  85      **********************************************************************/
  86 
  87     /*
  88      * We only maintain the values of the selectedIndex and selectedIndices
  89      * properties. The value of the selectedItem and selectedItems properties
  90      * is determined on-demand. We fire the SELECTED_ITEM and SELECTED_ITEMS
  91      * property change events whenever the related SELECTED_INDEX or
  92      * SELECTED_INDICES properties change.
  93      *
  94      * This means that the cost of the ListViewSelectionModel is cheap in most
  95      * cases, assuming that the end-consumer isn't calling getSelectedItems
  96      * too aggressively. Of course, this is only an issue when the ListViewModel
  97      * is being populated by some remote, expensive to query data source.
  98      *
  99      * In addition, we do not provide ObservableLists for the selected indices or the
 100      * selected items properties, as this would allow the API consumer to add
 101      * observers to these ObservableLists. This would make life tougher as we would
 102      * then be forced to keep these ObservableLists in-sync at all times, which for
 103      * the selectedItems ObservableList, would require potentially a lot of work and
 104      * memory. Instead, we return a List, and allow for changes to these Lists
 105      * to be observed through the SELECTED_INDICES and SELECTED_ITEMS
 106      * properties.
 107      */
 108 
 109 
 110     final SelectedIndicesList selectedIndices;
 111     @Override public ObservableList<Integer> getSelectedIndices() {
 112         return selectedIndices;
 113     }
 114 
 115     private final ObservableListBase<T> selectedItems;
 116     @Override public ObservableList<T> getSelectedItems() {
 117         return selectedItems;
 118     }
 119 
 120 
 121 
 122     /***********************************************************************
 123      *                                                                     *
 124      * Internal field                                                      *
 125      *                                                                     *
 126      **********************************************************************/
 127 
 128     ListChangeListener.Change selectedItemChange;
 129 
 130 
 131 
 132     /***********************************************************************
 133      *                                                                     *
 134      * Public selection API                                                *
 135      *                                                                     *
 136      **********************************************************************/
 137 
 138     /**
 139      * Returns the number of items in the data model that underpins the control.
 140      * An example would be that a ListView selection model would likely return
 141      * <code>listView.getItems().size()</code>. The valid range of selectable
 142      * indices is between 0 and whatever is returned by this method.
 143      */
 144     protected abstract int getItemCount();
 145 
 146     /**
 147      * Returns the item at the given index. An example using ListView would be
 148      * <code>listView.getItems().get(index)</code>.
 149      *
 150      * @param index The index of the item that is requested from the underlying
 151      *      data model.
 152      * @return Returns null if the index is out of bounds, or an element of type
 153      *      T that is related to the given index.
 154      */
 155     protected abstract T getModelItem(int index);
 156     protected abstract void focus(int index);
 157     protected abstract int getFocusedIndex();
 158 
 159     static class ShiftParams {
 160         private final int clearIndex;
 161         private final int setIndex;
 162         private final boolean selected;
 163 
 164         ShiftParams(int clearIndex, int setIndex, boolean selected) {
 165             this.clearIndex = clearIndex;
 166             this.setIndex = setIndex;
 167             this.selected = selected;
 168         }
 169 
 170         public final int getClearIndex() {
 171             return clearIndex;
 172         }
 173 
 174         public final int getSetIndex() {
 175             return setIndex;
 176         }
 177 
 178         public final boolean isSelected() {
 179             return selected;
 180         }
 181     }
 182 
 183     // package only
 184     void shiftSelection(int position, int shift, final Callback<ShiftParams, Void> callback) {
 185         shiftSelection(Arrays.asList(new Pair<>(position, shift)), callback);
 186     }
 187 
 188     void shiftSelection(List<Pair<Integer, Integer>> shifts, final Callback<ShiftParams, Void> callback) {
 189         int selectedIndicesCardinality = selectedIndices.size(); // number of true bits
 190         if (selectedIndicesCardinality == 0) return;
 191 
 192         int selectedIndicesSize = selectedIndices.bitsetSize();   // number of bits reserved
 193 
 194         int[] perm = new int[selectedIndicesSize];
 195         Arrays.fill(perm, -1);
 196 
 197         // sort the list so that we iterate from highest position to lowest position
 198         Collections.sort(shifts, (s1, s2) -> Integer.compare(s2.getKey(), s1.getKey()));
 199         final int lowestShiftPosition = shifts.get(shifts.size() - 1).getKey();
 200 
 201         // make a copy of the selectedIndices before so we can compare to it afterwards
 202         BitSet selectedIndicesCopy = (BitSet) selectedIndices.bitset.clone();
 203 
 204         startAtomic();
 205         for (Pair<Integer, Integer> shift : shifts) {
 206             doShift(shift, callback, perm);
 207         }
 208         stopAtomic();
 209 
 210         // strip out all useless -1 default values from the perm array
 211         final int[] prunedPerm = Arrays.stream(perm).filter(value -> value > -1).toArray();
 212         final boolean hasSelectionChanged = prunedPerm.length > 0;
 213 
 214         // This ensure that the selection remains accurate when a shift occurs.
 215         final int selectedIndex = getSelectedIndex();
 216         if (selectedIndex >= lowestShiftPosition && selectedIndex > -1) {
 217             // sum up the total shift, where the position is less than or equal
 218             // to the previously selected index
 219             int totalShift = shifts.stream()
 220                     .filter(shift -> shift.getKey() <= selectedIndex)
 221                     .mapToInt(shift -> shift.getValue())
 222                     .sum();
 223 
 224             // Fix for RT-38787: we used to not enter this block if
 225             // selectedIndex + shift resulted in a value less than zero, whereas
 226             // now we just set the newSelectionLead to zero in that instance.
 227             // There exists unit tests that cover this.
 228             final int newSelectionLead = Math.max(0, selectedIndex + totalShift);
 229 
 230             setSelectedIndex(newSelectionLead);
 231 
 232             // added the selectedIndices call for RT-30356.
 233             // changed to check if hasPermutated, and to call select(..) for RT-40010.
 234             // This forces the selection event to go through the system and fire
 235             // the necessary events.
 236             if (hasSelectionChanged) {
 237                 selectedIndices.set(newSelectionLead, true);
 238             } else {
 239                 select(newSelectionLead);
 240             }
 241 
 242             // removed due to RT-27185
 243 //            focus(newSelectionLead);
 244         }
 245 
 246         if (hasSelectionChanged) {
 247             // work out what indices were removed and added
 248             BitSet removed = (BitSet) selectedIndicesCopy.clone();
 249             removed.andNot(selectedIndices.bitset);
 250 
 251             BitSet added = (BitSet) selectedIndices.bitset.clone();
 252             added.andNot(selectedIndicesCopy);
 253 
 254             selectedIndices.reset();
 255             selectedIndices.callObservers(new MultipleAdditionAndRemovedChange<>(
 256                     added.stream().boxed().collect(Collectors.toList()),
 257                     removed.stream().boxed().collect(Collectors.toList()),
 258                     selectedIndices
 259             ));
 260         }
 261     }
 262 
 263     private void doShift(Pair<Integer, Integer> shiftPair, final Callback<ShiftParams, Void> callback, int[] perm) {
 264         final int position = shiftPair.getKey();
 265         final int shift = shiftPair.getValue();
 266 
 267         // with no check here, we get RT-15024
 268         if (position < 0) return;
 269         if (shift == 0) return;
 270 
 271         int idx = (int) Arrays.stream(perm).filter(value -> value > -1).count();
 272 
 273         int selectedIndicesSize = selectedIndices.bitsetSize() - idx;   // number of bits reserved
 274 
 275         if (shift > 0) {
 276             for (int i = selectedIndicesSize - 1; i >= position && i >= 0; i--) {
 277                 boolean selected = selectedIndices.isSelected(i);
 278 
 279                 if (callback == null) {
 280                     selectedIndices.clear(i);
 281                     selectedIndices.set(i + shift, selected);
 282                 } else {
 283                     callback.call(new ShiftParams(i, i + shift, selected));
 284                 }
 285 
 286                 if (selected) {
 287                     perm[idx++] = i + 1;
 288                 }
 289             }
 290             selectedIndices.clear(position);
 291         } else if (shift < 0) {
 292             for (int i = position; i < selectedIndicesSize; i++) {
 293                 if ((i + shift) < 0) continue;
 294                 if ((i + 1 + shift) < position) continue;
 295                 boolean selected = selectedIndices.isSelected(i + 1);
 296 
 297                 if (callback == null) {
 298                     selectedIndices.clear(i + 1);
 299                     selectedIndices.set(i + 1 + shift, selected);
 300                 } else {
 301                     callback.call(new ShiftParams(i + 1, i + 1 + shift, selected));
 302                 }
 303 
 304                 if (selected) {
 305                     perm[idx++] = i;
 306                 }
 307             }
 308         }
 309     }
 310 
 311     void startAtomic() {
 312         selectedIndices.startAtomic();
 313     }
 314 
 315     void stopAtomic() {
 316         selectedIndices.stopAtomic();
 317     }
 318 
 319     boolean isAtomic() {
 320         return selectedIndices.isAtomic();
 321     }
 322 
 323     @Override public void clearAndSelect(int row) {
 324         if (row < 0 || row >= getItemCount()) {
 325             clearSelection();
 326             return;
 327         }
 328 
 329         final boolean wasSelected = isSelected(row);
 330 
 331         // RT-33558 if this method has been called with a given row, and that
 332         // row is the only selected row currently, then this method becomes a no-op.
 333         if (wasSelected && getSelectedIndices().size() == 1) {
 334             // before we return, we double-check that the selected item
 335             // is equal to the item in the given index
 336             if (getSelectedItem() == getModelItem(row)) {
 337                 return;
 338             }
 339         }
 340 
 341         // firstly we make a copy of the selection, so that we can send out
 342         // the correct details in the selection change event.
 343         // We remove the new selection from the list seeing as it is not removed.
 344         BitSet selectedIndicesCopy = new BitSet();
 345         selectedIndicesCopy.or(selectedIndices.bitset);
 346         selectedIndicesCopy.clear(row);
 347         List<Integer> previousSelectedIndices = new SelectedIndicesList(selectedIndicesCopy);
 348 
 349         // RT-32411 We used to call quietClearSelection() here, but this
 350         // resulted in the selectedItems and selectedIndices lists never
 351         // reporting that they were empty.
 352         // makeAtomic toggle added to resolve RT-32618
 353         startAtomic();
 354 
 355         // then clear the current selection
 356         clearSelection();
 357 
 358         // and select the new row
 359         select(row);
 360         stopAtomic();
 361 
 362         // fire off a single add/remove/replace notification (rather than
 363         // individual remove and add notifications) - see RT-33324
 364         ListChangeListener.Change<Integer> change;
 365 
 366         /*
 367          * getFrom() documentation:
 368          *   If wasAdded is true, the interval contains all the values that were added.
 369          *   If wasPermutated is true, the interval marks the values that were permutated.
 370          *   If wasRemoved is true and wasAdded is false, getFrom() and getTo() should
 371          *   return the same number - the place where the removed elements were positioned in the list.
 372          */
 373         if (wasSelected) {
 374             change = ControlUtils.buildClearAndSelectChange(selectedIndices, previousSelectedIndices, row);
 375         } else {
 376             int changeIndex = Math.max(0, selectedIndices.indexOf(row));
 377             change = new NonIterableChange.GenericAddRemoveChange<>(
 378                     changeIndex, changeIndex+1, previousSelectedIndices, selectedIndices);
 379         }
 380 
 381         selectedIndices.callObservers(change);
 382     }
 383 
 384     @Override public void select(int row) {
 385         if (row == -1) {
 386             clearSelection();
 387             return;
 388         }
 389         if (row < 0 || row >= getItemCount()) {
 390             return;
 391         }
 392 
 393         boolean isSameRow = row == getSelectedIndex();
 394         T currentItem = getSelectedItem();
 395         T newItem = getModelItem(row);
 396         boolean isSameItem = newItem != null && newItem.equals(currentItem);
 397         boolean fireUpdatedItemEvent = isSameRow && ! isSameItem;
 398 
 399         // focus must come first so that we have the anchors set appropriately
 400         focus(row);
 401 
 402         if (! selectedIndices.isSelected(row)) {
 403             if (getSelectionMode() == SINGLE) {
 404                 startAtomic();
 405                 quietClearSelection();
 406                 stopAtomic();
 407             }
 408             selectedIndices.set(row);
 409         }
 410 
 411         setSelectedIndex(row);
 412 
 413         if (fireUpdatedItemEvent) {
 414             setSelectedItem(newItem);
 415         }
 416     }
 417 
 418     @Override public void select(T obj) {
 419 //        if (getItemCount() <= 0) return;
 420 
 421         if (obj == null && getSelectionMode() == SelectionMode.SINGLE) {
 422             clearSelection();
 423             return;
 424         }
 425 
 426         // We have no option but to iterate through the model and select the
 427         // first occurrence of the given object. Once we find the first one, we
 428         // don't proceed to select any others.
 429         Object rowObj = null;
 430         for (int i = 0, max = getItemCount(); i < max; i++) {
 431             rowObj = getModelItem(i);
 432             if (rowObj == null) continue;
 433 
 434             if (rowObj.equals(obj)) {
 435                 if (isSelected(i)) {
 436                     return;
 437                 }
 438 
 439                 if (getSelectionMode() == SINGLE) {
 440                     quietClearSelection();
 441                 }
 442 
 443                 select(i);
 444                 return;
 445             }
 446         }
 447 
 448         // if we are here, we did not find the item in the entire data model.
 449         // Even still, we allow for this item to be set to the give object.
 450         // We expect that in concrete subclasses of this class we observe the
 451         // data model such that we check to see if the given item exists in it,
 452         // whilst SelectedIndex == -1 && SelectedItem != null.
 453         setSelectedIndex(-1);
 454         setSelectedItem(obj);
 455     }
 456 
 457     @Override public void selectIndices(int row, int... rows) {
 458         if (rows == null || rows.length == 0) {
 459             select(row);
 460             return;
 461         }
 462 
 463         /*
 464          * Performance optimisation - if multiple selection is disabled, only
 465          * process the end-most row index.
 466          */
 467 
 468         int rowCount = getItemCount();
 469 
 470         if (getSelectionMode() == SINGLE) {
 471             quietClearSelection();
 472 
 473             for (int i = rows.length - 1; i >= 0; i--) {
 474                 int index = rows[i];
 475                 if (index >= 0 && index < rowCount) {
 476                     selectedIndices.set(index);
 477                     select(index);
 478                     break;
 479                 }
 480             }
 481 
 482             if (selectedIndices.isEmpty()) {
 483                 if (row > 0 && row < rowCount) {
 484                     selectedIndices.set(row);
 485                     select(row);
 486                 }
 487             }
 488         } else {
 489             selectedIndices.set(row, rows);
 490 
 491             IntStream.concat(IntStream.of(row), IntStream.of(rows))
 492                      .filter(index -> index >= 0 && index < rowCount)
 493                      .reduce((first, second) -> second)
 494                      .ifPresent(lastIndex -> {
 495                          setSelectedIndex(lastIndex);
 496                          focus(lastIndex);
 497                          setSelectedItem(getModelItem(lastIndex));
 498                      });
 499         }
 500     }
 501 
 502     @Override public void selectAll() {
 503         if (getSelectionMode() == SINGLE) return;
 504 
 505         if (getItemCount() <= 0) return;
 506 
 507         final int rowCount = getItemCount();
 508         final int focusedIndex = getFocusedIndex();
 509 
 510         // set all selected indices to true
 511         clearSelection();
 512         selectedIndices.set(0, rowCount, true);
 513 
 514         if (focusedIndex == -1) {
 515             setSelectedIndex(rowCount - 1);
 516             focus(rowCount - 1);
 517         } else {
 518             setSelectedIndex(focusedIndex);
 519             focus(focusedIndex);
 520         }
 521     }
 522 
 523     @Override public void selectFirst() {
 524         if (getSelectionMode() == SINGLE) {
 525             quietClearSelection();
 526         }
 527 
 528         if (getItemCount() > 0) {
 529             select(0);
 530         }
 531     }
 532 
 533     @Override public void selectLast() {
 534         if (getSelectionMode() == SINGLE) {
 535             quietClearSelection();
 536         }
 537 
 538         int numItems = getItemCount();
 539         if (numItems > 0 && getSelectedIndex() < numItems - 1) {
 540             select(numItems - 1);
 541         }
 542     }
 543 
 544     @Override public void clearSelection(int index) {
 545         if (index < 0) return;
 546 
 547         // TODO shouldn't directly access like this
 548         // TODO might need to update focus and / or selected index/item
 549         boolean wasEmpty = selectedIndices.isEmpty();
 550         selectedIndices.clear(index);
 551 
 552         if (! wasEmpty && selectedIndices.isEmpty()) {
 553             clearSelection();
 554         }
 555     }
 556 
 557     @Override public void clearSelection() {
 558         quietClearSelection();
 559 
 560         if (! isAtomic()) {
 561             setSelectedIndex(-1);
 562             focus(-1);
 563         }
 564     }
 565 
 566     private void quietClearSelection() {
 567         selectedIndices.clear();
 568     }
 569 
 570     @Override public boolean isSelected(int index) {
 571         // Note the change in semantics here - we used to check to ensure that
 572         // the index is less than the item count, but now simply ensure that
 573         // it is less than the length of the selectedIndices bitset. This helps
 574         // to resolve issues such as RT-26721, where isSelected(int) was being
 575         // called for indices that exceeded the item count, as a TreeItem (e.g.
 576         // the root) was being collapsed.
 577 //        if (index >= 0 && index < getItemCount()) {
 578         if (index >= 0 && index < selectedIndices.bitsetSize()) {
 579             return selectedIndices.isSelected(index);
 580         }
 581 
 582         return false;
 583     }
 584 
 585     @Override public boolean isEmpty() {
 586         return selectedIndices.isEmpty();
 587     }
 588 
 589     @Override public void selectPrevious() {
 590         int focusIndex = getFocusedIndex();
 591 
 592         if (getSelectionMode() == SINGLE) {
 593             quietClearSelection();
 594         }
 595 
 596         if (focusIndex == -1) {
 597             select(getItemCount() - 1);
 598         } else if (focusIndex > 0) {
 599             select(focusIndex - 1);
 600         }
 601     }
 602 
 603     @Override public void selectNext() {
 604         int focusIndex = getFocusedIndex();
 605 
 606         if (getSelectionMode() == SINGLE) {
 607             quietClearSelection();
 608         }
 609 
 610         if (focusIndex == -1) {
 611             select(0);
 612         } else if (focusIndex != getItemCount() -1) {
 613             select(focusIndex + 1);
 614         }
 615     }
 616 
 617 
 618 
 619     /***********************************************************************
 620      *                                                                     *
 621      * Private implementation                                              *
 622      *                                                                     *
 623      **********************************************************************/
 624 
 625     class SelectedIndicesList extends ReadOnlyUnbackedObservableList<Integer> {
 626         private final BitSet bitset;
 627 
 628         private int lastGetIndex = -1;
 629         private int lastGetValue = -1;
 630 
 631         // Fix for RT-20945 (and numerous other issues!)
 632         private int atomicityCount = 0;
 633 
 634 //        @Override
 635 //        public void callObservers(Change<Integer> c) {
 636 //            throw new RuntimeException("callObservers unavailable");
 637 //        }
 638 
 639         public SelectedIndicesList() {
 640             this(new BitSet());
 641         }
 642 
 643         public SelectedIndicesList(BitSet bitset) {
 644             this.bitset = bitset;
 645         }
 646 
 647         boolean isAtomic() {
 648             return atomicityCount > 0;
 649         }
 650         void startAtomic() {
 651             atomicityCount++;
 652         }
 653         void stopAtomic() {
 654             atomicityCount = Math.max(0, atomicityCount - 1);
 655         }
 656 
 657         // Returns the selected index at the given index.
 658         // e.g. if our selectedIndices are [1,3,5], then an index of 2 will return 5 here.
 659         @Override public Integer get(int index) {
 660             final int itemCount = size();
 661             if (index < 0 || index >= itemCount)  {
 662                 throw new IndexOutOfBoundsException(index + " >= " + itemCount);
 663             }
 664 
 665             if (index == (lastGetIndex + 1) && lastGetValue < itemCount) {
 666                 // we're iterating forward in order, short circuit for
 667                 // performance reasons (RT-39776)
 668                 lastGetIndex++;
 669                 lastGetValue = bitset.nextSetBit(lastGetValue + 1);
 670                 return lastGetValue;
 671             } else if (index == (lastGetIndex - 1) && lastGetValue > 0) {
 672                 // we're iterating backward in order, short circuit for
 673                 // performance reasons (RT-39776)
 674                 lastGetIndex--;
 675                 lastGetValue = bitset.previousSetBit(lastGetValue - 1);
 676                 return lastGetValue;
 677             } else {
 678                 for (lastGetIndex = 0, lastGetValue = bitset.nextSetBit(0);
 679                      lastGetValue >= 0 || lastGetIndex == index;
 680                      lastGetIndex++, lastGetValue = bitset.nextSetBit(lastGetValue + 1)) {
 681                     if (lastGetIndex == index) {
 682                         return lastGetValue;
 683                     }
 684                 }
 685             }
 686 
 687             return -1;
 688         }
 689 
 690         public void set(int index) {
 691             if (!isValidIndex(index) || isSelected(index)) {
 692                 return;
 693             }
 694 
 695             _beginChange();
 696             bitset.set(index);
 697             int indicesIndex = indexOf(index);
 698             _nextAdd(indicesIndex, indicesIndex + 1);
 699             _endChange();
 700         }
 701 
 702         private boolean isValidIndex(int index) {
 703             return index >= 0 && index < getItemCount();
 704         }
 705 
 706         public void set(int index, boolean isSet) {
 707             if (isSet) {
 708                 set(index);
 709             } else {
 710                 clear(index);
 711             }
 712         }
 713 
 714         public void set(int index, int end, boolean isSet) {
 715             _beginChange();
 716             if (isSet) {
 717                 bitset.set(index, end, isSet);
 718                 int indicesIndex = indexOf(index);
 719                 int span = end - index;
 720                 _nextAdd(indicesIndex, indicesIndex + span);
 721             } else {
 722                 // TODO handle remove
 723                 bitset.set(index, end, isSet);
 724             }
 725             _endChange();
 726         }
 727 
 728         public void set(int index, int... indices) {
 729             if (indices == null || indices.length == 0) {
 730                 set(index);
 731             } else {
 732                 // we reduce down to the minimal number of changes possible
 733                 // by finding all contiguous indices, of all indices that are
 734                 // not already selected, and which are in the valid range
 735                 startAtomic();
 736                 List<Integer> sortedNewIndices =
 737                         IntStream.concat(IntStream.of(index), IntStream.of(indices))
 738                         .distinct()
 739                         .filter(this::isValidIndex)
 740                         .filter(this::isNotSelected)
 741                         .sorted()
 742                         .boxed()
 743                         .peek(this::set) // we also set here, but it's atomic!
 744                         .collect(Collectors.toList());
 745                 stopAtomic();
 746 
 747                 final int size = sortedNewIndices.size();
 748                 if (size == 0) {
 749                     // no-op
 750                 } else if (size == 1) {
 751                     _beginChange();
 752                     int _index = sortedNewIndices.get(0);
 753                     _nextAdd(_index, _index + 1);
 754                     _endChange();
 755                 } else {
 756                     _beginChange();
 757                     int pos = 0;
 758                     int start = 0;
 759                     int end = 0;
 760 
 761                     // starting from pos, we keep going until the value is
 762                     // not the next value
 763                     int startValue = sortedNewIndices.get(pos++);
 764                     start = indexOf(startValue);
 765                     end = start + 1;
 766                     int endValue = startValue;
 767                     while (pos < size) {
 768                         int previousEndValue = endValue;
 769                         endValue = sortedNewIndices.get(pos++);
 770                         ++end;
 771                         if (previousEndValue != (endValue - 1)) {
 772                             _nextAdd(start, end);
 773                             start = end;
 774                             continue;
 775                         }
 776 
 777                         // special case for when we get to the point where the loop is about to end
 778                         // and we have uncommitted changes to fire.
 779                         if (pos == size) {
 780                             _nextAdd(start, start + pos);
 781                         }
 782                     }
 783 
 784                     _endChange();
 785                 }
 786             }
 787         }
 788 
 789         public void clear() {
 790             _beginChange();
 791             List<Integer> removed = bitset.stream().boxed().collect(Collectors.toList());
 792             bitset.clear();
 793             _nextRemove(0, removed);
 794             _endChange();
 795         }
 796 
 797         public void clear(int index) {
 798             if (!bitset.get(index)) return;
 799 
 800             _beginChange();
 801             bitset.clear(index);
 802             _nextRemove(index, index);
 803             _endChange();
 804         }
 805 
 806 //        public void clearAndSelect(int index) {
 807 //            if (index < 0 || index >= getItemCount()) {
 808 //                clearSelection();
 809 //                return;
 810 //            }
 811 //
 812 //            final boolean wasSelected = isSelected(index);
 813 //
 814 //            // RT-33558 if this method has been called with a given row, and that
 815 //            // row is the only selected row currently, then this method becomes a no-op.
 816 //            if (wasSelected && getSelectedIndices().size() == 1) {
 817 //                // before we return, we double-check that the selected item
 818 //                // is equal to the item in the given index
 819 //                if (getSelectedItem() == getModelItem(index)) {
 820 //                    return;
 821 //                }
 822 //            }
 823 //
 824 //            List<Integer> removed = bitset.stream().boxed().collect(Collectors.toList());
 825 //            boolean isSelected = removed.contains(index);
 826 //            if (isSelected) {
 827 //                removed.remove((Object)index);
 828 //            }
 829 //
 830 //            if (removed.isEmpty()) {
 831 //                set(index);
 832 //            }
 833 //
 834 //            bitset.clear();
 835 //            bitset.set(index);
 836 //            _beginChange();
 837 //            if (isSelected) {
 838 //                _nextRemove(0, removed);
 839 //            } else {
 840 //                _nextAdd(0, 1);
 841 //                _nextRemove(0, removed);
 842 //            }
 843 //            _endChange();
 844 //        }
 845 
 846         public boolean isSelected(int index) {
 847             return bitset.get(index);
 848         }
 849 
 850         public boolean isNotSelected(int index) {
 851             return !isSelected(index);
 852         }
 853 
 854         /** Returns number of true bits in BitSet */
 855         @Override public int size() {
 856             return bitset.cardinality();
 857         }
 858 
 859         /** Returns the number of bits reserved in the BitSet */
 860         public int bitsetSize() {
 861             return bitset.size();
 862         }
 863 
 864         @Override public int indexOf(Object obj) {
 865             reset();
 866             return super.indexOf(obj);
 867         }
 868 
 869         @Override public boolean contains(Object o) {
 870             if (o instanceof Number) {
 871                 Number n = (Number) o;
 872                 int index = n.intValue();
 873 
 874                 return index >= 0 && index < bitset.length() &&
 875                         bitset.get(index);
 876             }
 877 
 878             return false;
 879         }
 880 
 881         public void reset() {
 882             this.lastGetIndex = -1;
 883             this.lastGetValue = -1;
 884         }
 885 
 886         @Override public void _beginChange() {
 887             if (!isAtomic()) {
 888                 super._beginChange();
 889             }
 890         }
 891 
 892         @Override public void _endChange() {
 893             if (!isAtomic()) {
 894                 super._endChange();
 895             }
 896         }
 897 
 898         @Override public final void _nextUpdate(int pos) {
 899             if (!isAtomic()) {
 900                 nextUpdate(pos);
 901             }
 902         }
 903 
 904         @Override public final void _nextSet(int idx, Integer old) {
 905             if (!isAtomic()) {
 906                 nextSet(idx, old);
 907             }
 908         }
 909 
 910         @Override public final void _nextReplace(int from, int to, List<? extends Integer> removed) {
 911             if (!isAtomic()) {
 912                 nextReplace(from, to, removed);
 913             }
 914         }
 915 
 916         @Override public final void _nextRemove(int idx, List<? extends Integer> removed) {
 917             if (!isAtomic()) {
 918                 nextRemove(idx, removed);
 919             }
 920         }
 921 
 922         @Override public final void _nextRemove(int idx, Integer removed) {
 923             if (!isAtomic()) {
 924                 nextRemove(idx, removed);
 925             }
 926         }
 927 
 928         @Override public final void _nextPermutation(int from, int to, int[] perm) {
 929             if (!isAtomic()) {
 930                 nextPermutation(from, to, perm);
 931             }
 932         }
 933 
 934         @Override public final void _nextAdd(int from, int to) {
 935             if (!isAtomic()) {
 936                 nextAdd(from, to);
 937             }
 938         }
 939     }
 940 }