/**
  * @return true if the delete was queued, false if the row was an unsaved addition and was deleted
  *     immediately.
  */
 public boolean queueDelete(T bean) {
   if (changeTracker.isPendingAdd(bean)) {
     cancelAdd(bean);
     return false;
   }
   changeTracker.pendingDelete(bean);
   int index = indexOf(bean);
   fireTableRowsUpdated(index, index);
   return true;
 }
 private void processChangeTrackerStopped(ChangeTracker tracker) {
   Log.d(Log.TAG_SYNC, "changeTrackerStopped.  lifecycle: %s", lifecycle);
   switch (lifecycle) {
     case ONESHOT:
       // TODO: This is too early to fire STOP_GRACEFUL, Need to change.
       Log.d(Log.TAG_SYNC, "fire STOP_GRACEFUL");
       if (tracker.getLastError() != null) {
         setError(tracker.getLastError());
       }
       stateMachine.fire(ReplicationTrigger.STOP_GRACEFUL);
       break;
     case CONTINUOUS:
       if (stateMachine.isInState(ReplicationState.OFFLINE)) {
         // in this case, we don't want to do anything here, since
         // we told the change tracker to go offline ..
         Log.d(Log.TAG_SYNC, "Change tracker stopped because we are going offline");
       } else if (stateMachine.isInState(ReplicationState.STOPPING)
           || stateMachine.isInState(ReplicationState.STOPPED)) {
         Log.d(Log.TAG_SYNC, "Change tracker stopped because replicator is stopping or stopped.");
       } else {
         // otherwise, try to restart the change tracker, since it should
         // always be running in continuous replications
         String msg = String.format("Change tracker stopped during continuous replication");
         Log.e(Log.TAG_SYNC, msg);
         parentReplication.setLastError(new Exception(msg));
         fireTrigger(ReplicationTrigger.WAITING_FOR_CHANGES);
         Log.d(
             Log.TAG_SYNC,
             "Scheduling change tracker restart in %d ms",
             CHANGE_TRACKER_RESTART_DELAY_MS);
         workExecutor.schedule(
             new Runnable() {
               @Override
               public void run() {
                 // the replication may have been stopped by the time this scheduled fires
                 // so we need to check the state here.
                 if (stateMachine.isInState(ReplicationState.RUNNING)) {
                   Log.d(Log.TAG_SYNC, "%s still running, restarting change tracker", this);
                   startChangeTracker();
                 } else {
                   Log.d(
                       Log.TAG_SYNC,
                       "%s still no longer running, not restarting change tracker",
                       this);
                 }
               }
             },
             CHANGE_TRACKER_RESTART_DELAY_MS,
             TimeUnit.MILLISECONDS);
       }
       break;
     default:
       Log.e(Log.TAG_SYNC, "Unknown lifecycle: %s", lifecycle);
   }
 }
  protected void startChangeTracker() {

    ChangeTracker.ChangeTrackerMode changeTrackerMode;

    // it always starts out as OneShot, but if its a continuous replication
    // it will switch to longpoll later.
    changeTrackerMode = ChangeTracker.ChangeTrackerMode.OneShot;

    Log.d(
        Log.TAG_SYNC,
        "%s: starting ChangeTracker with since=%s mode=%s",
        this,
        lastSequence,
        changeTrackerMode);
    changeTracker = new ChangeTracker(remote, changeTrackerMode, true, lastSequence, this);
    changeTracker.setAuthenticator(getAuthenticator());
    Log.d(Log.TAG_SYNC, "%s: started ChangeTracker %s", this, changeTracker);

    if (filterName != null) {
      changeTracker.setFilterName(filterName);
      if (filterParams != null) {
        changeTracker.setFilterParams(filterParams);
      }
    }
    changeTracker.setDocIDs(documentIDs);
    changeTracker.setRequestHeaders(requestHeaders);
    changeTracker.setContinuous(lifecycle == Replication.Lifecycle.CONTINUOUS);

    changeTracker.setUsePOST(serverIsSyncGatewayVersion("0.93"));
    changeTracker.start();
  }
  @Override
  protected void goOffline() {
    super.goOffline();

    // stop change tracker
    if (changeTracker != null) {
      changeTracker.stop();
    }

    // TODO: stop remote requests in progress, but first
    // TODO: write a test that verifies this actually works
  }
 private void cancelAdd(T bean) {
   changeTracker.resetItem(bean);
   removeRow(bean);
 }
 public void queueAdd(int row, T bean) {
   changeTracker.pendingAdd(bean);
   addRow(row, bean);
 }
 @Override
 public void setBeans(Collection<T> beans) {
   changeTracker.reset();
   super.setBeans(beans);
 }
 @Override
 public void undoDelete(int rowIndex) {
   changeTracker.undoDelete(getRow(rowIndex));
 }
 protected void pauseOrResume() {
   int pending = batcher.count() + pendingSequences.count();
   changeTracker.setPaused(pending >= MAX_PENDING_DOCS);
 }
 @Override
 public List<T> getPendingDeletes() {
   return changeTracker.getDeletes();
 }
 @Override
 public Stream<T> getChangedRows() {
   return changeTracker.getChanges();
 }
 @Override
 public boolean isChangedAt(int rowIndex, int columnIndex) {
   return changeTracker.isChanged(getRow(rowIndex), columnIndex);
 }
 @Override
 public boolean isChanged() {
   return !changeTracker.isEmpty();
 }
 @Override
 public void commit() {
   changeTracker.commit();
 }
 @Override
 public void revert() {
   changeTracker.revert();
 }
 @Override
 protected void setValue(Object value, int rowIndex, int columnIndex) {
   T row = getRow(rowIndex);
   changeTracker.setValue(row, columnIndex, getValue(row, columnIndex), value);
   super.setValue(value, rowIndex, columnIndex);
 }
 @Override
 public void undoChangedAt(int rowIndex, int columnIndex) {
   changeTracker.undoChange(getRow(rowIndex), columnIndex);
 }
 @Override
 public List<T> getPendingAdds() {
   return changeTracker.getAdds();
 }
 public boolean isPendingAdd(int rowIndex) {
   return changeTracker.isPendingAdd(getRow(rowIndex));
 }
 @Override
 public Stream<T> getPendingUpdates() {
   return changeTracker.getUpdates();
 }
 @Override
 public boolean isPendingDelete(int rowIndex) {
   return changeTracker.isPendingDelete(getRow(rowIndex));
 }