public synchronized void addIDataSetListenerIfAbsent(
      IDataSetListener listener, String listenerId) {
    Helper.checkNotNull(listener, "listener");
    Helper.checkNotNullNotTrimNotEmpty(listenerId, "listenerId");

    if (!listeners.containsKey(listenerId)) {
      listeners.put(listenerId, listener);
    }
  }
  protected DataStoreChangedEvent createEvent(
      IDataStore prev, final IDataStore curr, IDataSet set) {
    if (prev == null) {
      return new DataStoreChangedEvent(
          set,
          curr,
          curr,
          getAllRecords(curr),
          new ArrayList<IRecord>(0),
          new ArrayList<IRecord>(0));
    }
    // prev!=null
    Helper.checkNotNull(curr, "curr");

    IMetaData prevMeta = prev.getMetaData();
    IMetaData currMeta = curr.getMetaData();
    Assert.assertNotNull(prevMeta, "prevMeta");
    Assert.assertNotNull(currMeta, "currMeta");

    int prevFieldIndex = prevMeta.getIdFieldIndex();
    int currFieldIndex = currMeta.getIdFieldIndex();
    if (prevFieldIndex == ID_NOT_DEFINED || currFieldIndex == ID_NOT_DEFINED) {
      return new DataStoreChangedEvent(
          set, prev, curr, getAllRecords(curr), new ArrayList<IRecord>(0), getAllRecords(prev));
    }

    List<IRecord> updated =
        getUpdated(
            prev,
            new RecordIdRetriever() {

              public IRecord getRecordById(Object id) {
                return curr.getRecordByID(id);
              }
            },
            prevFieldIndex);
    List<IRecord> deleted = getDeleted(prev, curr, prevFieldIndex);
    List<IRecord> added =
        getAdded(
            prev,
            new RecordRetriever() {

              public IRecord getRecord(int index) {
                return curr.getRecordAt(index);
              }

              public int countRecords() {
                return (int) curr.getRecordsCount();
              }
            },
            prevFieldIndex);
    DataStoreChangedEvent res = new DataStoreChangedEvent(set, prev, curr, added, updated, deleted);
    return res;
  }
  public synchronized List<ListenerResult> changedDataSetUpdatedOrAdded(
      final List<IRecord> updatedOrAdded, int idFieldIndex) {
    Helper.checkNotNull(updatedOrAdded, "updatedOrAdded");
    Helper.checkNotNegative(idFieldIndex, "idFieldIndex");
    checkStateNotNull();

    IDataStore prev = this.store;
    // find added records
    List<IRecord> added = getAdded(updatedOrAdded, idFieldIndex, prev);

    // find updated records
    List<IRecord> updated = getUpdated(updatedOrAdded, idFieldIndex, prev);

    IDataStore curr = dataStoreCloner.clone(prev, added, new ArrayList<IRecord>(0), updated);
    this.store = curr;

    // use the current data set
    DataStoreChangedEvent event = createEvent(prev, curr, this.dataSet);
    List<ListenerResult> res = fireListeners(event);
    return res;
  }
  public synchronized List<ListenerResult> changedDataSet(IDataSet currDataSet) {
    Helper.checkNotNull(currDataSet, "currDataSet");

    IDataStore prev = this.store;
    IDataStore curr = currDataSet.getDataStore();
    Assert.assertNotNull(curr, "curr!=null");

    this.store = curr;
    this.dataSet = currDataSet;

    List<ListenerResult> res = fireListeners(createEvent(prev, curr, currDataSet));
    return res;
  }
  private List<ListenerResult> fireListeners(DataStoreChangedEvent event) {
    Helper.checkNotNull(event, "event");

    List<ListenerResult> res = new ArrayList<ListenerResult>();
    for (IDataSetListener l : listeners.values()) {
      try {
        l.dataStoreChanged(event);
        res.add(new ListenerResult());
      } catch (Exception e) {
        res.add(new ListenerResult(e));
      }
    }
    return res;
  }
  public synchronized List<ListenerResult> changedDataSet(List<IRecord> updated) {
    Helper.checkNotNull(updated, "updated");
    checkStateNotNull();

    IDataStore prev = this.store;
    IDataStore curr =
        dataStoreCloner.clone(prev, new ArrayList<IRecord>(0), new ArrayList<IRecord>(0), updated);
    this.store = curr;

    // use the current data set
    DataStoreChangedEvent event = createEvent(prev, curr, this.dataSet);
    List<ListenerResult> res = fireListeners(event);
    return res;
  }
  public synchronized boolean removeIDataSetListener(String listenerId) {
    Helper.checkNotNullNotTrimNotEmpty(listenerId, "listenerId");

    IDataSetListener res = listeners.remove(listenerId);
    return res != null;
  }