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 javafx.scene.control;
  27 
  28 import com.sun.javafx.collections.MappingChange;
  29 import com.sun.javafx.collections.NonIterableChange;
  30 import static javafx.scene.control.SelectionMode.SINGLE;
  31 
  32 import java.util.AbstractList;
  33 import java.util.ArrayList;
  34 import java.util.BitSet;
  35 import java.util.Collections;
  36 import java.util.List;
  37 
  38 import javafx.collections.ListChangeListener;
  39 import javafx.collections.ObservableList;
  40 import javafx.collections.ListChangeListener.Change;
  41 import javafx.util.Callback;
  42 
  43 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
  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 BitSet();
  71 
  72         selectedIndicesSeq = createListFromBitSet(selectedIndices);
  73         
  74         final MappingChange.Map<Integer,T> map = f -> getModelItem(f);
  75         
  76         selectedIndicesSeq.addListener(new ListChangeListener<Integer>() {
  77             @Override public void onChanged(final Change<? extends Integer> c) {
  78                 // when the selectedIndices ObservableList changes, we manually call
  79                 // the observers of the selectedItems ObservableList.
  80 
  81                 // Fix for a bug identified whilst fixing RT-37395:
  82                 // We shouldn't fire events on the selectedItems list unless
  83                 // the indices list has actually changed. This means that index
  84                 // permutation events should not be forwarded blindly through the
  85                 // items list, as a index permutation implies the items list is
  86                 // unchanged, not changed!
  87                 boolean hasRealChangeOccurred = false;
  88                 while (c.next() && ! hasRealChangeOccurred) {
  89                     hasRealChangeOccurred = c.wasAdded() || c.wasRemoved();
  90                 }
  91 
  92                 if (hasRealChangeOccurred) {
  93                     if (selectedItemChange != null) {
  94                         selectedItemsSeq.callObservers(selectedItemChange);
  95                     } else {
  96                         c.reset();
  97                         selectedItemsSeq.callObservers(new MappingChange<Integer, T>(c, map, selectedItemsSeq));
  98                     }
  99                 }
 100                 c.reset();
 101             }
 102         });
 103 
 104 
 105         selectedItemsSeq = new ReadOnlyUnbackedObservableList<T>() {
 106             @Override public T get(int i) {
 107                 int pos = selectedIndicesSeq.get(i);
 108                 return getModelItem(pos);
 109             }
 110 
 111             @Override public int size() {
 112                 return selectedIndices.cardinality();
 113             }
 114         };
 115     }
 116 
 117 
 118 
 119     /***********************************************************************
 120      *                                                                     *
 121      * Observable properties                                               *
 122      *                                                                     *
 123      **********************************************************************/
 124 
 125     /*
 126      * We only maintain the values of the selectedIndex and selectedIndices
 127      * properties. The value of the selectedItem and selectedItems properties
 128      * is determined on-demand. We fire the SELECTED_ITEM and SELECTED_ITEMS
 129      * property change events whenever the related SELECTED_INDEX or
 130      * SELECTED_INDICES properties change.
 131      *
 132      * This means that the cost of the ListViewSelectionModel is cheap in most
 133      * cases, assuming that the end-consumer isn't calling getSelectedItems
 134      * too aggressively. Of course, this is only an issue when the ListViewModel
 135      * is being populated by some remote, expensive to query data source.
 136      *
 137      * In addition, we do not provide ObservableLists for the selected indices or the
 138      * selected items properties, as this would allow the API consumer to add
 139      * observers to these ObservableLists. This would make life tougher as we would
 140      * then be forced to keep these ObservableLists in-sync at all times, which for
 141      * the selectedItems ObservableList, would require potentially a lot of work and
 142      * memory. Instead, we return a List, and allow for changes to these Lists
 143      * to be observed through the SELECTED_INDICES and SELECTED_ITEMS
 144      * properties.
 145      */
 146 
 147 
 148     final BitSet selectedIndices;
 149     final ReadOnlyUnbackedObservableList<Integer> selectedIndicesSeq;
 150     @Override public ObservableList<Integer> getSelectedIndices() {
 151         return selectedIndicesSeq;
 152     }
 153 
 154     private final ReadOnlyUnbackedObservableList<T> selectedItemsSeq;
 155     @Override public ObservableList<T> getSelectedItems() {
 156         return selectedItemsSeq;
 157     }
 158 
 159 
 160 
 161     /***********************************************************************
 162      *                                                                     *
 163      * Internal field                                                      *
 164      *                                                                     *
 165      **********************************************************************/
 166 
 167     ListChangeListener.Change selectedItemChange;
 168 
 169     // Fix for RT-20945 (and numerous other issues!)
 170     private int atomicityCount = 0;
 171     boolean isAtomic() {
 172         return atomicityCount > 0;
 173     }
 174     void startAtomic() {
 175         atomicityCount++;
 176     }
 177     void stopAtomic() {
 178         atomicityCount = Math.max(0, --atomicityCount);
 179     }
 180 
 181 
 182     /***********************************************************************
 183      *                                                                     *
 184      * Public selection API                                                *
 185      *                                                                     *
 186      **********************************************************************/
 187 
 188     /**
 189      * Returns the number of items in the data model that underpins the control.
 190      * An example would be that a ListView selection model would likely return
 191      * <code>listView.getItems().size()</code>. The valid range of selectable
 192      * indices is between 0 and whatever is returned by this method.
 193      */
 194     protected abstract int getItemCount();
 195     
 196     /**
 197      * Returns the item at the given index. An example using ListView would be
 198      * <code>listView.getItems().get(index)</code>.
 199      * 
 200      * @param index The index of the item that is requested from the underlying
 201      *      data model.
 202      * @return Returns null if the index is out of bounds, or an element of type
 203      *      T that is related to the given index.
 204      */
 205     protected abstract T getModelItem(int index);
 206     protected abstract void focus(int index);
 207     protected abstract int getFocusedIndex();
 208     
 209     static class ShiftParams {
 210         private final int clearIndex;
 211         private final int setIndex;
 212         private final boolean selected;
 213         
 214         ShiftParams(int clearIndex, int setIndex, boolean selected) {
 215             this.clearIndex = clearIndex;
 216             this.setIndex = setIndex;
 217             this.selected = selected;
 218         }
 219         
 220         public final int getClearIndex() {
 221             return clearIndex;
 222         }
 223         
 224         public final int getSetIndex() {
 225             return setIndex;
 226         }
 227         
 228         public final boolean isSelected() {
 229             return selected;
 230         }
 231     }
 232     
 233     // package only
 234     void shiftSelection(int position, int shift, final Callback<ShiftParams, Void> callback) {
 235         // with no check here, we get RT-15024
 236         if (position < 0) return;
 237         if (shift == 0) return;
 238         
 239         int selectedIndicesCardinality = selectedIndices.cardinality(); // number of true bits
 240         if (selectedIndicesCardinality == 0) return;
 241         
 242         int selectedIndicesSize = selectedIndices.size();   // number of bits reserved 
 243         
 244         int[] perm = new int[selectedIndicesSize];
 245         int idx = 0;
 246         boolean hasPermutated = false;
 247         
 248         if (shift > 0) {
 249             for (int i = selectedIndicesSize - 1; i >= position && i >= 0; i--) {
 250                 boolean selected = selectedIndices.get(i);
 251                 
 252                 if (callback == null) {
 253                     selectedIndices.clear(i);
 254                     selectedIndices.set(i + shift, selected);
 255                 } else {
 256                     callback.call(new ShiftParams(i, i + shift, selected));
 257                 }
 258 
 259                 if (selected) {
 260                     perm[idx++] = i + 1;
 261                     hasPermutated = true;
 262                 }
 263             }
 264             selectedIndices.clear(position);
 265         } else if (shift < 0) {
 266             for (int i = position; i < selectedIndicesSize; i++) {
 267                 if ((i + shift) < 0) continue;
 268                 if ((i + 1 + shift) < position) continue;
 269                 boolean selected = selectedIndices.get(i + 1);
 270                 
 271                 if (callback == null) {
 272                     selectedIndices.clear(i + 1);
 273                     selectedIndices.set(i + 1 + shift, selected);
 274                 } else {
 275                     callback.call(new ShiftParams(i + 1, i + 1 + shift, selected));
 276                 }
 277 
 278                 if (selected) {
 279                     perm[idx++] = i;
 280                     hasPermutated = true;
 281                 }
 282             }
 283         }
 284         
 285         // This ensure that the selection remains accurate when a shift occurs.
 286         final int selectedIndex = getSelectedIndex();
 287         if (selectedIndex >= position && selectedIndex > -1) {
 288             // Fix for RT-38787: we used to not enter this block if
 289             // selectedIndex + shift resulted in a value less than zero, whereas
 290             // now we just set the newSelectionLead to zero in that instance.
 291             // There exists unit tests that cover this.
 292             final int newSelectionLead = Math.max(0, selectedIndex + shift);
 293 
 294             setSelectedIndex(newSelectionLead);
 295 
 296             // added the selectedIndices call for RT-30356.
 297             // changed to check if hasPermutated, and to call select(..) for RT-40010.
 298             // This forces the selection event to go through the system and fire
 299             // the necessary events.
 300             if (hasPermutated) {
 301                 selectedIndices.set(newSelectionLead, true);
 302             } else {
 303                 select(newSelectionLead);
 304             }
 305 
 306             // removed due to RT-27185
 307 //            focus(newSelectionLead);
 308         }
 309 
 310         if (hasPermutated) {
 311             selectedIndicesSeq.callObservers(
 312                 new NonIterableChange.SimplePermutationChange<Integer>(
 313                         0, 
 314                         selectedIndicesCardinality, 
 315                         perm, 
 316                         selectedIndicesSeq));
 317         }
 318     }
 319 
 320     @Override public void clearAndSelect(int row) {
 321         if (row < 0 || row >= getItemCount()) {
 322             clearSelection();
 323             return;
 324         }
 325 
 326         final boolean wasSelected = isSelected(row);
 327 
 328         // RT-33558 if this method has been called with a given row, and that
 329         // row is the only selected row currently, then this method becomes a no-op.
 330         if (wasSelected && getSelectedIndices().size() == 1) {
 331             // before we return, we double-check that the selected item
 332             // is equal to the item in the given index
 333             if (getSelectedItem() == getModelItem(row)) {
 334                 return;
 335             }
 336         }
 337 
 338         // firstly we make a copy of the selection, so that we can send out
 339         // the correct details in the selection change event.
 340         // We remove the new selection from the list seeing as it is not removed.
 341         BitSet selectedIndicesCopy = new BitSet();
 342         selectedIndicesCopy.or(selectedIndices);
 343         selectedIndicesCopy.clear(row);
 344         List<Integer> previousSelectedIndices = createListFromBitSet(selectedIndicesCopy);
 345 
 346         // RT-32411 We used to call quietClearSelection() here, but this
 347         // resulted in the selectedItems and selectedIndices lists never
 348         // reporting that they were empty.
 349         // makeAtomic toggle added to resolve RT-32618
 350         startAtomic();
 351 
 352         // then clear the current selection
 353         clearSelection();
 354 
 355         // and select the new row
 356         select(row);
 357         stopAtomic();
 358 
 359         // fire off a single add/remove/replace notification (rather than
 360         // individual remove and add notifications) - see RT-33324
 361         ListChangeListener.Change<Integer> change;
 362 
 363         /*
 364          * getFrom() documentation:
 365          *   If wasAdded is true, the interval contains all the values that were added.
 366          *   If wasPermutated is true, the interval marks the values that were permutated.
 367          *   If wasRemoved is true and wasAdded is false, getFrom() and getTo() should
 368          *   return the same number - the place where the removed elements were positioned in the list.
 369          */
 370         if (wasSelected) {
 371             change = ControlUtils.buildClearAndSelectChange(selectedIndicesSeq, previousSelectedIndices, row);
 372         } else {
 373             int changeIndex = selectedIndicesSeq.indexOf(row);
 374             change = new NonIterableChange.GenericAddRemoveChange<>(
 375                     changeIndex, changeIndex+1, previousSelectedIndices, selectedIndicesSeq);
 376         }
 377 
 378         selectedIndicesSeq.callObservers(change);
 379     }
 380 
 381     @Override public void select(int row) {
 382         if (row == -1) {
 383             clearSelection();
 384             return;
 385         }
 386         if (row < 0 || row >= getItemCount()) {
 387             return;
 388         }
 389         
 390         boolean isSameRow = row == getSelectedIndex();
 391         T currentItem = getSelectedItem();
 392         T newItem = getModelItem(row);
 393         boolean isSameItem = newItem != null && newItem.equals(currentItem);
 394         boolean fireUpdatedItemEvent = isSameRow && ! isSameItem;
 395 
 396         startAtomic();
 397         if (! selectedIndices.get(row)) {
 398             if (getSelectionMode() == SINGLE) {
 399                 quietClearSelection();
 400             }
 401             selectedIndices.set(row);
 402         }
 403 
 404         setSelectedIndex(row);
 405         focus(row);
 406 
 407         stopAtomic();
 408 
 409         if (! isAtomic()) {
 410             int changeIndex = selectedIndicesSeq.indexOf(row);
 411             selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<Integer>(changeIndex, changeIndex+1, selectedIndicesSeq));
 412         }
 413         
 414         if (fireUpdatedItemEvent) {
 415             setSelectedItem(newItem);
 416         }
 417     }
 418 
 419     @Override public void select(T obj) {
 420 //        if (getItemCount() <= 0) return;
 421         
 422         if (obj == null && getSelectionMode() == SelectionMode.SINGLE) {
 423             clearSelection();
 424             return;
 425         }
 426         
 427         // We have no option but to iterate through the model and select the
 428         // first occurrence of the given object. Once we find the first one, we
 429         // don't proceed to select any others.
 430         Object rowObj = null;
 431         for (int i = 0, max = getItemCount(); i < max; i++) {
 432             rowObj = getModelItem(i);
 433             if (rowObj == null) continue;
 434 
 435             if (rowObj.equals(obj)) {
 436                 if (isSelected(i)) {
 437                     return;
 438                 }
 439 
 440                 if (getSelectionMode() == SINGLE) {
 441                     quietClearSelection();
 442                 }
 443 
 444                 select(i);
 445                 return;
 446             }
 447         }
 448 
 449         // if we are here, we did not find the item in the entire data model.
 450         // Even still, we allow for this item to be set to the give object.
 451         // We expect that in concrete subclasses of this class we observe the
 452         // data model such that we check to see if the given item exists in it,
 453         // whilst SelectedIndex == -1 && SelectedItem != null.
 454         setSelectedIndex(-1);
 455         setSelectedItem(obj);
 456     }
 457 
 458     @Override public void selectIndices(int row, int... rows) {
 459         if (rows == null || rows.length == 0) {
 460             select(row);
 461             return;
 462         }
 463 
 464         /*
 465          * Performance optimisation - if multiple selection is disabled, only
 466          * process the end-most row index.
 467          */
 468 
 469         int rowCount = getItemCount();
 470 
 471         if (getSelectionMode() == SINGLE) {
 472             quietClearSelection();
 473 
 474             for (int i = rows.length - 1; i >= 0; i--) {
 475                 int index = rows[i];
 476                 if (index >= 0 && index < rowCount) {
 477                     selectedIndices.set(index);
 478                     select(index);
 479                     break;
 480                 }
 481             }
 482 
 483             if (selectedIndices.isEmpty()) {
 484                 if (row > 0 && row < rowCount) {
 485                     selectedIndices.set(row);
 486                     select(row);
 487                 }
 488             }
 489 
 490             selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<Integer>(0, 1, selectedIndicesSeq));
 491         } else {
 492             final List<Integer> actualSelectedRows = new ArrayList<Integer>();
 493             
 494             int lastIndex = -1;
 495             if (row >= 0 && row < rowCount) {
 496                 lastIndex = row;
 497                 if (! selectedIndices.get(row)) {
 498                     selectedIndices.set(row);
 499                     actualSelectedRows.add(row);
 500                 }
 501             }
 502 
 503             for (int i = 0; i < rows.length; i++) {
 504                 int index = rows[i];
 505                 if (index < 0 || index >= rowCount) continue;
 506                 lastIndex = index;
 507                 
 508                 if (! selectedIndices.get(index)) {
 509                     selectedIndices.set(index);
 510                     actualSelectedRows.add(index);
 511                 }
 512             }
 513 
 514             if (lastIndex != -1) {
 515                 setSelectedIndex(lastIndex);
 516                 focus(lastIndex);
 517                 setSelectedItem(getModelItem(lastIndex));
 518             }
 519 
 520             // need to come up with ranges based on the actualSelectedRows, and
 521             // then fire the appropriate number of changes. We also need to
 522             // translate from a desired row to select to where that row is 
 523             // represented in the selectedIndices list. For example,
 524             // we may have requested to select row 5, and the selectedIndices
 525             // list may therefore have the following: [1,4,5], meaning row 5
 526             // is in position 2 of the selectedIndices list
 527             Collections.sort(actualSelectedRows);
 528             Change<Integer> change = createRangeChange(selectedIndicesSeq, actualSelectedRows);
 529             selectedIndicesSeq.callObservers(change);
 530         }
 531     }
 532     
 533     static Change<Integer> createRangeChange(final ObservableList<Integer> list, final List<Integer> addedItems) {
 534         Change<Integer> change = new Change<Integer>(list) {
 535             private final int[] EMPTY_PERM = new int[0];
 536             private final int addedSize = addedItems.size(); 
 537             
 538             private boolean invalid = true;
 539             
 540             private int pos = 0;
 541             private int from = pos;
 542             private int to = pos;
 543             
 544             @Override public int getFrom() {
 545                 checkState();
 546                 return from;
 547             }
 548 
 549             @Override public int getTo() {
 550                 checkState();
 551                 return to;
 552             }
 553 
 554             @Override public List<Integer> getRemoved() {
 555                 checkState();
 556                 return Collections.<Integer>emptyList();
 557             }
 558 
 559             @Override protected int[] getPermutation() {
 560                 checkState();
 561                 return EMPTY_PERM;
 562             }
 563             
 564             @Override public int getAddedSize() {
 565                 return to - from;
 566             }
 567 
 568             @Override public boolean next() {
 569                 if (pos >= addedSize) return false;
 570                 
 571                 // starting from pos, we keep going until the value is
 572                 // not the next value
 573                 int startValue = addedItems.get(pos++);
 574                 from = list.indexOf(startValue);
 575                 to = from + 1;
 576                 int endValue = startValue;
 577                 while (pos < addedSize) {
 578                     int previousEndValue = endValue;
 579                     endValue = addedItems.get(pos++);
 580                     ++to;
 581                     if (previousEndValue != (endValue - 1)) {
 582                         break;
 583                     }
 584                 }
 585 
 586                 if (invalid) {
 587                     invalid = false;
 588                     return true; 
 589                 }
 590                 
 591                 // we keep going until we've represented all changes!
 592                 return pos < addedSize;
 593             }
 594 
 595             @Override public void reset() {
 596                 invalid = true;
 597                 pos = 0;
 598             }
 599             
 600             private void checkState() {
 601                 if (invalid) {
 602                     throw new IllegalStateException("Invalid Change state: next() must be called before inspecting the Change.");
 603                 }
 604             }
 605             
 606         };
 607         return change;
 608     }
 609 
 610     @Override public void selectAll() {
 611         if (getSelectionMode() == SINGLE) return;
 612 
 613         if (getItemCount() <= 0) return;
 614 
 615         final int rowCount = getItemCount();
 616         final int focusedIndex = getFocusedIndex();
 617 
 618         // set all selected indices to true
 619         clearSelection();
 620         selectedIndices.set(0, rowCount, true);
 621         selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<>(0, rowCount, selectedIndicesSeq));
 622 
 623         if (focusedIndex == -1) {
 624             setSelectedIndex(rowCount - 1);
 625             focus(rowCount - 1);
 626         } else {
 627             setSelectedIndex(focusedIndex);
 628             focus(focusedIndex);
 629         }
 630     }
 631     
 632     @Override public void selectFirst() {
 633         if (getSelectionMode() == SINGLE) {
 634             quietClearSelection();
 635         }
 636             
 637         if (getItemCount() > 0) {
 638             select(0);
 639         }
 640     }
 641 
 642     @Override public void selectLast() {
 643         if (getSelectionMode() == SINGLE) {
 644             quietClearSelection();
 645         }
 646             
 647         int numItems = getItemCount();
 648         if (numItems > 0 && getSelectedIndex() < numItems - 1) {
 649             select(numItems - 1);
 650         }
 651     }
 652 
 653     @Override public void clearSelection(int index) {
 654         if (index < 0) return;
 655         
 656         // TODO shouldn't directly access like this
 657         // TODO might need to update focus and / or selected index/item
 658         boolean wasEmpty = selectedIndices.isEmpty();
 659         selectedIndices.clear(index);
 660         
 661         if (! wasEmpty && selectedIndices.isEmpty()) {
 662             clearSelection();
 663         }
 664 
 665         if (!isAtomic()) {
 666             // we pass in (index, index) here to represent that nothing was added
 667             // in this change.
 668             selectedIndicesSeq.callObservers(
 669                     new NonIterableChange.GenericAddRemoveChange<>(index, index,
 670                             Collections.singletonList(index), selectedIndicesSeq));
 671         }
 672     }
 673 
 674     @Override public void clearSelection() {
 675         List<Integer> removed = new AbstractList<Integer>() {
 676             final BitSet clone = (BitSet) selectedIndices.clone();
 677 
 678             @Override public Integer get(int index) {
 679                 return clone.nextSetBit(index);
 680             }
 681 
 682             @Override public int size() {
 683                 return clone.cardinality();
 684             }
 685         };
 686 
 687         quietClearSelection();
 688 
 689         if (! isAtomic()) {
 690             setSelectedIndex(-1);
 691             focus(-1);
 692             selectedIndicesSeq.callObservers(
 693                     new NonIterableChange.GenericAddRemoveChange<>(0, 0,
 694                     removed, selectedIndicesSeq));
 695         }
 696     }
 697 
 698     private void quietClearSelection() {
 699         selectedIndices.clear();
 700     }
 701 
 702     @Override public boolean isSelected(int index) {
 703         // Note the change in semantics here - we used to check to ensure that
 704         // the index is less than the item count, but now simply ensure that
 705         // it is less than the length of the selectedIndices bitset. This helps
 706         // to resolve issues such as RT-26721, where isSelected(int) was being
 707         // called for indices that exceeded the item count, as a TreeItem (e.g.
 708         // the root) was being collapsed.
 709 //        if (index >= 0 && index < getItemCount()) {
 710         if (index >= 0 && index < selectedIndices.length()) {
 711             return selectedIndices.get(index);
 712         }
 713 
 714         return false;
 715     }
 716 
 717     @Override public boolean isEmpty() {
 718         return selectedIndices.isEmpty();
 719     }
 720 
 721     @Override public void selectPrevious() {
 722         int focusIndex = getFocusedIndex();
 723 
 724         if (getSelectionMode() == SINGLE) {
 725             quietClearSelection();
 726         }
 727         
 728         if (focusIndex == -1) {
 729             select(getItemCount() - 1);
 730         } else if (focusIndex > 0) {
 731             select(focusIndex - 1);
 732         }
 733     }
 734 
 735     @Override public void selectNext() {
 736         int focusIndex = getFocusedIndex();
 737 
 738         if (getSelectionMode() == SINGLE) {
 739             quietClearSelection();
 740         }
 741 
 742         if (focusIndex == -1) {
 743             select(0);
 744         } else if (focusIndex != getItemCount() -1) {
 745             select(focusIndex + 1);
 746         }
 747     }
 748 
 749 
 750 
 751     /***********************************************************************
 752      *                                                                     *
 753      * Private implementation                                              *
 754      *                                                                     *
 755      **********************************************************************/
 756 
 757     private ReadOnlyUnbackedObservableList<Integer> createListFromBitSet(final BitSet bitset) {
 758         return new ReadOnlyUnbackedObservableList<Integer>() {
 759             private int lastGetIndex = -1;
 760             private int lastGetValue = -1;
 761 
 762             @Override public Integer get(int index) {
 763                 final int itemCount = getItemCount();
 764                 if (index < 0 || index >= itemCount) return -1;
 765 
 766                 if (index == (lastGetIndex + 1) && lastGetValue < itemCount) {
 767                     // we're iterating forward in order, short circuit for
 768                     // performance reasons (RT-39776)
 769                     lastGetIndex++;
 770                     lastGetValue = bitset.nextSetBit(lastGetValue + 1);
 771                     return lastGetValue;
 772                 } else if (index == (lastGetIndex - 1) && lastGetValue > 0) {
 773                     // we're iterating backward in order, short circuit for
 774                     // performance reasons (RT-39776)
 775                     lastGetIndex--;
 776                     lastGetValue = bitset.previousSetBit(lastGetValue - 1);
 777                     return lastGetValue;
 778                 } else {
 779                     for (lastGetIndex = 0, lastGetValue = bitset.nextSetBit(0);
 780                          lastGetValue >= 0 || lastGetIndex == index;
 781                          lastGetIndex++, lastGetValue = bitset.nextSetBit(lastGetValue + 1)) {
 782                         if (lastGetIndex == index) {
 783                             return lastGetValue;
 784                         }
 785                     }
 786                 }
 787 
 788                 return -1;
 789             }
 790 
 791             @Override public int size() {
 792                 return bitset.cardinality();
 793             }
 794 
 795             @Override public boolean contains(Object o) {
 796                 if (o instanceof Number) {
 797                     Number n = (Number) o;
 798                     int index = n.intValue();
 799 
 800                     return index >= 0 && index < bitset.length() &&
 801                             bitset.get(index);
 802                 }
 803 
 804                 return false;
 805             }
 806         };
 807     }
 808 }