private int silentAdd(R record) {
    if (record == null) {
      throw new NullPointerException("Record is null passed into add() to store");
    }

    Long id = record.getRecordId();

    if (id == null) {
      id = ID_GENERATOR.next();
      try {
        record.setRecordId(id);
      } catch (Exception e) {
        // this can't happen if we have exclusive access to the object (2 threads?)
      }
    }

    R existing = data.get(id);
    if (existing != null) {
      throw new RuntimeException("The store already contains this");
    }

    // add it to our internal memory
    int index = data.add(id, record);

    // listen for change events. (null safe version)
    record.onChange().addObserver(this.onValueChanged);

    return index;
  }
        @Override
        public void notify(Object observable, DataEventObject<Record> e) {

          if (e != null && e.getData() != null) {
            if (!contains(e.getData().getRecordId())) {
              throw new RuntimeException(
                  "Assertion error. We've received an event for a record we do not know about. We probably didn't unregister a listener through the remove() event.");
            }
          }

          // one of our records changed!
          // the Observable here is the record.
          if (onChange != null) {
            @SuppressWarnings(
                value =
                    "unchecked") // Guaranteed type safe as R is defined as <R extends Record> and e
                                 // is a Record which is an interface
            R record = (R) e.getData();

            // we need to find the index for this.
            int index = data.indexOfValue(record);

            // rethrow but with an index.
            onChange.notifyObservers(this, getEventObject(record, index));
          }
        }
  @Override
  public void clearFilter() {

    data.clearFilter();

    // announce that a full redraw is necessary
    if (onLoad != null) {
      onLoad.notifyObservers(this, new EventObject(this));
    }
  }
  public void sort(final Comparator<R> comparator) {
    // the 2 different data structures require different API's
    // let's wrap another comparator around to adapt between them.
    data.sort(
        new Comparator<KeyValuePair<Long, R>>() {
          public int compare(KeyValuePair<Long, R> o1, KeyValuePair<Long, R> o2) {
            return comparator.compare(o1.getValue(), o2.getValue());
          }
        });

    if (onLoad != null) {
      onLoad.notifyObservers(this, getEventObject());
    }
  }
  @Override
  public void setFilter(final Filter<R> filter) {
    // do the filter (NOTE: we ignore events from the MixedCollection)
    // so we have to throw our own event manually
    data.setFilter(
        new Filter<KeyValuePair<Long, R>>() {

          public boolean call(KeyValuePair<Long, R> item) throws Exception {
            return filter.call(item.getValue());
          }
        });

    // announce that a full redraw is necessary
    if (onLoad != null) {
      onLoad.notifyObservers(this, new EventObject(this));
    }
  }
  private void remove(R record) {
    if (record == null) {
      return;
    }

    Long id = record.getRecordId();
    if (!contains(id)) {
      return;
    }

    // need to know where it is so we can update the GUI efficiently
    int index = indexOf(id);

    // kill from our data backing.
    // NOTE: this MUST pierce the veil of filtering
    data.remove(id);

    // stop listening for changes.
    record.onChange().removeObserver(this.onValueChanged);

    if (this.onRemove != null) {
      this.onRemove.notifyObservers(this, getEventObject(record, index));
    }
  }
 @Override
 public boolean isFiltered() {
   return data.isFiltered();
 }
 @Override
 public boolean contains(Long recordId) {
   return data.containsKey(recordId);
 }
 @Override
 public int indexOf(Long id) {
   return data.indexOfKey(id);
 }
 @Override
 public R getAt(int index) {
   return data.getAt(index);
 }
 @Override
 public int size() {
   return data.size();
 }
 @Override
 public R get(Long id) {
   return data.get(id);
 }